From 126f2df21b2b65972d3ae4d4143ea52d762dec19 Mon Sep 17 00:00:00 2001 From: "Julien G." <66769052+jubnl@users.noreply.github.com> Date: Tue, 26 May 2026 20:27:29 +0200 Subject: [PATCH] chore: move i18n to shared package (#1066) * chore: move i18n to shared package * chore: move server translations to shared package and apply linter and prettier on entire shared package --- .issue-scratch/759.png | Bin 24257 -> 0 bytes .issue-scratch/764-1.png | Bin 466058 -> 0 bytes .issue-scratch/764-2.png | Bin 65535 -> 0 bytes .issue-scratch/release-draft-new.md | 524 ---- .issue-scratch/release-draft.md | 405 --- client/src/api/client.ts | 42 +- .../src/components/Layout/BottomNav.test.tsx | 16 +- client/src/i18n/TranslationContext.tsx | 111 +- client/src/i18n/supportedLanguages.ts | 29 +- client/src/i18n/translations/ar.ts | 2164 --------------- client/src/i18n/translations/br.ts | 2367 ---------------- client/src/i18n/translations/cs.ts | 2371 ---------------- client/src/i18n/translations/de.ts | 2377 ---------------- client/src/i18n/translations/en.ts | 2429 ---------------- client/src/i18n/translations/es.ts | 2373 ---------------- client/src/i18n/translations/fr.ts | 2367 ---------------- client/src/i18n/translations/hu.ts | 2368 ---------------- client/src/i18n/translations/id.ts | 2408 ---------------- client/src/i18n/translations/it.ts | 2368 ---------------- client/src/i18n/translations/ja.ts | 2431 ----------------- client/src/i18n/translations/ko.ts | 2428 ---------------- client/src/i18n/translations/nl.ts | 2367 ---------------- client/src/i18n/translations/pl.ts | 2359 ---------------- client/src/i18n/translations/ru.ts | 2367 ---------------- client/src/i18n/translations/tr.ts | 2431 ----------------- client/src/i18n/translations/uk.ts | 2390 ---------------- client/src/i18n/translations/zh.ts | 2367 ---------------- client/src/i18n/translations/zhTw.ts | 2367 ---------------- package-lock.json | 307 +++ scripts/migrate-i18n.mts | 117 + server/src/config.ts | 5 +- server/src/services/notifications.ts | 229 +- shared/eslint.config.mjs | 29 + shared/package.json | 23 +- shared/src/common/primitives.schema.spec.ts | 19 +- shared/src/i18n/ar/admin.ts | 319 +++ shared/src/i18n/ar/airport.ts | 6 + shared/src/i18n/ar/atlas.ts | 58 + shared/src/i18n/ar/backup.ts | 75 + shared/src/i18n/ar/budget.ts | 42 + shared/src/i18n/ar/categories.ts | 26 + shared/src/i18n/ar/collab.ts | 72 + shared/src/i18n/ar/common.ts | 51 + shared/src/i18n/ar/dashboard.ts | 82 + shared/src/i18n/ar/day.ts | 26 + shared/src/i18n/ar/dayplan.ts | 38 + shared/src/i18n/ar/externalNotifications.ts | 63 + shared/src/i18n/ar/files.ts | 62 + shared/src/i18n/ar/index.ts | 86 + shared/src/i18n/ar/inspector.ts | 22 + shared/src/i18n/ar/journey.ts | 56 + shared/src/i18n/ar/login.ts | 91 + shared/src/i18n/ar/map.ts | 8 + shared/src/i18n/ar/members.ts | 24 + shared/src/i18n/ar/memories.ts | 77 + shared/src/i18n/ar/nav.ts | 15 + shared/src/i18n/ar/notif.ts | 41 + shared/src/i18n/ar/notifications.ts | 37 + shared/src/i18n/ar/oauth.ts | 93 + shared/src/i18n/ar/packing.ts | 184 ++ shared/src/i18n/ar/pdf.ts | 10 + shared/src/i18n/ar/perm.ts | 56 + shared/src/i18n/ar/photos.ts | 26 + shared/src/i18n/ar/places.ts | 90 + shared/src/i18n/ar/planner.ts | 67 + shared/src/i18n/ar/register.ts | 25 + shared/src/i18n/ar/reservations.ts | 117 + shared/src/i18n/ar/settings.ts | 274 ++ shared/src/i18n/ar/share.ts | 16 + shared/src/i18n/ar/shared.ts | 21 + shared/src/i18n/ar/stats.ts | 13 + shared/src/i18n/ar/system_notice.ts | 51 + shared/src/i18n/ar/todo.ts | 40 + shared/src/i18n/ar/transport.ts | 10 + shared/src/i18n/ar/trip.ts | 31 + shared/src/i18n/ar/trips.ts | 17 + shared/src/i18n/ar/undo.ts | 21 + shared/src/i18n/ar/vacay.ts | 96 + shared/src/i18n/br/admin.ts | 367 +++ shared/src/i18n/br/airport.ts | 6 + shared/src/i18n/br/atlas.ts | 59 + shared/src/i18n/br/backup.ts | 78 + shared/src/i18n/br/budget.ts | 43 + shared/src/i18n/br/categories.ts | 26 + shared/src/i18n/br/collab.ts | 75 + shared/src/i18n/br/common.ts | 54 + shared/src/i18n/br/dashboard.ts | 107 + shared/src/i18n/br/day.ts | 27 + shared/src/i18n/br/dayplan.ts | 46 + shared/src/i18n/br/externalNotifications.ts | 63 + shared/src/i18n/br/files.ts | 63 + shared/src/i18n/br/index.ts | 86 + shared/src/i18n/br/inspector.ts | 22 + shared/src/i18n/br/journey.ts | 240 ++ shared/src/i18n/br/login.ts | 95 + shared/src/i18n/br/map.ts | 8 + shared/src/i18n/br/members.ts | 24 + shared/src/i18n/br/memories.ts | 83 + shared/src/i18n/br/nav.ts | 20 + shared/src/i18n/br/notif.ts | 42 + shared/src/i18n/br/notifications.ts | 37 + shared/src/i18n/br/oauth.ts | 98 + shared/src/i18n/br/packing.ts | 185 ++ shared/src/i18n/br/pdf.ts | 10 + shared/src/i18n/br/perm.ts | 64 + shared/src/i18n/br/photos.ts | 25 + shared/src/i18n/br/places.ts | 92 + shared/src/i18n/br/planner.ts | 69 + shared/src/i18n/br/register.ts | 25 + shared/src/i18n/br/reservations.ts | 118 + shared/src/i18n/br/settings.ts | 298 ++ shared/src/i18n/br/share.ts | 16 + shared/src/i18n/br/shared.ts | 22 + shared/src/i18n/br/stats.ts | 13 + shared/src/i18n/br/system_notice.ts | 64 + shared/src/i18n/br/todo.ts | 40 + shared/src/i18n/br/transport.ts | 10 + shared/src/i18n/br/trip.ts | 31 + shared/src/i18n/br/trips.ts | 17 + shared/src/i18n/br/undo.ts | 21 + shared/src/i18n/br/vacay.ts | 108 + shared/src/i18n/cs/admin.ts | 362 +++ shared/src/i18n/cs/airport.ts | 6 + shared/src/i18n/cs/atlas.ts | 59 + shared/src/i18n/cs/backup.ts | 76 + shared/src/i18n/cs/budget.ts | 43 + shared/src/i18n/cs/categories.ts | 26 + shared/src/i18n/cs/collab.ts | 74 + shared/src/i18n/cs/common.ts | 54 + shared/src/i18n/cs/dashboard.ts | 107 + shared/src/i18n/cs/day.ts | 27 + shared/src/i18n/cs/dayplan.ts | 46 + shared/src/i18n/cs/externalNotifications.ts | 63 + shared/src/i18n/cs/files.ts | 62 + shared/src/i18n/cs/index.ts | 86 + shared/src/i18n/cs/inspector.ts | 22 + shared/src/i18n/cs/journey.ts | 239 ++ shared/src/i18n/cs/login.ts | 95 + shared/src/i18n/cs/map.ts | 8 + shared/src/i18n/cs/members.ts | 24 + shared/src/i18n/cs/memories.ts | 81 + shared/src/i18n/cs/nav.ts | 20 + shared/src/i18n/cs/notif.ts | 42 + shared/src/i18n/cs/notifications.ts | 37 + shared/src/i18n/cs/oauth.ts | 98 + shared/src/i18n/cs/packing.ts | 186 ++ shared/src/i18n/cs/pdf.ts | 10 + shared/src/i18n/cs/perm.ts | 59 + shared/src/i18n/cs/photos.ts | 25 + shared/src/i18n/cs/places.ts | 91 + shared/src/i18n/cs/planner.ts | 68 + shared/src/i18n/cs/register.ts | 26 + shared/src/i18n/cs/reservations.ts | 117 + shared/src/i18n/cs/settings.ts | 299 ++ shared/src/i18n/cs/share.ts | 16 + shared/src/i18n/cs/shared.ts | 21 + shared/src/i18n/cs/stats.ts | 13 + shared/src/i18n/cs/system_notice.ts | 59 + shared/src/i18n/cs/todo.ts | 40 + shared/src/i18n/cs/transport.ts | 10 + shared/src/i18n/cs/trip.ts | 31 + shared/src/i18n/cs/trips.ts | 17 + shared/src/i18n/cs/undo.ts | 21 + shared/src/i18n/cs/vacay.ts | 99 + shared/src/i18n/de/admin.ts | 365 +++ shared/src/i18n/de/airport.ts | 6 + shared/src/i18n/de/atlas.ts | 58 + shared/src/i18n/de/backup.ts | 78 + shared/src/i18n/de/budget.ts | 44 + shared/src/i18n/de/categories.ts | 26 + shared/src/i18n/de/collab.ts | 75 + shared/src/i18n/de/common.ts | 55 + shared/src/i18n/de/dashboard.ts | 108 + shared/src/i18n/de/day.ts | 27 + shared/src/i18n/de/dayplan.ts | 48 + shared/src/i18n/de/externalNotifications.ts | 64 + shared/src/i18n/de/files.ts | 63 + shared/src/i18n/de/index.ts | 86 + shared/src/i18n/de/inspector.ts | 22 + shared/src/i18n/de/journey.ts | 246 ++ shared/src/i18n/de/login.ts | 98 + shared/src/i18n/de/map.ts | 8 + shared/src/i18n/de/members.ts | 24 + shared/src/i18n/de/memories.ts | 82 + shared/src/i18n/de/nav.ts | 20 + shared/src/i18n/de/notif.ts | 44 + shared/src/i18n/de/notifications.ts | 39 + shared/src/i18n/de/oauth.ts | 99 + shared/src/i18n/de/packing.ts | 186 ++ shared/src/i18n/de/pdf.ts | 10 + shared/src/i18n/de/perm.ts | 63 + shared/src/i18n/de/photos.ts | 25 + shared/src/i18n/de/places.ts | 92 + shared/src/i18n/de/planner.ts | 68 + shared/src/i18n/de/register.ts | 26 + shared/src/i18n/de/reservations.ts | 119 + shared/src/i18n/de/settings.ts | 302 ++ shared/src/i18n/de/share.ts | 16 + shared/src/i18n/de/shared.ts | 21 + shared/src/i18n/de/stats.ts | 13 + shared/src/i18n/de/system_notice.ts | 64 + shared/src/i18n/de/todo.ts | 40 + shared/src/i18n/de/transport.ts | 10 + shared/src/i18n/de/trip.ts | 31 + shared/src/i18n/de/trips.ts | 17 + shared/src/i18n/de/undo.ts | 21 + shared/src/i18n/de/vacay.ts | 105 + shared/src/i18n/en/admin.ts | 358 +++ shared/src/i18n/en/airport.ts | 6 + shared/src/i18n/en/atlas.ts | 58 + shared/src/i18n/en/backup.ts | 77 + shared/src/i18n/en/budget.ts | 43 + shared/src/i18n/en/categories.ts | 26 + shared/src/i18n/en/collab.ts | 74 + shared/src/i18n/en/common.ts | 54 + shared/src/i18n/en/dashboard.ts | 121 + shared/src/i18n/en/day.ts | 26 + shared/src/i18n/en/dayplan.ts | 48 + shared/src/i18n/en/externalNotifications.ts | 63 + shared/src/i18n/en/files.ts | 62 + shared/src/i18n/en/index.ts | 86 + shared/src/i18n/en/inspector.ts | 22 + shared/src/i18n/en/journey.ts | 244 ++ shared/src/i18n/en/login.ts | 95 + shared/src/i18n/en/map.ts | 8 + shared/src/i18n/en/members.ts | 24 + shared/src/i18n/en/memories.ts | 80 + shared/src/i18n/en/nav.ts | 20 + shared/src/i18n/en/notif.ts | 41 + shared/src/i18n/en/notifications.ts | 38 + shared/src/i18n/en/oauth.ts | 99 + shared/src/i18n/en/packing.ts | 186 ++ shared/src/i18n/en/pdf.ts | 10 + shared/src/i18n/en/perm.ts | 57 + shared/src/i18n/en/photos.ts | 25 + shared/src/i18n/en/places.ts | 91 + shared/src/i18n/en/planner.ts | 68 + shared/src/i18n/en/register.ts | 25 + shared/src/i18n/en/reservations.ts | 118 + shared/src/i18n/en/settings.ts | 292 ++ shared/src/i18n/en/share.ts | 16 + shared/src/i18n/en/shared.ts | 21 + shared/src/i18n/en/stats.ts | 13 + shared/src/i18n/en/system_notice.ts | 60 + shared/src/i18n/en/todo.ts | 40 + shared/src/i18n/en/transport.ts | 10 + shared/src/i18n/en/trip.ts | 31 + shared/src/i18n/en/trips.ts | 17 + shared/src/i18n/en/undo.ts | 21 + shared/src/i18n/en/vacay.ts | 103 + shared/src/i18n/es/admin.ts | 373 +++ shared/src/i18n/es/airport.ts | 6 + shared/src/i18n/es/atlas.ts | 60 + shared/src/i18n/es/backup.ts | 79 + shared/src/i18n/es/budget.ts | 44 + shared/src/i18n/es/categories.ts | 26 + shared/src/i18n/es/collab.ts | 75 + shared/src/i18n/es/common.ts | 55 + shared/src/i18n/es/dashboard.ts | 107 + shared/src/i18n/es/day.ts | 27 + shared/src/i18n/es/dayplan.ts | 46 + shared/src/i18n/es/externalNotifications.ts | 64 + shared/src/i18n/es/files.ts | 63 + shared/src/i18n/es/index.ts | 86 + shared/src/i18n/es/inspector.ts | 22 + shared/src/i18n/es/journey.ts | 240 ++ shared/src/i18n/es/login.ts | 99 + shared/src/i18n/es/map.ts | 8 + shared/src/i18n/es/members.ts | 24 + shared/src/i18n/es/memories.ts | 82 + shared/src/i18n/es/nav.ts | 20 + shared/src/i18n/es/notif.ts | 42 + shared/src/i18n/es/notifications.ts | 38 + shared/src/i18n/es/oauth.ts | 98 + shared/src/i18n/es/packing.ts | 186 ++ shared/src/i18n/es/pdf.ts | 10 + shared/src/i18n/es/perm.ts | 63 + shared/src/i18n/es/photos.ts | 25 + shared/src/i18n/es/places.ts | 92 + shared/src/i18n/es/planner.ts | 68 + shared/src/i18n/es/register.ts | 25 + shared/src/i18n/es/reservations.ts | 118 + shared/src/i18n/es/settings.ts | 299 ++ shared/src/i18n/es/share.ts | 16 + shared/src/i18n/es/shared.ts | 21 + shared/src/i18n/es/stats.ts | 13 + shared/src/i18n/es/system_notice.ts | 64 + shared/src/i18n/es/todo.ts | 40 + shared/src/i18n/es/transport.ts | 10 + shared/src/i18n/es/trip.ts | 31 + shared/src/i18n/es/trips.ts | 17 + shared/src/i18n/es/undo.ts | 21 + shared/src/i18n/es/vacay.ts | 106 + .../src/i18n/externalNotifications/index.ts | 64 + .../src/i18n/externalNotifications/types.ts | 40 + shared/src/i18n/fr/admin.ts | 373 +++ shared/src/i18n/fr/airport.ts | 6 + shared/src/i18n/fr/atlas.ts | 60 + shared/src/i18n/fr/backup.ts | 79 + shared/src/i18n/fr/budget.ts | 44 + shared/src/i18n/fr/categories.ts | 26 + shared/src/i18n/fr/collab.ts | 76 + shared/src/i18n/fr/common.ts | 54 + shared/src/i18n/fr/dashboard.ts | 110 + shared/src/i18n/fr/day.ts | 27 + shared/src/i18n/fr/dayplan.ts | 46 + shared/src/i18n/fr/externalNotifications.ts | 64 + shared/src/i18n/fr/files.ts | 63 + shared/src/i18n/fr/index.ts | 86 + shared/src/i18n/fr/inspector.ts | 22 + shared/src/i18n/fr/journey.ts | 241 ++ shared/src/i18n/fr/login.ts | 102 + shared/src/i18n/fr/map.ts | 8 + shared/src/i18n/fr/members.ts | 24 + shared/src/i18n/fr/memories.ts | 84 + shared/src/i18n/fr/nav.ts | 20 + shared/src/i18n/fr/notif.ts | 45 + shared/src/i18n/fr/notifications.ts | 39 + shared/src/i18n/fr/oauth.ts | 99 + shared/src/i18n/fr/packing.ts | 186 ++ shared/src/i18n/fr/pdf.ts | 10 + shared/src/i18n/fr/perm.ts | 65 + shared/src/i18n/fr/photos.ts | 25 + shared/src/i18n/fr/places.ts | 93 + shared/src/i18n/fr/planner.ts | 69 + shared/src/i18n/fr/register.ts | 27 + shared/src/i18n/fr/reservations.ts | 119 + shared/src/i18n/fr/settings.ts | 303 ++ shared/src/i18n/fr/share.ts | 16 + shared/src/i18n/fr/shared.ts | 21 + shared/src/i18n/fr/stats.ts | 13 + shared/src/i18n/fr/system_notice.ts | 63 + shared/src/i18n/fr/todo.ts | 41 + shared/src/i18n/fr/transport.ts | 10 + shared/src/i18n/fr/trip.ts | 31 + shared/src/i18n/fr/trips.ts | 17 + shared/src/i18n/fr/undo.ts | 21 + shared/src/i18n/fr/vacay.ts | 109 + shared/src/i18n/hu/admin.ts | 366 +++ shared/src/i18n/hu/airport.ts | 6 + shared/src/i18n/hu/atlas.ts | 62 + shared/src/i18n/hu/backup.ts | 77 + shared/src/i18n/hu/budget.ts | 44 + shared/src/i18n/hu/categories.ts | 26 + shared/src/i18n/hu/collab.ts | 76 + shared/src/i18n/hu/common.ts | 55 + shared/src/i18n/hu/dashboard.ts | 108 + shared/src/i18n/hu/day.ts | 27 + shared/src/i18n/hu/dayplan.ts | 46 + shared/src/i18n/hu/externalNotifications.ts | 64 + shared/src/i18n/hu/files.ts | 62 + shared/src/i18n/hu/index.ts | 86 + shared/src/i18n/hu/inspector.ts | 22 + shared/src/i18n/hu/journey.ts | 240 ++ shared/src/i18n/hu/login.ts | 102 + shared/src/i18n/hu/map.ts | 8 + shared/src/i18n/hu/members.ts | 24 + shared/src/i18n/hu/memories.ts | 82 + shared/src/i18n/hu/nav.ts | 20 + shared/src/i18n/hu/notif.ts | 45 + shared/src/i18n/hu/notifications.ts | 37 + shared/src/i18n/hu/oauth.ts | 99 + shared/src/i18n/hu/packing.ts | 186 ++ shared/src/i18n/hu/pdf.ts | 10 + shared/src/i18n/hu/perm.ts | 65 + shared/src/i18n/hu/photos.ts | 25 + shared/src/i18n/hu/places.ts | 93 + shared/src/i18n/hu/planner.ts | 68 + shared/src/i18n/hu/register.ts | 27 + shared/src/i18n/hu/reservations.ts | 119 + shared/src/i18n/hu/settings.ts | 301 ++ shared/src/i18n/hu/share.ts | 16 + shared/src/i18n/hu/shared.ts | 21 + shared/src/i18n/hu/stats.ts | 13 + shared/src/i18n/hu/system_notice.ts | 58 + shared/src/i18n/hu/todo.ts | 40 + shared/src/i18n/hu/transport.ts | 10 + shared/src/i18n/hu/trip.ts | 31 + shared/src/i18n/hu/trips.ts | 17 + shared/src/i18n/hu/undo.ts | 21 + shared/src/i18n/hu/vacay.ts | 107 + shared/src/i18n/id/admin.ts | 361 +++ shared/src/i18n/id/airport.ts | 6 + shared/src/i18n/id/atlas.ts | 59 + shared/src/i18n/id/backup.ts | 77 + shared/src/i18n/id/budget.ts | 43 + shared/src/i18n/id/categories.ts | 26 + shared/src/i18n/id/collab.ts | 74 + shared/src/i18n/id/common.ts | 54 + shared/src/i18n/id/dashboard.ts | 107 + shared/src/i18n/id/day.ts | 27 + shared/src/i18n/id/dayplan.ts | 46 + shared/src/i18n/id/externalNotifications.ts | 64 + shared/src/i18n/id/files.ts | 62 + shared/src/i18n/id/index.ts | 86 + shared/src/i18n/id/inspector.ts | 22 + shared/src/i18n/id/journey.ts | 241 ++ shared/src/i18n/id/login.ts | 97 + shared/src/i18n/id/map.ts | 8 + shared/src/i18n/id/members.ts | 24 + shared/src/i18n/id/memories.ts | 80 + shared/src/i18n/id/nav.ts | 20 + shared/src/i18n/id/notif.ts | 42 + shared/src/i18n/id/notifications.ts | 38 + shared/src/i18n/id/oauth.ts | 99 + shared/src/i18n/id/packing.ts | 186 ++ shared/src/i18n/id/pdf.ts | 10 + shared/src/i18n/id/perm.ts | 66 + shared/src/i18n/id/photos.ts | 25 + shared/src/i18n/id/places.ts | 92 + shared/src/i18n/id/planner.ts | 68 + shared/src/i18n/id/register.ts | 25 + shared/src/i18n/id/reservations.ts | 118 + shared/src/i18n/id/settings.ts | 299 ++ shared/src/i18n/id/share.ts | 16 + shared/src/i18n/id/shared.ts | 21 + shared/src/i18n/id/stats.ts | 13 + shared/src/i18n/id/system_notice.ts | 61 + shared/src/i18n/id/todo.ts | 40 + shared/src/i18n/id/transport.ts | 10 + shared/src/i18n/id/trip.ts | 31 + shared/src/i18n/id/trips.ts | 17 + shared/src/i18n/id/undo.ts | 21 + shared/src/i18n/id/vacay.ts | 106 + shared/src/i18n/index.ts | 2 + shared/src/i18n/it/admin.ts | 369 +++ shared/src/i18n/it/airport.ts | 6 + shared/src/i18n/it/atlas.ts | 61 + shared/src/i18n/it/backup.ts | 77 + shared/src/i18n/it/budget.ts | 44 + shared/src/i18n/it/categories.ts | 26 + shared/src/i18n/it/collab.ts | 75 + shared/src/i18n/it/common.ts | 54 + shared/src/i18n/it/dashboard.ts | 110 + shared/src/i18n/it/day.ts | 27 + shared/src/i18n/it/dayplan.ts | 46 + shared/src/i18n/it/externalNotifications.ts | 64 + shared/src/i18n/it/files.ts | 62 + shared/src/i18n/it/index.ts | 86 + shared/src/i18n/it/inspector.ts | 22 + shared/src/i18n/it/journey.ts | 240 ++ shared/src/i18n/it/login.ts | 96 + shared/src/i18n/it/map.ts | 8 + shared/src/i18n/it/members.ts | 24 + shared/src/i18n/it/memories.ts | 82 + shared/src/i18n/it/nav.ts | 20 + shared/src/i18n/it/notif.ts | 42 + shared/src/i18n/it/notifications.ts | 37 + shared/src/i18n/it/oauth.ts | 99 + shared/src/i18n/it/packing.ts | 186 ++ shared/src/i18n/it/pdf.ts | 10 + shared/src/i18n/it/perm.ts | 63 + shared/src/i18n/it/photos.ts | 25 + shared/src/i18n/it/places.ts | 92 + shared/src/i18n/it/planner.ts | 69 + shared/src/i18n/it/register.ts | 26 + shared/src/i18n/it/reservations.ts | 120 + shared/src/i18n/it/settings.ts | 298 ++ shared/src/i18n/it/share.ts | 16 + shared/src/i18n/it/shared.ts | 21 + shared/src/i18n/it/stats.ts | 13 + shared/src/i18n/it/system_notice.ts | 62 + shared/src/i18n/it/todo.ts | 40 + shared/src/i18n/it/transport.ts | 10 + shared/src/i18n/it/trip.ts | 31 + shared/src/i18n/it/trips.ts | 17 + shared/src/i18n/it/undo.ts | 21 + shared/src/i18n/it/vacay.ts | 105 + shared/src/i18n/ja/admin.ts | 340 +++ shared/src/i18n/ja/airport.ts | 6 + shared/src/i18n/ja/atlas.ts | 58 + shared/src/i18n/ja/backup.ts | 77 + shared/src/i18n/ja/budget.ts | 42 + shared/src/i18n/ja/categories.ts | 26 + shared/src/i18n/ja/collab.ts | 74 + shared/src/i18n/ja/common.ts | 55 + shared/src/i18n/ja/dashboard.ts | 120 + shared/src/i18n/ja/day.ts | 26 + shared/src/i18n/ja/dayplan.ts | 43 + shared/src/i18n/ja/externalNotifications.ts | 63 + shared/src/i18n/ja/files.ts | 62 + shared/src/i18n/ja/index.ts | 86 + shared/src/i18n/ja/inspector.ts | 22 + shared/src/i18n/ja/journey.ts | 240 ++ shared/src/i18n/ja/login.ts | 95 + shared/src/i18n/ja/map.ts | 8 + shared/src/i18n/ja/members.ts | 24 + shared/src/i18n/ja/memories.ts | 79 + shared/src/i18n/ja/nav.ts | 20 + shared/src/i18n/ja/notif.ts | 44 + shared/src/i18n/ja/notifications.ts | 37 + shared/src/i18n/ja/oauth.ts | 83 + shared/src/i18n/ja/packing.ts | 185 ++ shared/src/i18n/ja/pdf.ts | 10 + shared/src/i18n/ja/perm.ts | 51 + shared/src/i18n/ja/photos.ts | 25 + shared/src/i18n/ja/places.ts | 93 + shared/src/i18n/ja/planner.ts | 67 + shared/src/i18n/ja/register.ts | 25 + shared/src/i18n/ja/reservations.ts | 116 + shared/src/i18n/ja/settings.ts | 278 ++ shared/src/i18n/ja/share.ts | 15 + shared/src/i18n/ja/shared.ts | 21 + shared/src/i18n/ja/stats.ts | 13 + shared/src/i18n/ja/system_notice.ts | 53 + shared/src/i18n/ja/todo.ts | 40 + shared/src/i18n/ja/transport.ts | 10 + shared/src/i18n/ja/trip.ts | 31 + shared/src/i18n/ja/trips.ts | 17 + shared/src/i18n/ja/undo.ts | 21 + shared/src/i18n/ja/vacay.ts | 97 + shared/src/i18n/ko/admin.ts | 353 +++ shared/src/i18n/ko/airport.ts | 6 + shared/src/i18n/ko/atlas.ts | 58 + shared/src/i18n/ko/backup.ts | 74 + shared/src/i18n/ko/budget.ts | 42 + shared/src/i18n/ko/categories.ts | 26 + shared/src/i18n/ko/collab.ts | 73 + shared/src/i18n/ko/common.ts | 55 + shared/src/i18n/ko/dashboard.ts | 120 + shared/src/i18n/ko/day.ts | 26 + shared/src/i18n/ko/dayplan.ts | 46 + shared/src/i18n/ko/externalNotifications.ts | 63 + shared/src/i18n/ko/files.ts | 62 + shared/src/i18n/ko/index.ts | 86 + shared/src/i18n/ko/inspector.ts | 22 + shared/src/i18n/ko/journey.ts | 244 ++ shared/src/i18n/ko/login.ts | 93 + shared/src/i18n/ko/map.ts | 8 + shared/src/i18n/ko/members.ts | 24 + shared/src/i18n/ko/memories.ts | 80 + shared/src/i18n/ko/nav.ts | 20 + shared/src/i18n/ko/notif.ts | 43 + shared/src/i18n/ko/notifications.ts | 39 + shared/src/i18n/ko/oauth.ts | 87 + shared/src/i18n/ko/packing.ts | 185 ++ shared/src/i18n/ko/pdf.ts | 10 + shared/src/i18n/ko/perm.ts | 60 + shared/src/i18n/ko/photos.ts | 25 + shared/src/i18n/ko/places.ts | 90 + shared/src/i18n/ko/planner.ts | 67 + shared/src/i18n/ko/register.ts | 25 + shared/src/i18n/ko/reservations.ts | 116 + shared/src/i18n/ko/settings.ts | 295 ++ shared/src/i18n/ko/share.ts | 16 + shared/src/i18n/ko/shared.ts | 21 + shared/src/i18n/ko/stats.ts | 13 + shared/src/i18n/ko/system_notice.ts | 56 + shared/src/i18n/ko/todo.ts | 40 + shared/src/i18n/ko/transport.ts | 10 + shared/src/i18n/ko/trip.ts | 31 + shared/src/i18n/ko/trips.ts | 17 + shared/src/i18n/ko/undo.ts | 21 + shared/src/i18n/ko/vacay.ts | 99 + shared/src/i18n/languages.ts | 49 + shared/src/i18n/nl/admin.ts | 364 +++ shared/src/i18n/nl/airport.ts | 6 + shared/src/i18n/nl/atlas.ts | 59 + shared/src/i18n/nl/backup.ts | 78 + shared/src/i18n/nl/budget.ts | 44 + shared/src/i18n/nl/categories.ts | 26 + shared/src/i18n/nl/collab.ts | 73 + shared/src/i18n/nl/common.ts | 54 + shared/src/i18n/nl/dashboard.ts | 107 + shared/src/i18n/nl/day.ts | 27 + shared/src/i18n/nl/dayplan.ts | 46 + shared/src/i18n/nl/externalNotifications.ts | 63 + shared/src/i18n/nl/files.ts | 63 + shared/src/i18n/nl/index.ts | 86 + shared/src/i18n/nl/inspector.ts | 22 + shared/src/i18n/nl/journey.ts | 240 ++ shared/src/i18n/nl/login.ts | 98 + shared/src/i18n/nl/map.ts | 8 + shared/src/i18n/nl/members.ts | 24 + shared/src/i18n/nl/memories.ts | 82 + shared/src/i18n/nl/nav.ts | 20 + shared/src/i18n/nl/notif.ts | 44 + shared/src/i18n/nl/notifications.ts | 37 + shared/src/i18n/nl/oauth.ts | 99 + shared/src/i18n/nl/packing.ts | 186 ++ shared/src/i18n/nl/pdf.ts | 10 + shared/src/i18n/nl/perm.ts | 62 + shared/src/i18n/nl/photos.ts | 25 + shared/src/i18n/nl/places.ts | 93 + shared/src/i18n/nl/planner.ts | 68 + shared/src/i18n/nl/register.ts | 26 + shared/src/i18n/nl/reservations.ts | 119 + shared/src/i18n/nl/settings.ts | 298 ++ shared/src/i18n/nl/share.ts | 16 + shared/src/i18n/nl/shared.ts | 21 + shared/src/i18n/nl/stats.ts | 13 + shared/src/i18n/nl/system_notice.ts | 61 + shared/src/i18n/nl/todo.ts | 40 + shared/src/i18n/nl/transport.ts | 10 + shared/src/i18n/nl/trip.ts | 32 + shared/src/i18n/nl/trips.ts | 17 + shared/src/i18n/nl/undo.ts | 21 + shared/src/i18n/nl/vacay.ts | 105 + shared/src/i18n/pl/admin.ts | 366 +++ shared/src/i18n/pl/airport.ts | 6 + shared/src/i18n/pl/atlas.ts | 59 + shared/src/i18n/pl/backup.ts | 81 + shared/src/i18n/pl/budget.ts | 42 + shared/src/i18n/pl/categories.ts | 26 + shared/src/i18n/pl/collab.ts | 75 + shared/src/i18n/pl/common.ts | 54 + shared/src/i18n/pl/dashboard.ts | 107 + shared/src/i18n/pl/day.ts | 26 + shared/src/i18n/pl/dayplan.ts | 46 + shared/src/i18n/pl/externalNotifications.ts | 64 + shared/src/i18n/pl/files.ts | 62 + shared/src/i18n/pl/index.ts | 86 + shared/src/i18n/pl/inspector.ts | 22 + shared/src/i18n/pl/journey.ts | 239 ++ shared/src/i18n/pl/login.ts | 98 + shared/src/i18n/pl/map.ts | 8 + shared/src/i18n/pl/members.ts | 24 + shared/src/i18n/pl/memories.ts | 81 + shared/src/i18n/pl/nav.ts | 20 + shared/src/i18n/pl/notif.ts | 43 + shared/src/i18n/pl/notifications.ts | 36 + shared/src/i18n/pl/oauth.ts | 99 + shared/src/i18n/pl/packing.ts | 186 ++ shared/src/i18n/pl/pdf.ts | 10 + shared/src/i18n/pl/perm.ts | 51 + shared/src/i18n/pl/photos.ts | 25 + shared/src/i18n/pl/places.ts | 92 + shared/src/i18n/pl/planner.ts | 69 + shared/src/i18n/pl/register.ts | 26 + shared/src/i18n/pl/reservations.ts | 119 + shared/src/i18n/pl/settings.ts | 300 ++ shared/src/i18n/pl/share.ts | 16 + shared/src/i18n/pl/shared.ts | 21 + shared/src/i18n/pl/stats.ts | 13 + shared/src/i18n/pl/system_notice.ts | 60 + shared/src/i18n/pl/todo.ts | 40 + shared/src/i18n/pl/transport.ts | 10 + shared/src/i18n/pl/trip.ts | 31 + shared/src/i18n/pl/trips.ts | 16 + shared/src/i18n/pl/undo.ts | 21 + shared/src/i18n/pl/vacay.ts | 106 + shared/src/i18n/ru/admin.ts | 370 +++ shared/src/i18n/ru/airport.ts | 6 + shared/src/i18n/ru/atlas.ts | 59 + shared/src/i18n/ru/backup.ts | 78 + shared/src/i18n/ru/budget.ts | 44 + shared/src/i18n/ru/categories.ts | 26 + shared/src/i18n/ru/collab.ts | 75 + shared/src/i18n/ru/common.ts | 54 + shared/src/i18n/ru/dashboard.ts | 107 + shared/src/i18n/ru/day.ts | 26 + shared/src/i18n/ru/dayplan.ts | 46 + shared/src/i18n/ru/externalNotifications.ts | 63 + shared/src/i18n/ru/files.ts | 63 + shared/src/i18n/ru/index.ts | 86 + shared/src/i18n/ru/inspector.ts | 22 + shared/src/i18n/ru/journey.ts | 240 ++ shared/src/i18n/ru/login.ts | 97 + shared/src/i18n/ru/map.ts | 8 + shared/src/i18n/ru/members.ts | 24 + shared/src/i18n/ru/memories.ts | 80 + shared/src/i18n/ru/nav.ts | 20 + shared/src/i18n/ru/notif.ts | 41 + shared/src/i18n/ru/notifications.ts | 37 + shared/src/i18n/ru/oauth.ts | 99 + shared/src/i18n/ru/packing.ts | 186 ++ shared/src/i18n/ru/pdf.ts | 10 + shared/src/i18n/ru/perm.ts | 63 + shared/src/i18n/ru/photos.ts | 25 + shared/src/i18n/ru/places.ts | 92 + shared/src/i18n/ru/planner.ts | 68 + shared/src/i18n/ru/register.ts | 25 + shared/src/i18n/ru/reservations.ts | 119 + shared/src/i18n/ru/settings.ts | 299 ++ shared/src/i18n/ru/share.ts | 16 + shared/src/i18n/ru/shared.ts | 21 + shared/src/i18n/ru/stats.ts | 13 + shared/src/i18n/ru/system_notice.ts | 59 + shared/src/i18n/ru/todo.ts | 40 + shared/src/i18n/ru/transport.ts | 10 + shared/src/i18n/ru/trip.ts | 31 + shared/src/i18n/ru/trips.ts | 17 + shared/src/i18n/ru/undo.ts | 21 + shared/src/i18n/ru/vacay.ts | 107 + shared/src/i18n/tr/admin.ts | 358 +++ shared/src/i18n/tr/airport.ts | 6 + shared/src/i18n/tr/atlas.ts | 58 + shared/src/i18n/tr/backup.ts | 77 + shared/src/i18n/tr/budget.ts | 43 + shared/src/i18n/tr/categories.ts | 26 + shared/src/i18n/tr/collab.ts | 74 + shared/src/i18n/tr/common.ts | 55 + shared/src/i18n/tr/dashboard.ts | 121 + shared/src/i18n/tr/day.ts | 26 + shared/src/i18n/tr/dayplan.ts | 48 + shared/src/i18n/tr/externalNotifications.ts | 63 + shared/src/i18n/tr/files.ts | 62 + shared/src/i18n/tr/index.ts | 86 + shared/src/i18n/tr/inspector.ts | 22 + shared/src/i18n/tr/journey.ts | 244 ++ shared/src/i18n/tr/login.ts | 95 + shared/src/i18n/tr/map.ts | 8 + shared/src/i18n/tr/members.ts | 24 + shared/src/i18n/tr/memories.ts | 80 + shared/src/i18n/tr/nav.ts | 20 + shared/src/i18n/tr/notif.ts | 41 + shared/src/i18n/tr/notifications.ts | 38 + shared/src/i18n/tr/oauth.ts | 99 + shared/src/i18n/tr/packing.ts | 186 ++ shared/src/i18n/tr/pdf.ts | 10 + shared/src/i18n/tr/perm.ts | 57 + shared/src/i18n/tr/photos.ts | 25 + shared/src/i18n/tr/places.ts | 91 + shared/src/i18n/tr/planner.ts | 68 + shared/src/i18n/tr/register.ts | 26 + shared/src/i18n/tr/reservations.ts | 118 + shared/src/i18n/tr/settings.ts | 293 ++ shared/src/i18n/tr/share.ts | 16 + shared/src/i18n/tr/shared.ts | 21 + shared/src/i18n/tr/stats.ts | 13 + shared/src/i18n/tr/system_notice.ts | 60 + shared/src/i18n/tr/todo.ts | 40 + shared/src/i18n/tr/transport.ts | 10 + shared/src/i18n/tr/trip.ts | 31 + shared/src/i18n/tr/trips.ts | 17 + shared/src/i18n/tr/undo.ts | 21 + shared/src/i18n/tr/vacay.ts | 103 + shared/src/i18n/types.ts | 2 + shared/src/i18n/uk/admin.ts | 374 +++ shared/src/i18n/uk/airport.ts | 6 + shared/src/i18n/uk/atlas.ts | 59 + shared/src/i18n/uk/backup.ts | 76 + shared/src/i18n/uk/budget.ts | 43 + shared/src/i18n/uk/categories.ts | 26 + shared/src/i18n/uk/collab.ts | 74 + shared/src/i18n/uk/common.ts | 54 + shared/src/i18n/uk/dashboard.ts | 121 + shared/src/i18n/uk/day.ts | 26 + shared/src/i18n/uk/dayplan.ts | 48 + shared/src/i18n/uk/externalNotifications.ts | 63 + shared/src/i18n/uk/files.ts | 62 + shared/src/i18n/uk/index.ts | 86 + shared/src/i18n/uk/inspector.ts | 22 + shared/src/i18n/uk/journey.ts | 245 ++ shared/src/i18n/uk/login.ts | 98 + shared/src/i18n/uk/map.ts | 8 + shared/src/i18n/uk/members.ts | 24 + shared/src/i18n/uk/memories.ts | 81 + shared/src/i18n/uk/nav.ts | 20 + shared/src/i18n/uk/notif.ts | 42 + shared/src/i18n/uk/notifications.ts | 37 + shared/src/i18n/uk/oauth.ts | 99 + shared/src/i18n/uk/packing.ts | 186 ++ shared/src/i18n/uk/pdf.ts | 10 + shared/src/i18n/uk/perm.ts | 63 + shared/src/i18n/uk/photos.ts | 25 + shared/src/i18n/uk/places.ts | 92 + shared/src/i18n/uk/planner.ts | 68 + shared/src/i18n/uk/register.ts | 25 + shared/src/i18n/uk/reservations.ts | 119 + shared/src/i18n/uk/settings.ts | 297 ++ shared/src/i18n/uk/share.ts | 16 + shared/src/i18n/uk/shared.ts | 21 + shared/src/i18n/uk/stats.ts | 13 + shared/src/i18n/uk/system_notice.ts | 59 + shared/src/i18n/uk/todo.ts | 40 + shared/src/i18n/uk/transport.ts | 10 + shared/src/i18n/uk/trip.ts | 31 + shared/src/i18n/uk/trips.ts | 17 + shared/src/i18n/uk/undo.ts | 21 + shared/src/i18n/uk/vacay.ts | 107 + shared/src/i18n/zh-TW/admin.ts | 331 +++ shared/src/i18n/zh-TW/airport.ts | 6 + shared/src/i18n/zh-TW/atlas.ts | 58 + shared/src/i18n/zh-TW/backup.ts | 74 + shared/src/i18n/zh-TW/budget.ts | 42 + shared/src/i18n/zh-TW/categories.ts | 25 + shared/src/i18n/zh-TW/collab.ts | 73 + shared/src/i18n/zh-TW/common.ts | 54 + shared/src/i18n/zh-TW/dashboard.ts | 105 + shared/src/i18n/zh-TW/day.ts | 25 + shared/src/i18n/zh-TW/dayplan.ts | 41 + .../src/i18n/zh-TW/externalNotifications.ts | 62 + shared/src/i18n/zh-TW/files.ts | 60 + shared/src/i18n/zh-TW/index.ts | 86 + shared/src/i18n/zh-TW/inspector.ts | 22 + shared/src/i18n/zh-TW/journey.ts | 229 ++ shared/src/i18n/zh-TW/login.ts | 88 + shared/src/i18n/zh-TW/map.ts | 8 + shared/src/i18n/zh-TW/members.ts | 24 + shared/src/i18n/zh-TW/memories.ts | 77 + shared/src/i18n/zh-TW/nav.ts | 20 + shared/src/i18n/zh-TW/notif.ts | 41 + shared/src/i18n/zh-TW/notifications.ts | 36 + shared/src/i18n/zh-TW/oauth.ts | 77 + shared/src/i18n/zh-TW/packing.ts | 183 ++ shared/src/i18n/zh-TW/pdf.ts | 10 + shared/src/i18n/zh-TW/perm.ts | 51 + shared/src/i18n/zh-TW/photos.ts | 25 + shared/src/i18n/zh-TW/places.ts | 87 + shared/src/i18n/zh-TW/planner.ts | 67 + shared/src/i18n/zh-TW/register.ts | 25 + shared/src/i18n/zh-TW/reservations.ts | 115 + shared/src/i18n/zh-TW/settings.ts | 286 ++ shared/src/i18n/zh-TW/share.ts | 16 + shared/src/i18n/zh-TW/shared.ts | 21 + shared/src/i18n/zh-TW/stats.ts | 13 + shared/src/i18n/zh-TW/system_notice.ts | 50 + shared/src/i18n/zh-TW/todo.ts | 40 + shared/src/i18n/zh-TW/transport.ts | 10 + shared/src/i18n/zh-TW/trip.ts | 31 + shared/src/i18n/zh-TW/trips.ts | 17 + shared/src/i18n/zh-TW/undo.ts | 21 + shared/src/i18n/zh-TW/vacay.ts | 93 + shared/src/i18n/zh/admin.ts | 330 +++ shared/src/i18n/zh/airport.ts | 6 + shared/src/i18n/zh/atlas.ts | 58 + shared/src/i18n/zh/backup.ts | 74 + shared/src/i18n/zh/budget.ts | 42 + shared/src/i18n/zh/categories.ts | 25 + shared/src/i18n/zh/collab.ts | 73 + shared/src/i18n/zh/common.ts | 54 + shared/src/i18n/zh/dashboard.ts | 105 + shared/src/i18n/zh/day.ts | 25 + shared/src/i18n/zh/dayplan.ts | 41 + shared/src/i18n/zh/externalNotifications.ts | 62 + shared/src/i18n/zh/files.ts | 60 + shared/src/i18n/zh/index.ts | 86 + shared/src/i18n/zh/inspector.ts | 22 + shared/src/i18n/zh/journey.ts | 229 ++ shared/src/i18n/zh/login.ts | 87 + shared/src/i18n/zh/map.ts | 8 + shared/src/i18n/zh/members.ts | 24 + shared/src/i18n/zh/memories.ts | 77 + shared/src/i18n/zh/nav.ts | 20 + shared/src/i18n/zh/notif.ts | 41 + shared/src/i18n/zh/notifications.ts | 36 + shared/src/i18n/zh/oauth.ts | 77 + shared/src/i18n/zh/packing.ts | 183 ++ shared/src/i18n/zh/pdf.ts | 10 + shared/src/i18n/zh/perm.ts | 51 + shared/src/i18n/zh/photos.ts | 25 + shared/src/i18n/zh/places.ts | 87 + shared/src/i18n/zh/planner.ts | 67 + shared/src/i18n/zh/register.ts | 25 + shared/src/i18n/zh/reservations.ts | 115 + shared/src/i18n/zh/settings.ts | 285 ++ shared/src/i18n/zh/share.ts | 16 + shared/src/i18n/zh/shared.ts | 21 + shared/src/i18n/zh/stats.ts | 13 + shared/src/i18n/zh/system_notice.ts | 50 + shared/src/i18n/zh/todo.ts | 40 + shared/src/i18n/zh/transport.ts | 10 + shared/src/i18n/zh/trip.ts | 31 + shared/src/i18n/zh/trips.ts | 17 + shared/src/i18n/zh/undo.ts | 21 + shared/src/i18n/zh/vacay.ts | 93 + shared/src/index.ts | 3 + shared/src/weather/weather.schema.spec.ts | 60 +- shared/tsup.config.ts | 3 +- 860 files changed, 56891 insertions(+), 46377 deletions(-) delete mode 100644 .issue-scratch/759.png delete mode 100644 .issue-scratch/764-1.png delete mode 100644 .issue-scratch/764-2.png delete mode 100644 .issue-scratch/release-draft-new.md delete mode 100644 .issue-scratch/release-draft.md delete mode 100644 client/src/i18n/translations/ar.ts delete mode 100644 client/src/i18n/translations/br.ts delete mode 100644 client/src/i18n/translations/cs.ts delete mode 100644 client/src/i18n/translations/de.ts delete mode 100644 client/src/i18n/translations/en.ts delete mode 100644 client/src/i18n/translations/es.ts delete mode 100644 client/src/i18n/translations/fr.ts delete mode 100644 client/src/i18n/translations/hu.ts delete mode 100644 client/src/i18n/translations/id.ts delete mode 100644 client/src/i18n/translations/it.ts delete mode 100644 client/src/i18n/translations/ja.ts delete mode 100644 client/src/i18n/translations/ko.ts delete mode 100644 client/src/i18n/translations/nl.ts delete mode 100644 client/src/i18n/translations/pl.ts delete mode 100644 client/src/i18n/translations/ru.ts delete mode 100644 client/src/i18n/translations/tr.ts delete mode 100644 client/src/i18n/translations/uk.ts delete mode 100644 client/src/i18n/translations/zh.ts delete mode 100644 client/src/i18n/translations/zhTw.ts create mode 100644 scripts/migrate-i18n.mts create mode 100644 shared/eslint.config.mjs create mode 100644 shared/src/i18n/ar/admin.ts create mode 100644 shared/src/i18n/ar/airport.ts create mode 100644 shared/src/i18n/ar/atlas.ts create mode 100644 shared/src/i18n/ar/backup.ts create mode 100644 shared/src/i18n/ar/budget.ts create mode 100644 shared/src/i18n/ar/categories.ts create mode 100644 shared/src/i18n/ar/collab.ts create mode 100644 shared/src/i18n/ar/common.ts create mode 100644 shared/src/i18n/ar/dashboard.ts create mode 100644 shared/src/i18n/ar/day.ts create mode 100644 shared/src/i18n/ar/dayplan.ts create mode 100644 shared/src/i18n/ar/externalNotifications.ts create mode 100644 shared/src/i18n/ar/files.ts create mode 100644 shared/src/i18n/ar/index.ts create mode 100644 shared/src/i18n/ar/inspector.ts create mode 100644 shared/src/i18n/ar/journey.ts create mode 100644 shared/src/i18n/ar/login.ts create mode 100644 shared/src/i18n/ar/map.ts create mode 100644 shared/src/i18n/ar/members.ts create mode 100644 shared/src/i18n/ar/memories.ts create mode 100644 shared/src/i18n/ar/nav.ts create mode 100644 shared/src/i18n/ar/notif.ts create mode 100644 shared/src/i18n/ar/notifications.ts create mode 100644 shared/src/i18n/ar/oauth.ts create mode 100644 shared/src/i18n/ar/packing.ts create mode 100644 shared/src/i18n/ar/pdf.ts create mode 100644 shared/src/i18n/ar/perm.ts create mode 100644 shared/src/i18n/ar/photos.ts create mode 100644 shared/src/i18n/ar/places.ts create mode 100644 shared/src/i18n/ar/planner.ts create mode 100644 shared/src/i18n/ar/register.ts create mode 100644 shared/src/i18n/ar/reservations.ts create mode 100644 shared/src/i18n/ar/settings.ts create mode 100644 shared/src/i18n/ar/share.ts create mode 100644 shared/src/i18n/ar/shared.ts create mode 100644 shared/src/i18n/ar/stats.ts create mode 100644 shared/src/i18n/ar/system_notice.ts create mode 100644 shared/src/i18n/ar/todo.ts create mode 100644 shared/src/i18n/ar/transport.ts create mode 100644 shared/src/i18n/ar/trip.ts create mode 100644 shared/src/i18n/ar/trips.ts create mode 100644 shared/src/i18n/ar/undo.ts create mode 100644 shared/src/i18n/ar/vacay.ts create mode 100644 shared/src/i18n/br/admin.ts create mode 100644 shared/src/i18n/br/airport.ts create mode 100644 shared/src/i18n/br/atlas.ts create mode 100644 shared/src/i18n/br/backup.ts create mode 100644 shared/src/i18n/br/budget.ts create mode 100644 shared/src/i18n/br/categories.ts create mode 100644 shared/src/i18n/br/collab.ts create mode 100644 shared/src/i18n/br/common.ts create mode 100644 shared/src/i18n/br/dashboard.ts create mode 100644 shared/src/i18n/br/day.ts create mode 100644 shared/src/i18n/br/dayplan.ts create mode 100644 shared/src/i18n/br/externalNotifications.ts create mode 100644 shared/src/i18n/br/files.ts create mode 100644 shared/src/i18n/br/index.ts create mode 100644 shared/src/i18n/br/inspector.ts create mode 100644 shared/src/i18n/br/journey.ts create mode 100644 shared/src/i18n/br/login.ts create mode 100644 shared/src/i18n/br/map.ts create mode 100644 shared/src/i18n/br/members.ts create mode 100644 shared/src/i18n/br/memories.ts create mode 100644 shared/src/i18n/br/nav.ts create mode 100644 shared/src/i18n/br/notif.ts create mode 100644 shared/src/i18n/br/notifications.ts create mode 100644 shared/src/i18n/br/oauth.ts create mode 100644 shared/src/i18n/br/packing.ts create mode 100644 shared/src/i18n/br/pdf.ts create mode 100644 shared/src/i18n/br/perm.ts create mode 100644 shared/src/i18n/br/photos.ts create mode 100644 shared/src/i18n/br/places.ts create mode 100644 shared/src/i18n/br/planner.ts create mode 100644 shared/src/i18n/br/register.ts create mode 100644 shared/src/i18n/br/reservations.ts create mode 100644 shared/src/i18n/br/settings.ts create mode 100644 shared/src/i18n/br/share.ts create mode 100644 shared/src/i18n/br/shared.ts create mode 100644 shared/src/i18n/br/stats.ts create mode 100644 shared/src/i18n/br/system_notice.ts create mode 100644 shared/src/i18n/br/todo.ts create mode 100644 shared/src/i18n/br/transport.ts create mode 100644 shared/src/i18n/br/trip.ts create mode 100644 shared/src/i18n/br/trips.ts create mode 100644 shared/src/i18n/br/undo.ts create mode 100644 shared/src/i18n/br/vacay.ts create mode 100644 shared/src/i18n/cs/admin.ts create mode 100644 shared/src/i18n/cs/airport.ts create mode 100644 shared/src/i18n/cs/atlas.ts create mode 100644 shared/src/i18n/cs/backup.ts create mode 100644 shared/src/i18n/cs/budget.ts create mode 100644 shared/src/i18n/cs/categories.ts create mode 100644 shared/src/i18n/cs/collab.ts create mode 100644 shared/src/i18n/cs/common.ts create mode 100644 shared/src/i18n/cs/dashboard.ts create mode 100644 shared/src/i18n/cs/day.ts create mode 100644 shared/src/i18n/cs/dayplan.ts create mode 100644 shared/src/i18n/cs/externalNotifications.ts create mode 100644 shared/src/i18n/cs/files.ts create mode 100644 shared/src/i18n/cs/index.ts create mode 100644 shared/src/i18n/cs/inspector.ts create mode 100644 shared/src/i18n/cs/journey.ts create mode 100644 shared/src/i18n/cs/login.ts create mode 100644 shared/src/i18n/cs/map.ts create mode 100644 shared/src/i18n/cs/members.ts create mode 100644 shared/src/i18n/cs/memories.ts create mode 100644 shared/src/i18n/cs/nav.ts create mode 100644 shared/src/i18n/cs/notif.ts create mode 100644 shared/src/i18n/cs/notifications.ts create mode 100644 shared/src/i18n/cs/oauth.ts create mode 100644 shared/src/i18n/cs/packing.ts create mode 100644 shared/src/i18n/cs/pdf.ts create mode 100644 shared/src/i18n/cs/perm.ts create mode 100644 shared/src/i18n/cs/photos.ts create mode 100644 shared/src/i18n/cs/places.ts create mode 100644 shared/src/i18n/cs/planner.ts create mode 100644 shared/src/i18n/cs/register.ts create mode 100644 shared/src/i18n/cs/reservations.ts create mode 100644 shared/src/i18n/cs/settings.ts create mode 100644 shared/src/i18n/cs/share.ts create mode 100644 shared/src/i18n/cs/shared.ts create mode 100644 shared/src/i18n/cs/stats.ts create mode 100644 shared/src/i18n/cs/system_notice.ts create mode 100644 shared/src/i18n/cs/todo.ts create mode 100644 shared/src/i18n/cs/transport.ts create mode 100644 shared/src/i18n/cs/trip.ts create mode 100644 shared/src/i18n/cs/trips.ts create mode 100644 shared/src/i18n/cs/undo.ts create mode 100644 shared/src/i18n/cs/vacay.ts create mode 100644 shared/src/i18n/de/admin.ts create mode 100644 shared/src/i18n/de/airport.ts create mode 100644 shared/src/i18n/de/atlas.ts create mode 100644 shared/src/i18n/de/backup.ts create mode 100644 shared/src/i18n/de/budget.ts create mode 100644 shared/src/i18n/de/categories.ts create mode 100644 shared/src/i18n/de/collab.ts create mode 100644 shared/src/i18n/de/common.ts create mode 100644 shared/src/i18n/de/dashboard.ts create mode 100644 shared/src/i18n/de/day.ts create mode 100644 shared/src/i18n/de/dayplan.ts create mode 100644 shared/src/i18n/de/externalNotifications.ts create mode 100644 shared/src/i18n/de/files.ts create mode 100644 shared/src/i18n/de/index.ts create mode 100644 shared/src/i18n/de/inspector.ts create mode 100644 shared/src/i18n/de/journey.ts create mode 100644 shared/src/i18n/de/login.ts create mode 100644 shared/src/i18n/de/map.ts create mode 100644 shared/src/i18n/de/members.ts create mode 100644 shared/src/i18n/de/memories.ts create mode 100644 shared/src/i18n/de/nav.ts create mode 100644 shared/src/i18n/de/notif.ts create mode 100644 shared/src/i18n/de/notifications.ts create mode 100644 shared/src/i18n/de/oauth.ts create mode 100644 shared/src/i18n/de/packing.ts create mode 100644 shared/src/i18n/de/pdf.ts create mode 100644 shared/src/i18n/de/perm.ts create mode 100644 shared/src/i18n/de/photos.ts create mode 100644 shared/src/i18n/de/places.ts create mode 100644 shared/src/i18n/de/planner.ts create mode 100644 shared/src/i18n/de/register.ts create mode 100644 shared/src/i18n/de/reservations.ts create mode 100644 shared/src/i18n/de/settings.ts create mode 100644 shared/src/i18n/de/share.ts create mode 100644 shared/src/i18n/de/shared.ts create mode 100644 shared/src/i18n/de/stats.ts create mode 100644 shared/src/i18n/de/system_notice.ts create mode 100644 shared/src/i18n/de/todo.ts create mode 100644 shared/src/i18n/de/transport.ts create mode 100644 shared/src/i18n/de/trip.ts create mode 100644 shared/src/i18n/de/trips.ts create mode 100644 shared/src/i18n/de/undo.ts create mode 100644 shared/src/i18n/de/vacay.ts create mode 100644 shared/src/i18n/en/admin.ts create mode 100644 shared/src/i18n/en/airport.ts create mode 100644 shared/src/i18n/en/atlas.ts create mode 100644 shared/src/i18n/en/backup.ts create mode 100644 shared/src/i18n/en/budget.ts create mode 100644 shared/src/i18n/en/categories.ts create mode 100644 shared/src/i18n/en/collab.ts create mode 100644 shared/src/i18n/en/common.ts create mode 100644 shared/src/i18n/en/dashboard.ts create mode 100644 shared/src/i18n/en/day.ts create mode 100644 shared/src/i18n/en/dayplan.ts create mode 100644 shared/src/i18n/en/externalNotifications.ts create mode 100644 shared/src/i18n/en/files.ts create mode 100644 shared/src/i18n/en/index.ts create mode 100644 shared/src/i18n/en/inspector.ts create mode 100644 shared/src/i18n/en/journey.ts create mode 100644 shared/src/i18n/en/login.ts create mode 100644 shared/src/i18n/en/map.ts create mode 100644 shared/src/i18n/en/members.ts create mode 100644 shared/src/i18n/en/memories.ts create mode 100644 shared/src/i18n/en/nav.ts create mode 100644 shared/src/i18n/en/notif.ts create mode 100644 shared/src/i18n/en/notifications.ts create mode 100644 shared/src/i18n/en/oauth.ts create mode 100644 shared/src/i18n/en/packing.ts create mode 100644 shared/src/i18n/en/pdf.ts create mode 100644 shared/src/i18n/en/perm.ts create mode 100644 shared/src/i18n/en/photos.ts create mode 100644 shared/src/i18n/en/places.ts create mode 100644 shared/src/i18n/en/planner.ts create mode 100644 shared/src/i18n/en/register.ts create mode 100644 shared/src/i18n/en/reservations.ts create mode 100644 shared/src/i18n/en/settings.ts create mode 100644 shared/src/i18n/en/share.ts create mode 100644 shared/src/i18n/en/shared.ts create mode 100644 shared/src/i18n/en/stats.ts create mode 100644 shared/src/i18n/en/system_notice.ts create mode 100644 shared/src/i18n/en/todo.ts create mode 100644 shared/src/i18n/en/transport.ts create mode 100644 shared/src/i18n/en/trip.ts create mode 100644 shared/src/i18n/en/trips.ts create mode 100644 shared/src/i18n/en/undo.ts create mode 100644 shared/src/i18n/en/vacay.ts create mode 100644 shared/src/i18n/es/admin.ts create mode 100644 shared/src/i18n/es/airport.ts create mode 100644 shared/src/i18n/es/atlas.ts create mode 100644 shared/src/i18n/es/backup.ts create mode 100644 shared/src/i18n/es/budget.ts create mode 100644 shared/src/i18n/es/categories.ts create mode 100644 shared/src/i18n/es/collab.ts create mode 100644 shared/src/i18n/es/common.ts create mode 100644 shared/src/i18n/es/dashboard.ts create mode 100644 shared/src/i18n/es/day.ts create mode 100644 shared/src/i18n/es/dayplan.ts create mode 100644 shared/src/i18n/es/externalNotifications.ts create mode 100644 shared/src/i18n/es/files.ts create mode 100644 shared/src/i18n/es/index.ts create mode 100644 shared/src/i18n/es/inspector.ts create mode 100644 shared/src/i18n/es/journey.ts create mode 100644 shared/src/i18n/es/login.ts create mode 100644 shared/src/i18n/es/map.ts create mode 100644 shared/src/i18n/es/members.ts create mode 100644 shared/src/i18n/es/memories.ts create mode 100644 shared/src/i18n/es/nav.ts create mode 100644 shared/src/i18n/es/notif.ts create mode 100644 shared/src/i18n/es/notifications.ts create mode 100644 shared/src/i18n/es/oauth.ts create mode 100644 shared/src/i18n/es/packing.ts create mode 100644 shared/src/i18n/es/pdf.ts create mode 100644 shared/src/i18n/es/perm.ts create mode 100644 shared/src/i18n/es/photos.ts create mode 100644 shared/src/i18n/es/places.ts create mode 100644 shared/src/i18n/es/planner.ts create mode 100644 shared/src/i18n/es/register.ts create mode 100644 shared/src/i18n/es/reservations.ts create mode 100644 shared/src/i18n/es/settings.ts create mode 100644 shared/src/i18n/es/share.ts create mode 100644 shared/src/i18n/es/shared.ts create mode 100644 shared/src/i18n/es/stats.ts create mode 100644 shared/src/i18n/es/system_notice.ts create mode 100644 shared/src/i18n/es/todo.ts create mode 100644 shared/src/i18n/es/transport.ts create mode 100644 shared/src/i18n/es/trip.ts create mode 100644 shared/src/i18n/es/trips.ts create mode 100644 shared/src/i18n/es/undo.ts create mode 100644 shared/src/i18n/es/vacay.ts create mode 100644 shared/src/i18n/externalNotifications/index.ts create mode 100644 shared/src/i18n/externalNotifications/types.ts create mode 100644 shared/src/i18n/fr/admin.ts create mode 100644 shared/src/i18n/fr/airport.ts create mode 100644 shared/src/i18n/fr/atlas.ts create mode 100644 shared/src/i18n/fr/backup.ts create mode 100644 shared/src/i18n/fr/budget.ts create mode 100644 shared/src/i18n/fr/categories.ts create mode 100644 shared/src/i18n/fr/collab.ts create mode 100644 shared/src/i18n/fr/common.ts create mode 100644 shared/src/i18n/fr/dashboard.ts create mode 100644 shared/src/i18n/fr/day.ts create mode 100644 shared/src/i18n/fr/dayplan.ts create mode 100644 shared/src/i18n/fr/externalNotifications.ts create mode 100644 shared/src/i18n/fr/files.ts create mode 100644 shared/src/i18n/fr/index.ts create mode 100644 shared/src/i18n/fr/inspector.ts create mode 100644 shared/src/i18n/fr/journey.ts create mode 100644 shared/src/i18n/fr/login.ts create mode 100644 shared/src/i18n/fr/map.ts create mode 100644 shared/src/i18n/fr/members.ts create mode 100644 shared/src/i18n/fr/memories.ts create mode 100644 shared/src/i18n/fr/nav.ts create mode 100644 shared/src/i18n/fr/notif.ts create mode 100644 shared/src/i18n/fr/notifications.ts create mode 100644 shared/src/i18n/fr/oauth.ts create mode 100644 shared/src/i18n/fr/packing.ts create mode 100644 shared/src/i18n/fr/pdf.ts create mode 100644 shared/src/i18n/fr/perm.ts create mode 100644 shared/src/i18n/fr/photos.ts create mode 100644 shared/src/i18n/fr/places.ts create mode 100644 shared/src/i18n/fr/planner.ts create mode 100644 shared/src/i18n/fr/register.ts create mode 100644 shared/src/i18n/fr/reservations.ts create mode 100644 shared/src/i18n/fr/settings.ts create mode 100644 shared/src/i18n/fr/share.ts create mode 100644 shared/src/i18n/fr/shared.ts create mode 100644 shared/src/i18n/fr/stats.ts create mode 100644 shared/src/i18n/fr/system_notice.ts create mode 100644 shared/src/i18n/fr/todo.ts create mode 100644 shared/src/i18n/fr/transport.ts create mode 100644 shared/src/i18n/fr/trip.ts create mode 100644 shared/src/i18n/fr/trips.ts create mode 100644 shared/src/i18n/fr/undo.ts create mode 100644 shared/src/i18n/fr/vacay.ts create mode 100644 shared/src/i18n/hu/admin.ts create mode 100644 shared/src/i18n/hu/airport.ts create mode 100644 shared/src/i18n/hu/atlas.ts create mode 100644 shared/src/i18n/hu/backup.ts create mode 100644 shared/src/i18n/hu/budget.ts create mode 100644 shared/src/i18n/hu/categories.ts create mode 100644 shared/src/i18n/hu/collab.ts create mode 100644 shared/src/i18n/hu/common.ts create mode 100644 shared/src/i18n/hu/dashboard.ts create mode 100644 shared/src/i18n/hu/day.ts create mode 100644 shared/src/i18n/hu/dayplan.ts create mode 100644 shared/src/i18n/hu/externalNotifications.ts create mode 100644 shared/src/i18n/hu/files.ts create mode 100644 shared/src/i18n/hu/index.ts create mode 100644 shared/src/i18n/hu/inspector.ts create mode 100644 shared/src/i18n/hu/journey.ts create mode 100644 shared/src/i18n/hu/login.ts create mode 100644 shared/src/i18n/hu/map.ts create mode 100644 shared/src/i18n/hu/members.ts create mode 100644 shared/src/i18n/hu/memories.ts create mode 100644 shared/src/i18n/hu/nav.ts create mode 100644 shared/src/i18n/hu/notif.ts create mode 100644 shared/src/i18n/hu/notifications.ts create mode 100644 shared/src/i18n/hu/oauth.ts create mode 100644 shared/src/i18n/hu/packing.ts create mode 100644 shared/src/i18n/hu/pdf.ts create mode 100644 shared/src/i18n/hu/perm.ts create mode 100644 shared/src/i18n/hu/photos.ts create mode 100644 shared/src/i18n/hu/places.ts create mode 100644 shared/src/i18n/hu/planner.ts create mode 100644 shared/src/i18n/hu/register.ts create mode 100644 shared/src/i18n/hu/reservations.ts create mode 100644 shared/src/i18n/hu/settings.ts create mode 100644 shared/src/i18n/hu/share.ts create mode 100644 shared/src/i18n/hu/shared.ts create mode 100644 shared/src/i18n/hu/stats.ts create mode 100644 shared/src/i18n/hu/system_notice.ts create mode 100644 shared/src/i18n/hu/todo.ts create mode 100644 shared/src/i18n/hu/transport.ts create mode 100644 shared/src/i18n/hu/trip.ts create mode 100644 shared/src/i18n/hu/trips.ts create mode 100644 shared/src/i18n/hu/undo.ts create mode 100644 shared/src/i18n/hu/vacay.ts create mode 100644 shared/src/i18n/id/admin.ts create mode 100644 shared/src/i18n/id/airport.ts create mode 100644 shared/src/i18n/id/atlas.ts create mode 100644 shared/src/i18n/id/backup.ts create mode 100644 shared/src/i18n/id/budget.ts create mode 100644 shared/src/i18n/id/categories.ts create mode 100644 shared/src/i18n/id/collab.ts create mode 100644 shared/src/i18n/id/common.ts create mode 100644 shared/src/i18n/id/dashboard.ts create mode 100644 shared/src/i18n/id/day.ts create mode 100644 shared/src/i18n/id/dayplan.ts create mode 100644 shared/src/i18n/id/externalNotifications.ts create mode 100644 shared/src/i18n/id/files.ts create mode 100644 shared/src/i18n/id/index.ts create mode 100644 shared/src/i18n/id/inspector.ts create mode 100644 shared/src/i18n/id/journey.ts create mode 100644 shared/src/i18n/id/login.ts create mode 100644 shared/src/i18n/id/map.ts create mode 100644 shared/src/i18n/id/members.ts create mode 100644 shared/src/i18n/id/memories.ts create mode 100644 shared/src/i18n/id/nav.ts create mode 100644 shared/src/i18n/id/notif.ts create mode 100644 shared/src/i18n/id/notifications.ts create mode 100644 shared/src/i18n/id/oauth.ts create mode 100644 shared/src/i18n/id/packing.ts create mode 100644 shared/src/i18n/id/pdf.ts create mode 100644 shared/src/i18n/id/perm.ts create mode 100644 shared/src/i18n/id/photos.ts create mode 100644 shared/src/i18n/id/places.ts create mode 100644 shared/src/i18n/id/planner.ts create mode 100644 shared/src/i18n/id/register.ts create mode 100644 shared/src/i18n/id/reservations.ts create mode 100644 shared/src/i18n/id/settings.ts create mode 100644 shared/src/i18n/id/share.ts create mode 100644 shared/src/i18n/id/shared.ts create mode 100644 shared/src/i18n/id/stats.ts create mode 100644 shared/src/i18n/id/system_notice.ts create mode 100644 shared/src/i18n/id/todo.ts create mode 100644 shared/src/i18n/id/transport.ts create mode 100644 shared/src/i18n/id/trip.ts create mode 100644 shared/src/i18n/id/trips.ts create mode 100644 shared/src/i18n/id/undo.ts create mode 100644 shared/src/i18n/id/vacay.ts create mode 100644 shared/src/i18n/index.ts create mode 100644 shared/src/i18n/it/admin.ts create mode 100644 shared/src/i18n/it/airport.ts create mode 100644 shared/src/i18n/it/atlas.ts create mode 100644 shared/src/i18n/it/backup.ts create mode 100644 shared/src/i18n/it/budget.ts create mode 100644 shared/src/i18n/it/categories.ts create mode 100644 shared/src/i18n/it/collab.ts create mode 100644 shared/src/i18n/it/common.ts create mode 100644 shared/src/i18n/it/dashboard.ts create mode 100644 shared/src/i18n/it/day.ts create mode 100644 shared/src/i18n/it/dayplan.ts create mode 100644 shared/src/i18n/it/externalNotifications.ts create mode 100644 shared/src/i18n/it/files.ts create mode 100644 shared/src/i18n/it/index.ts create mode 100644 shared/src/i18n/it/inspector.ts create mode 100644 shared/src/i18n/it/journey.ts create mode 100644 shared/src/i18n/it/login.ts create mode 100644 shared/src/i18n/it/map.ts create mode 100644 shared/src/i18n/it/members.ts create mode 100644 shared/src/i18n/it/memories.ts create mode 100644 shared/src/i18n/it/nav.ts create mode 100644 shared/src/i18n/it/notif.ts create mode 100644 shared/src/i18n/it/notifications.ts create mode 100644 shared/src/i18n/it/oauth.ts create mode 100644 shared/src/i18n/it/packing.ts create mode 100644 shared/src/i18n/it/pdf.ts create mode 100644 shared/src/i18n/it/perm.ts create mode 100644 shared/src/i18n/it/photos.ts create mode 100644 shared/src/i18n/it/places.ts create mode 100644 shared/src/i18n/it/planner.ts create mode 100644 shared/src/i18n/it/register.ts create mode 100644 shared/src/i18n/it/reservations.ts create mode 100644 shared/src/i18n/it/settings.ts create mode 100644 shared/src/i18n/it/share.ts create mode 100644 shared/src/i18n/it/shared.ts create mode 100644 shared/src/i18n/it/stats.ts create mode 100644 shared/src/i18n/it/system_notice.ts create mode 100644 shared/src/i18n/it/todo.ts create mode 100644 shared/src/i18n/it/transport.ts create mode 100644 shared/src/i18n/it/trip.ts create mode 100644 shared/src/i18n/it/trips.ts create mode 100644 shared/src/i18n/it/undo.ts create mode 100644 shared/src/i18n/it/vacay.ts create mode 100644 shared/src/i18n/ja/admin.ts create mode 100644 shared/src/i18n/ja/airport.ts create mode 100644 shared/src/i18n/ja/atlas.ts create mode 100644 shared/src/i18n/ja/backup.ts create mode 100644 shared/src/i18n/ja/budget.ts create mode 100644 shared/src/i18n/ja/categories.ts create mode 100644 shared/src/i18n/ja/collab.ts create mode 100644 shared/src/i18n/ja/common.ts create mode 100644 shared/src/i18n/ja/dashboard.ts create mode 100644 shared/src/i18n/ja/day.ts create mode 100644 shared/src/i18n/ja/dayplan.ts create mode 100644 shared/src/i18n/ja/externalNotifications.ts create mode 100644 shared/src/i18n/ja/files.ts create mode 100644 shared/src/i18n/ja/index.ts create mode 100644 shared/src/i18n/ja/inspector.ts create mode 100644 shared/src/i18n/ja/journey.ts create mode 100644 shared/src/i18n/ja/login.ts create mode 100644 shared/src/i18n/ja/map.ts create mode 100644 shared/src/i18n/ja/members.ts create mode 100644 shared/src/i18n/ja/memories.ts create mode 100644 shared/src/i18n/ja/nav.ts create mode 100644 shared/src/i18n/ja/notif.ts create mode 100644 shared/src/i18n/ja/notifications.ts create mode 100644 shared/src/i18n/ja/oauth.ts create mode 100644 shared/src/i18n/ja/packing.ts create mode 100644 shared/src/i18n/ja/pdf.ts create mode 100644 shared/src/i18n/ja/perm.ts create mode 100644 shared/src/i18n/ja/photos.ts create mode 100644 shared/src/i18n/ja/places.ts create mode 100644 shared/src/i18n/ja/planner.ts create mode 100644 shared/src/i18n/ja/register.ts create mode 100644 shared/src/i18n/ja/reservations.ts create mode 100644 shared/src/i18n/ja/settings.ts create mode 100644 shared/src/i18n/ja/share.ts create mode 100644 shared/src/i18n/ja/shared.ts create mode 100644 shared/src/i18n/ja/stats.ts create mode 100644 shared/src/i18n/ja/system_notice.ts create mode 100644 shared/src/i18n/ja/todo.ts create mode 100644 shared/src/i18n/ja/transport.ts create mode 100644 shared/src/i18n/ja/trip.ts create mode 100644 shared/src/i18n/ja/trips.ts create mode 100644 shared/src/i18n/ja/undo.ts create mode 100644 shared/src/i18n/ja/vacay.ts create mode 100644 shared/src/i18n/ko/admin.ts create mode 100644 shared/src/i18n/ko/airport.ts create mode 100644 shared/src/i18n/ko/atlas.ts create mode 100644 shared/src/i18n/ko/backup.ts create mode 100644 shared/src/i18n/ko/budget.ts create mode 100644 shared/src/i18n/ko/categories.ts create mode 100644 shared/src/i18n/ko/collab.ts create mode 100644 shared/src/i18n/ko/common.ts create mode 100644 shared/src/i18n/ko/dashboard.ts create mode 100644 shared/src/i18n/ko/day.ts create mode 100644 shared/src/i18n/ko/dayplan.ts create mode 100644 shared/src/i18n/ko/externalNotifications.ts create mode 100644 shared/src/i18n/ko/files.ts create mode 100644 shared/src/i18n/ko/index.ts create mode 100644 shared/src/i18n/ko/inspector.ts create mode 100644 shared/src/i18n/ko/journey.ts create mode 100644 shared/src/i18n/ko/login.ts create mode 100644 shared/src/i18n/ko/map.ts create mode 100644 shared/src/i18n/ko/members.ts create mode 100644 shared/src/i18n/ko/memories.ts create mode 100644 shared/src/i18n/ko/nav.ts create mode 100644 shared/src/i18n/ko/notif.ts create mode 100644 shared/src/i18n/ko/notifications.ts create mode 100644 shared/src/i18n/ko/oauth.ts create mode 100644 shared/src/i18n/ko/packing.ts create mode 100644 shared/src/i18n/ko/pdf.ts create mode 100644 shared/src/i18n/ko/perm.ts create mode 100644 shared/src/i18n/ko/photos.ts create mode 100644 shared/src/i18n/ko/places.ts create mode 100644 shared/src/i18n/ko/planner.ts create mode 100644 shared/src/i18n/ko/register.ts create mode 100644 shared/src/i18n/ko/reservations.ts create mode 100644 shared/src/i18n/ko/settings.ts create mode 100644 shared/src/i18n/ko/share.ts create mode 100644 shared/src/i18n/ko/shared.ts create mode 100644 shared/src/i18n/ko/stats.ts create mode 100644 shared/src/i18n/ko/system_notice.ts create mode 100644 shared/src/i18n/ko/todo.ts create mode 100644 shared/src/i18n/ko/transport.ts create mode 100644 shared/src/i18n/ko/trip.ts create mode 100644 shared/src/i18n/ko/trips.ts create mode 100644 shared/src/i18n/ko/undo.ts create mode 100644 shared/src/i18n/ko/vacay.ts create mode 100644 shared/src/i18n/languages.ts create mode 100644 shared/src/i18n/nl/admin.ts create mode 100644 shared/src/i18n/nl/airport.ts create mode 100644 shared/src/i18n/nl/atlas.ts create mode 100644 shared/src/i18n/nl/backup.ts create mode 100644 shared/src/i18n/nl/budget.ts create mode 100644 shared/src/i18n/nl/categories.ts create mode 100644 shared/src/i18n/nl/collab.ts create mode 100644 shared/src/i18n/nl/common.ts create mode 100644 shared/src/i18n/nl/dashboard.ts create mode 100644 shared/src/i18n/nl/day.ts create mode 100644 shared/src/i18n/nl/dayplan.ts create mode 100644 shared/src/i18n/nl/externalNotifications.ts create mode 100644 shared/src/i18n/nl/files.ts create mode 100644 shared/src/i18n/nl/index.ts create mode 100644 shared/src/i18n/nl/inspector.ts create mode 100644 shared/src/i18n/nl/journey.ts create mode 100644 shared/src/i18n/nl/login.ts create mode 100644 shared/src/i18n/nl/map.ts create mode 100644 shared/src/i18n/nl/members.ts create mode 100644 shared/src/i18n/nl/memories.ts create mode 100644 shared/src/i18n/nl/nav.ts create mode 100644 shared/src/i18n/nl/notif.ts create mode 100644 shared/src/i18n/nl/notifications.ts create mode 100644 shared/src/i18n/nl/oauth.ts create mode 100644 shared/src/i18n/nl/packing.ts create mode 100644 shared/src/i18n/nl/pdf.ts create mode 100644 shared/src/i18n/nl/perm.ts create mode 100644 shared/src/i18n/nl/photos.ts create mode 100644 shared/src/i18n/nl/places.ts create mode 100644 shared/src/i18n/nl/planner.ts create mode 100644 shared/src/i18n/nl/register.ts create mode 100644 shared/src/i18n/nl/reservations.ts create mode 100644 shared/src/i18n/nl/settings.ts create mode 100644 shared/src/i18n/nl/share.ts create mode 100644 shared/src/i18n/nl/shared.ts create mode 100644 shared/src/i18n/nl/stats.ts create mode 100644 shared/src/i18n/nl/system_notice.ts create mode 100644 shared/src/i18n/nl/todo.ts create mode 100644 shared/src/i18n/nl/transport.ts create mode 100644 shared/src/i18n/nl/trip.ts create mode 100644 shared/src/i18n/nl/trips.ts create mode 100644 shared/src/i18n/nl/undo.ts create mode 100644 shared/src/i18n/nl/vacay.ts create mode 100644 shared/src/i18n/pl/admin.ts create mode 100644 shared/src/i18n/pl/airport.ts create mode 100644 shared/src/i18n/pl/atlas.ts create mode 100644 shared/src/i18n/pl/backup.ts create mode 100644 shared/src/i18n/pl/budget.ts create mode 100644 shared/src/i18n/pl/categories.ts create mode 100644 shared/src/i18n/pl/collab.ts create mode 100644 shared/src/i18n/pl/common.ts create mode 100644 shared/src/i18n/pl/dashboard.ts create mode 100644 shared/src/i18n/pl/day.ts create mode 100644 shared/src/i18n/pl/dayplan.ts create mode 100644 shared/src/i18n/pl/externalNotifications.ts create mode 100644 shared/src/i18n/pl/files.ts create mode 100644 shared/src/i18n/pl/index.ts create mode 100644 shared/src/i18n/pl/inspector.ts create mode 100644 shared/src/i18n/pl/journey.ts create mode 100644 shared/src/i18n/pl/login.ts create mode 100644 shared/src/i18n/pl/map.ts create mode 100644 shared/src/i18n/pl/members.ts create mode 100644 shared/src/i18n/pl/memories.ts create mode 100644 shared/src/i18n/pl/nav.ts create mode 100644 shared/src/i18n/pl/notif.ts create mode 100644 shared/src/i18n/pl/notifications.ts create mode 100644 shared/src/i18n/pl/oauth.ts create mode 100644 shared/src/i18n/pl/packing.ts create mode 100644 shared/src/i18n/pl/pdf.ts create mode 100644 shared/src/i18n/pl/perm.ts create mode 100644 shared/src/i18n/pl/photos.ts create mode 100644 shared/src/i18n/pl/places.ts create mode 100644 shared/src/i18n/pl/planner.ts create mode 100644 shared/src/i18n/pl/register.ts create mode 100644 shared/src/i18n/pl/reservations.ts create mode 100644 shared/src/i18n/pl/settings.ts create mode 100644 shared/src/i18n/pl/share.ts create mode 100644 shared/src/i18n/pl/shared.ts create mode 100644 shared/src/i18n/pl/stats.ts create mode 100644 shared/src/i18n/pl/system_notice.ts create mode 100644 shared/src/i18n/pl/todo.ts create mode 100644 shared/src/i18n/pl/transport.ts create mode 100644 shared/src/i18n/pl/trip.ts create mode 100644 shared/src/i18n/pl/trips.ts create mode 100644 shared/src/i18n/pl/undo.ts create mode 100644 shared/src/i18n/pl/vacay.ts create mode 100644 shared/src/i18n/ru/admin.ts create mode 100644 shared/src/i18n/ru/airport.ts create mode 100644 shared/src/i18n/ru/atlas.ts create mode 100644 shared/src/i18n/ru/backup.ts create mode 100644 shared/src/i18n/ru/budget.ts create mode 100644 shared/src/i18n/ru/categories.ts create mode 100644 shared/src/i18n/ru/collab.ts create mode 100644 shared/src/i18n/ru/common.ts create mode 100644 shared/src/i18n/ru/dashboard.ts create mode 100644 shared/src/i18n/ru/day.ts create mode 100644 shared/src/i18n/ru/dayplan.ts create mode 100644 shared/src/i18n/ru/externalNotifications.ts create mode 100644 shared/src/i18n/ru/files.ts create mode 100644 shared/src/i18n/ru/index.ts create mode 100644 shared/src/i18n/ru/inspector.ts create mode 100644 shared/src/i18n/ru/journey.ts create mode 100644 shared/src/i18n/ru/login.ts create mode 100644 shared/src/i18n/ru/map.ts create mode 100644 shared/src/i18n/ru/members.ts create mode 100644 shared/src/i18n/ru/memories.ts create mode 100644 shared/src/i18n/ru/nav.ts create mode 100644 shared/src/i18n/ru/notif.ts create mode 100644 shared/src/i18n/ru/notifications.ts create mode 100644 shared/src/i18n/ru/oauth.ts create mode 100644 shared/src/i18n/ru/packing.ts create mode 100644 shared/src/i18n/ru/pdf.ts create mode 100644 shared/src/i18n/ru/perm.ts create mode 100644 shared/src/i18n/ru/photos.ts create mode 100644 shared/src/i18n/ru/places.ts create mode 100644 shared/src/i18n/ru/planner.ts create mode 100644 shared/src/i18n/ru/register.ts create mode 100644 shared/src/i18n/ru/reservations.ts create mode 100644 shared/src/i18n/ru/settings.ts create mode 100644 shared/src/i18n/ru/share.ts create mode 100644 shared/src/i18n/ru/shared.ts create mode 100644 shared/src/i18n/ru/stats.ts create mode 100644 shared/src/i18n/ru/system_notice.ts create mode 100644 shared/src/i18n/ru/todo.ts create mode 100644 shared/src/i18n/ru/transport.ts create mode 100644 shared/src/i18n/ru/trip.ts create mode 100644 shared/src/i18n/ru/trips.ts create mode 100644 shared/src/i18n/ru/undo.ts create mode 100644 shared/src/i18n/ru/vacay.ts create mode 100644 shared/src/i18n/tr/admin.ts create mode 100644 shared/src/i18n/tr/airport.ts create mode 100644 shared/src/i18n/tr/atlas.ts create mode 100644 shared/src/i18n/tr/backup.ts create mode 100644 shared/src/i18n/tr/budget.ts create mode 100644 shared/src/i18n/tr/categories.ts create mode 100644 shared/src/i18n/tr/collab.ts create mode 100644 shared/src/i18n/tr/common.ts create mode 100644 shared/src/i18n/tr/dashboard.ts create mode 100644 shared/src/i18n/tr/day.ts create mode 100644 shared/src/i18n/tr/dayplan.ts create mode 100644 shared/src/i18n/tr/externalNotifications.ts create mode 100644 shared/src/i18n/tr/files.ts create mode 100644 shared/src/i18n/tr/index.ts create mode 100644 shared/src/i18n/tr/inspector.ts create mode 100644 shared/src/i18n/tr/journey.ts create mode 100644 shared/src/i18n/tr/login.ts create mode 100644 shared/src/i18n/tr/map.ts create mode 100644 shared/src/i18n/tr/members.ts create mode 100644 shared/src/i18n/tr/memories.ts create mode 100644 shared/src/i18n/tr/nav.ts create mode 100644 shared/src/i18n/tr/notif.ts create mode 100644 shared/src/i18n/tr/notifications.ts create mode 100644 shared/src/i18n/tr/oauth.ts create mode 100644 shared/src/i18n/tr/packing.ts create mode 100644 shared/src/i18n/tr/pdf.ts create mode 100644 shared/src/i18n/tr/perm.ts create mode 100644 shared/src/i18n/tr/photos.ts create mode 100644 shared/src/i18n/tr/places.ts create mode 100644 shared/src/i18n/tr/planner.ts create mode 100644 shared/src/i18n/tr/register.ts create mode 100644 shared/src/i18n/tr/reservations.ts create mode 100644 shared/src/i18n/tr/settings.ts create mode 100644 shared/src/i18n/tr/share.ts create mode 100644 shared/src/i18n/tr/shared.ts create mode 100644 shared/src/i18n/tr/stats.ts create mode 100644 shared/src/i18n/tr/system_notice.ts create mode 100644 shared/src/i18n/tr/todo.ts create mode 100644 shared/src/i18n/tr/transport.ts create mode 100644 shared/src/i18n/tr/trip.ts create mode 100644 shared/src/i18n/tr/trips.ts create mode 100644 shared/src/i18n/tr/undo.ts create mode 100644 shared/src/i18n/tr/vacay.ts create mode 100644 shared/src/i18n/types.ts create mode 100644 shared/src/i18n/uk/admin.ts create mode 100644 shared/src/i18n/uk/airport.ts create mode 100644 shared/src/i18n/uk/atlas.ts create mode 100644 shared/src/i18n/uk/backup.ts create mode 100644 shared/src/i18n/uk/budget.ts create mode 100644 shared/src/i18n/uk/categories.ts create mode 100644 shared/src/i18n/uk/collab.ts create mode 100644 shared/src/i18n/uk/common.ts create mode 100644 shared/src/i18n/uk/dashboard.ts create mode 100644 shared/src/i18n/uk/day.ts create mode 100644 shared/src/i18n/uk/dayplan.ts create mode 100644 shared/src/i18n/uk/externalNotifications.ts create mode 100644 shared/src/i18n/uk/files.ts create mode 100644 shared/src/i18n/uk/index.ts create mode 100644 shared/src/i18n/uk/inspector.ts create mode 100644 shared/src/i18n/uk/journey.ts create mode 100644 shared/src/i18n/uk/login.ts create mode 100644 shared/src/i18n/uk/map.ts create mode 100644 shared/src/i18n/uk/members.ts create mode 100644 shared/src/i18n/uk/memories.ts create mode 100644 shared/src/i18n/uk/nav.ts create mode 100644 shared/src/i18n/uk/notif.ts create mode 100644 shared/src/i18n/uk/notifications.ts create mode 100644 shared/src/i18n/uk/oauth.ts create mode 100644 shared/src/i18n/uk/packing.ts create mode 100644 shared/src/i18n/uk/pdf.ts create mode 100644 shared/src/i18n/uk/perm.ts create mode 100644 shared/src/i18n/uk/photos.ts create mode 100644 shared/src/i18n/uk/places.ts create mode 100644 shared/src/i18n/uk/planner.ts create mode 100644 shared/src/i18n/uk/register.ts create mode 100644 shared/src/i18n/uk/reservations.ts create mode 100644 shared/src/i18n/uk/settings.ts create mode 100644 shared/src/i18n/uk/share.ts create mode 100644 shared/src/i18n/uk/shared.ts create mode 100644 shared/src/i18n/uk/stats.ts create mode 100644 shared/src/i18n/uk/system_notice.ts create mode 100644 shared/src/i18n/uk/todo.ts create mode 100644 shared/src/i18n/uk/transport.ts create mode 100644 shared/src/i18n/uk/trip.ts create mode 100644 shared/src/i18n/uk/trips.ts create mode 100644 shared/src/i18n/uk/undo.ts create mode 100644 shared/src/i18n/uk/vacay.ts create mode 100644 shared/src/i18n/zh-TW/admin.ts create mode 100644 shared/src/i18n/zh-TW/airport.ts create mode 100644 shared/src/i18n/zh-TW/atlas.ts create mode 100644 shared/src/i18n/zh-TW/backup.ts create mode 100644 shared/src/i18n/zh-TW/budget.ts create mode 100644 shared/src/i18n/zh-TW/categories.ts create mode 100644 shared/src/i18n/zh-TW/collab.ts create mode 100644 shared/src/i18n/zh-TW/common.ts create mode 100644 shared/src/i18n/zh-TW/dashboard.ts create mode 100644 shared/src/i18n/zh-TW/day.ts create mode 100644 shared/src/i18n/zh-TW/dayplan.ts create mode 100644 shared/src/i18n/zh-TW/externalNotifications.ts create mode 100644 shared/src/i18n/zh-TW/files.ts create mode 100644 shared/src/i18n/zh-TW/index.ts create mode 100644 shared/src/i18n/zh-TW/inspector.ts create mode 100644 shared/src/i18n/zh-TW/journey.ts create mode 100644 shared/src/i18n/zh-TW/login.ts create mode 100644 shared/src/i18n/zh-TW/map.ts create mode 100644 shared/src/i18n/zh-TW/members.ts create mode 100644 shared/src/i18n/zh-TW/memories.ts create mode 100644 shared/src/i18n/zh-TW/nav.ts create mode 100644 shared/src/i18n/zh-TW/notif.ts create mode 100644 shared/src/i18n/zh-TW/notifications.ts create mode 100644 shared/src/i18n/zh-TW/oauth.ts create mode 100644 shared/src/i18n/zh-TW/packing.ts create mode 100644 shared/src/i18n/zh-TW/pdf.ts create mode 100644 shared/src/i18n/zh-TW/perm.ts create mode 100644 shared/src/i18n/zh-TW/photos.ts create mode 100644 shared/src/i18n/zh-TW/places.ts create mode 100644 shared/src/i18n/zh-TW/planner.ts create mode 100644 shared/src/i18n/zh-TW/register.ts create mode 100644 shared/src/i18n/zh-TW/reservations.ts create mode 100644 shared/src/i18n/zh-TW/settings.ts create mode 100644 shared/src/i18n/zh-TW/share.ts create mode 100644 shared/src/i18n/zh-TW/shared.ts create mode 100644 shared/src/i18n/zh-TW/stats.ts create mode 100644 shared/src/i18n/zh-TW/system_notice.ts create mode 100644 shared/src/i18n/zh-TW/todo.ts create mode 100644 shared/src/i18n/zh-TW/transport.ts create mode 100644 shared/src/i18n/zh-TW/trip.ts create mode 100644 shared/src/i18n/zh-TW/trips.ts create mode 100644 shared/src/i18n/zh-TW/undo.ts create mode 100644 shared/src/i18n/zh-TW/vacay.ts create mode 100644 shared/src/i18n/zh/admin.ts create mode 100644 shared/src/i18n/zh/airport.ts create mode 100644 shared/src/i18n/zh/atlas.ts create mode 100644 shared/src/i18n/zh/backup.ts create mode 100644 shared/src/i18n/zh/budget.ts create mode 100644 shared/src/i18n/zh/categories.ts create mode 100644 shared/src/i18n/zh/collab.ts create mode 100644 shared/src/i18n/zh/common.ts create mode 100644 shared/src/i18n/zh/dashboard.ts create mode 100644 shared/src/i18n/zh/day.ts create mode 100644 shared/src/i18n/zh/dayplan.ts create mode 100644 shared/src/i18n/zh/externalNotifications.ts create mode 100644 shared/src/i18n/zh/files.ts create mode 100644 shared/src/i18n/zh/index.ts create mode 100644 shared/src/i18n/zh/inspector.ts create mode 100644 shared/src/i18n/zh/journey.ts create mode 100644 shared/src/i18n/zh/login.ts create mode 100644 shared/src/i18n/zh/map.ts create mode 100644 shared/src/i18n/zh/members.ts create mode 100644 shared/src/i18n/zh/memories.ts create mode 100644 shared/src/i18n/zh/nav.ts create mode 100644 shared/src/i18n/zh/notif.ts create mode 100644 shared/src/i18n/zh/notifications.ts create mode 100644 shared/src/i18n/zh/oauth.ts create mode 100644 shared/src/i18n/zh/packing.ts create mode 100644 shared/src/i18n/zh/pdf.ts create mode 100644 shared/src/i18n/zh/perm.ts create mode 100644 shared/src/i18n/zh/photos.ts create mode 100644 shared/src/i18n/zh/places.ts create mode 100644 shared/src/i18n/zh/planner.ts create mode 100644 shared/src/i18n/zh/register.ts create mode 100644 shared/src/i18n/zh/reservations.ts create mode 100644 shared/src/i18n/zh/settings.ts create mode 100644 shared/src/i18n/zh/share.ts create mode 100644 shared/src/i18n/zh/shared.ts create mode 100644 shared/src/i18n/zh/stats.ts create mode 100644 shared/src/i18n/zh/system_notice.ts create mode 100644 shared/src/i18n/zh/todo.ts create mode 100644 shared/src/i18n/zh/transport.ts create mode 100644 shared/src/i18n/zh/trip.ts create mode 100644 shared/src/i18n/zh/trips.ts create mode 100644 shared/src/i18n/zh/undo.ts create mode 100644 shared/src/i18n/zh/vacay.ts diff --git a/.issue-scratch/759.png b/.issue-scratch/759.png deleted file mode 100644 index e319ccc21426d75b42b327752674633a2a548007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24257 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 diff --git a/.issue-scratch/764-1.png b/.issue-scratch/764-1.png deleted file mode 100644 index 2734ff6e2318e0648cac62bbd2827e45501e6cee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 466058 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�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%0Av)9Ymt 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

l#!sglE{$LZ^SY#nLMjTh112WIH>=G1)6r> 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%0DbZz)@MCyn0>El9a<=k)aqg$zMX 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

osQ!qZRv1jIv#|KQz@C5kXy9J@LmLSvC9k#Tc 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%0D?6tG8E@G|XL$FfT?}CGGtM6@ah-0xu%% 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 R5KjbtN;hxI7s;9g{0DM;E;IlD diff --git a/.issue-scratch/release-draft-new.md b/.issue-scratch/release-draft-new.md deleted file mode 100644 index 12cd39bd..00000000 --- a/.issue-scratch/release-draft-new.md +++ /dev/null @@ -1,524 +0,0 @@ -Release 2 9 0 (2) - -# TREK 3.0.0 - - - -> **The biggest TREK release to date.** A new Journey addon turns your trips into rich travel journals. Mapbox GL joins Leaflet as a first-class renderer. MCP gets a full OAuth 2.1 authorization server. Offline-first PWA, self-service password reset, and a dashboard redesigned from the ground up. Fifteen languages, top to bottom. - ---- - -## Breaking Changes - -### Photos moved from Trip Planner to Journey - -In previous versions, Immich and Synology Photos were integrated directly into the Trip Planner via a "Photos" tab. **This tab has been removed.** Photos are now part of the new **Journey addon**, which is purpose-built for documenting your travels with stories, photos, and maps. - -**What this means for you:** -- **No photos are lost.** The previous integration was read-only — TREK never uploaded to or deleted from your Immich/Synology library. Your photos remain untouched in your photo provider. -- **Previously linked trip photos are no longer displayed in the Trip Planner.** To view and organize your travel photos, enable the Journey addon (Settings > Addons) and create a Journey linked to your trip. -- **Journey brings a much richer photo experience:** upload photos directly to TREK, browse and import from Immich/Synology with duplicate detection, reorder photos, view EXIF metadata, and export everything as a PDF photo book. - -### New Immich API Key Permissions Required - -Journey introduces **photo upload sync** — when you upload a photo to a Journey entry, TREK can optionally sync it to your Immich library. This requires an additional Immich API permission that was not needed before. - -**Previous versions required:** -| Permission | Used for | -|---|---| -| `user.read` | Connection test | -| `asset.read` | Browse photos by date, search | -| `asset.view` | Stream thumbnails | -| `asset.download` | Stream originals | -| `album.read` | List and browse albums | -| `timeline.read` | Browse timeline buckets | - -**New in 3.0.0 — additionally required:** -| Permission | Used for | -|---|---| -| `asset.upload` | Sync uploaded Journey photos to Immich | - -> **How to update your Immich API key:** Go to your Immich instance > User Settings > API Keys. Edit your existing TREK key (or create a new one) and ensure `asset.upload` is enabled in addition to the existing permissions. If you don't plan to use Journey's upload sync, the old key will continue to work — the upload simply won't sync to Immich. - -**No changes needed for Synology Photos** — Synology uses session-based authentication which inherits the user's full permissions. - -### OIDC_ONLY deprecated - -The `OIDC_ONLY` environment variable is deprecated. Replace with `DISABLE_LOCAL_LOGIN=true` + `DISABLE_LOCAL_REGISTRATION=true` for equivalent behavior. The old variable still works but will be removed in a future release. - ---- - -Release 2 9 0 (3) - -## Journey Addon — Travel Journal - -The headline feature of 3.0.0. Journey is a new global addon that transforms your trips into magazine-style travel stories. - -### Core -- **5-table schema** — journeys, entries, photos, trips, contributors with full relational integrity -- **Trip-to-Journey sync engine** — link one or more trips to a journey; skeleton entries and photos are synced automatically -- **Timeline, Gallery, and Map views** — browse entries chronologically, as a photo grid, or on an interactive map with SVG pin markers -- **Entry editor** — markdown toolbar, custom date picker, location search (Nominatim/Google Maps), mood (Amazing/Good/Neutral/Rough), weather (Sunny to Snowy), and Pros & Cons sections -- **Entry reorder** — move-up / move-down arrows on each entry (desktop), skipped on skeleton suggestions -- **Hide skeletons toggle** — per-contributor setting to focus on the written entries only - -### Photos -- **Immich & Synology browser** — browse by trip dates, custom range, or album with duplicate detection -- **Photo upload** — direct upload with drag-and-drop, reorder (Make 1st), and delete -- **EXIF metadata** — displayed in lightbox for Immich photos -- **Thumbnail to original fallback** — seamless resolution upgrade everywhere -- **HEIC rendering fix** — serve fullsize thumbnail for original to fix HEIC rendering on non-Safari browsers -- **Contributor photo access** — invited contributors can view all journey photos even without their own Immich/Synology connection (owner credentials are used for the proxy) -- **Safari gallery picker fix** — repaired grid layout collapse on Safari (#717) - -### Sharing & Export -- **Public share links** — token-based access with language picker, no login required -- **Public photo proxy** — validates share token instead of auth for photo streaming -- **Thumbnail size in public gallery** — grid loads thumbnails instead of originals, lightbox keeps originals (cuts bandwidth on shared links significantly) -- **PDF photo book export** — Polarsteps-inspired layout with cover, day chapters, photo grids, and stories - -### Collaboration -- **Contributors** — invite users as editors or viewers -- **Trip linking/unlinking** — manage synced trips from Journey Settings and Desktop Sidebar -- **Cover image** — upload or pick from journey photos - -### Frontend -- **JourneyPage** — frontpage with hero card, active journey stats, trip suggestions ("Trip just ended — turn it into a Journey") -- **JourneyDetailPage** — full timeline/gallery/map with inline entry editing -- **JourneyPublicPage** — public share view with language picker and read-only timeline - ---- - -## Mapbox GL as a First-Class Renderer - -Leaflet gets a sibling. Users can now switch the trip planner map to **Mapbox GL JS** for a proper 3D globe, terrain, and 3D buildings. - -- **Settings toggle** — choose between Leaflet and Mapbox GL in Settings > Map -- **Globe projection** — smooth rotating globe when zoomed out, mercator when zoomed in -- **3D terrain and buildings** — enabled on Standard and Satellite styles, with custom 3D buildings in dark/light mode -- **Trip route, GPX geometries, place markers** — full feature parity with the Leaflet renderer -- **Transport reservations overlay** — great-circle arcs for flights/cruises, straight lines for trains/cars, clickable endpoint badges with IATA codes, rotating mid-arc stats label for flights. Honours the per-booking "show route" toggle in DayPlanSidebar -- **Auto-fit on load** — planner map zooms to the trip's places on initial render -- **Booking route label toggle** — separate setting to hide IATA labels on endpoint markers -- **Infrastructure** — WebAssembly allowed in CSP for Mapbox GL's 3D engine, PWA precache limit raised so the mapbox-gl bundle builds, Mapbox endpoints allowed in `connect-src` / `img-src` - ---- - -## MCP: OAuth 2.1 & Granular Scopes - -MCP authentication has been completely rebuilt around the OAuth 2.1 specification. - -- **OAuth 2.1 authorization server** — full PKCE flow with authorization codes, access tokens, refresh tokens, and token rotation with replay detection -- **Granular scopes** — 24 scopes across 11 groups (trips, places, atlas, packing, todos, budget, reservations, collab, notifications, vacay, geo/weather) with per-scope read/write/delete control -- **Dynamic Client Registration (DCR)** — RFC 7591 endpoint at `POST /oauth/register`, with strict redirect_uri validation (HTTPS / loopback / reverse-DNS private-use schemes only; rejects `javascript:` / `data:` / `file:` / etc.) -- **RFC 9728 Protected Resource Metadata** — `/.well-known/oauth-protected-resource` exposes the MCP endpoint's auth requirements for client auto-discovery -- **RFC 8707 audience binding** — tokens are audience-bound to `/mcp` by default and validated on every MCP request -- **Consent screen** — user-facing scope selection with grouped permission display -- **Admin panel** — OAuth sessions management in MCP Access panel with collapsible scope lists -- **Per-client rate limiting** — configurable rate limits per OAuth client -- **Addon gating** — MCP tools are only registered when their corresponding addon is enabled -- **Compound tools** — single-call multi-step workflows (e.g. create day with places in one tool call, fetch full trip context) to reduce MCP round-trips -- **Surface alignment** — MCP tool schemas and responses kept in sync with the current app state (fewer drifted fields, correct enum sets) -- **Static token deprecation** — existing MCP tokens still work but surface deprecation notices; migration path to OAuth is documented -- **Collab sub-feature gating** — MCP tools for chat/notes/polls respect the admin-level collab sub-feature toggles - ---- - -## Self-Service Password Reset - -Users can now reset their own password without admin intervention. - -- **Email-based flow** — `/forgot-password` issues a single-use reset token delivered via SMTP (or logged to the server console if SMTP is not configured) -- **MFA-aware** — if the user has MFA enabled, the reset endpoint additionally verifies a TOTP code or backup code before rotating the password -- **Session invalidation** — resetting the password bumps `users.password_version`, which kicks every existing JWT, MCP static token, and OAuth bearer token for that user out in one shot -- **Server-side URL building** — the reset link is built from `APP_URL` / `ALLOWED_ORIGINS`, not from request headers, so a spoofed `Host` / `Origin` cannot redirect the link to an attacker-controlled domain -- **Rate limiting + audit** — per-IP rate limit on `/forgot-password`, all requests audited (including "no such user" so abuse is visible) - ---- - -## Dashboard Redesign - -The dashboard has been rebuilt with a mobile-first design language. - -### Mobile -- **Greeting header** — "Good morning, {username}" with notification bell and avatar -- **Spotlight hero card** — the next upcoming or ongoing trip as a full-width hero with cover image, progress bar (for live trips), stats grid, and frosted-glass action buttons -- **Quick Actions** — New Trip, Currency Converter, Timezone as icon cards -- **Trip cards** — cover image with title overlay, status badge (In X days / Starts today / Ongoing / Completed), bottom stats (starts, duration, places, buddies) - -### Desktop -- **Unified header toolbar** — the dashboard, planner, vacay, and journey now share the same toolbar style -- **Unified card design** — desktop grid cards now match the mobile card style (cover + title overlay + stats) -- **Hero card** — SpotlightCard with progress bar for ongoing trips, countdown for upcoming, stats grid -- **Hover actions** — edit/copy/archive/delete buttons appear on hover as frosted-glass icons -- **Status badges** — CircleCheck icon for completed trips, Clock for upcoming, pulsing dot for ongoing - -### Both -- **BottomNav profile sheet** — slide-up sheet with user info, settings, admin, and logout -- **Dark mode** — full dark mode support across all new components -- **Shared PageSidebar** — Settings and Admin pages share a single sidebar component for layout consistency - ---- - -## PWA Offline Mode - -TREK now works offline as a Progressive Web App with full data synchronization. - -- **IndexedDB (Dexie) storage** — trips, places, assignments, categories, tags, accommodations, reservations, budget items, packing items, files, and trip members cached locally -- **Offline mutation queue** — changes made offline are queued with monotonic timestamps and replayed on reconnect (FIFO) -- **Offline dashboard** — trip list loaded from Dexie when network is unavailable -- **Offline trip planner** — full planner functionality with cached data -- **Repo layer** — all data access routed through repository layer that falls back to offline storage -- **Offline banner** — visible indicator with safe-area-inset support for iOS PWA -- **Idempotency keys** — prevents duplicate mutations on replay, scoped by `(key, user_id, method, path)` so the same key on different endpoints can't leak cached bodies -- **Offline document downloads** — document downloads work from the PWA cache when the network is unavailable - ---- - -## Transport Reservations: Multi-Day + Map Visualization - -- **Multi-day transport reservations** — flights, trains, cruises, car rentals can span multiple days with a dedicated modal and automatic route segmentation across the affected days (#384, #587) -- **Map visualization** — transport endpoints render on both Leaflet and Mapbox GL maps as clickable badges with IATA codes, great-circle arcs for flights/cruises, straight lines for trains/cars, and a rotating mid-arc stats label (IATA → IATA · distance · duration) on flights -- **Per-booking route toggle** — each booking in DayPlanSidebar has a "Show booking routes" button; connections only render when toggled on -- **Check-in time ranges** — hotel bookings now support a check-in window (e.g. "15:00 -- 22:00") with a new `check_in_end` field (#366) -- **Cascaded delete** — deleting a reservation now cleans up related budget items, file links, and trip_items - ---- - -## Reservations Redesign - -The reservations panel has been completely redesigned with a modern, unified layout. - -- **Unified toolbar** — title, type filter pills with count badges, and add button in one row with muted background -- **Type filters** — multi-select filter buttons (Flight, Hotel, Restaurant, etc.) with per-type count badges, persisted in sessionStorage -- **Responsive grid** — auto-fill layout with max 3 columns that fills full width -- **Card redesign** — status + type badge in header, labeled fields in rounded boxes, hover shadow -- **Mobile responsive** — filters hidden on mobile, booking code on separate row, weekday hidden in dates, reduced padding - ---- - -## Apple Wallet pkpass Support - -- **.pkpass MIME type** — server correctly serves `application/vnd.apple.pkpass` with the right Content-Type -- **Upload + download** — .pkpass files can be attached to bookings or places and opened directly in Apple Wallet on iOS - ---- - -## Todo Due-Date Reminders - -- **Scheduler** — a new background scheduler scans todos with upcoming due dates and sends one reminder per item (default lead: 3 days) -- **No spam** — `todo_items.reminded_at` prevents re-sending a reminder for the same item on subsequent scheduler runs -- **Notification channel aware** — reminders respect the user's notification channel preferences (email, webhook, ntfy) - ---- - -## Collab Sub-Feature Toggles - -Individual collab sections can now be toggled on/off from the admin addons page (#604). - -- **Admin UI** — sub-toggles for Chat, Notes, Polls, and What's Next under the Collab addon, with icons matching the collab panel tabs -- **Dynamic desktop layout** — Chat always stays at fixed 380px width; remaining active panels share space equally -- **Mobile** — disabled tabs are hidden from the tab bar -- **API** — GET/PUT /admin/collab-features endpoints stored in app_settings - ---- - -## Place Import: KMZ/KML + Naver Maps + Selective GPX - -Three ways to import places into your trips. - -### KMZ/KML Import -- **Unified file import modal** — drag-and-drop or file picker for KML, KMZ, and GPX files -- **KMZ unpacking** — extracts KML from ZIP archive with 50MB decompressed size limit -- **Folder-to-category mapping** — KML folders are automatically matched to TREK categories -- **Place deduplication** — skips places that already exist in the trip (by name + coordinates) - -### Naver Maps List Import -- **Always enabled** — no longer requires addon toggle, available alongside Google Maps list import -- **Shortlink resolution** — resolves naver.me shortlinks to full list URLs -- **Pagination support** — handles large Naver Maps lists with automatic pagination - -### Selective GPX/KML Element Import -- **Pick what to import** — import modal now lets you choose individual waypoints / tracks / folders instead of an all-or-nothing dump -- **Performance** — larger files (thousands of points) parse and render without freezing the UI - ---- - -## Search Autocomplete - -- **Real-time suggestions** — autocomplete suggestions appear as you type in the place search field -- **Google Places API** — primary autocomplete provider with location bias -- **Nominatim fallback** — free fallback when Google API key is not configured -- **Bounding box bias** — search results biased to the current map viewport - ---- - -## ntfy Notification Channel - -- **ntfy as first-class channel** — push notifications via any ntfy server (self-hosted or ntfy.sh) -- **Admin configuration** — server URL and topic configuration in admin panel with clear token button -- **Per-user opt-in** — users can enable/disable ntfy in their notification preferences -- **Full i18n** — ntfy strings translated in all 15 languages - ---- - -## Login & Language - -- **Language dropdown on login page** — users can select their preferred language before logging in -- **Browser auto-detection** — language is automatically detected from browser settings on first visit -- **DEFAULT_LANGUAGE env var** — configurable default language for the instance, documented across all deployment configs (Docker, Helm, Synology) - ---- - -## Granular Auth Toggles - -- **OIDC_ONLY replaced** — split into `DISABLE_LOCAL_LOGIN`, `DISABLE_LOCAL_REGISTRATION`, and `DISABLE_PASSWORD_CHANGE` for fine-grained control over authentication methods -- Allows mixed setups (e.g., OIDC + local admin account, or OIDC-only with no local registration) - ---- - -## Synology Photos: OTP, SSL Skip & Session Management - -- **OTP support** — one-time password field for 2FA-enabled Synology NAS -- **Skip SSL verification** — toggle for self-signed certificates -- **Device ID persistence** — prevents repeated 2FA prompts -- **Session-cleared notification** — routed through unified notification system -- **Provider URL hint** — contextual help text for Synology URL format -- **Thumbnail size bump** — default thumbnail size raised from `sm` (240 px) to `m` (320 px) so grids no longer look pixelated on retina -- **Passphrase support** — shared-album links with passphrases work from the browse UI (#689) - ---- - -## Atlas Improvements - -- **Scoped region matching** — region name matching is now scoped by country to prevent cross-country false matches -- **Expanded country lookup tables** — more countries and regions recognized correctly, including A3 fallback for invalid ISO_A2 codes -- **Nominatim rate limiting** — shared throttle prevents 429 errors, background region fill, fetch timeout -- **Stadia Maps fix** — resolved 401 errors on journey and atlas maps - ---- - -## i18n: Full 15-Language Coverage - -- **Indonesian added** — complete translation with full parity to English, bringing the total to 15 languages (EN, DE, FR, ES, IT, NL, PL, RU, ZH, ZH-TW, BR, CS, HU, AR, ID) -- **Comprehensive audit** — every key translated natively, no English fallbacks -- **OAuth scope labels** — all 24 scopes have localized names and descriptions -- **Journey addon** — complete coverage for all journal, editor, sharing, and PDF export strings -- **Mapbox GL settings** — localized labels for renderer toggle, style picker, 3D / quality switches -- **Ellipsis standardization** — all ellipsis characters normalized to three dots (...) - ---- - -## Vacay Improvements - -- **Trip indicator dots** — small blue dots on calendar days where trips are scheduled -- **Configurable week start** — choose Monday or Sunday as first day of the week (#224) -- **Holiday overlap** — vacations can now be placed on public holidays -- **Today marker** — visual indicator for the current day in the calendar -- **Unified toolbar** — same header style as planner/dashboard/journey -- **Bottom padding fix** — toolbar no longer overlaps the last row (#533) - ---- - -## iCal Export Improvements - -- **Day activities and notes** — iCal export now includes daily activities and notes, not just the trip dates (#375) - ---- - -## Budget Improvements - -- **Drag-and-drop reorder** — budget categories and individual items can be reordered via drag-and-drop (#479) -- **Category legend redesign** — prevents overflow on small screens (#564) -- **Comma decimal support** — pasting numbers with comma separators works correctly -- **Table alignment fix** — budget data rows and the "New Entry" row now share column widths (#759) - ---- - -## Packing List Improvements - -- **Bulk import + template apply without full reload** — new items appear in place instead of triggering the trip loading screen (#760) -- **Reservation link cleanup** — packing items linked to deleted reservations stay in the list without the dangling reference -- **Bag tracking** — keep track of which items live in which bag, with optional weight tracking and per-bag totals - ---- - -## Planner & UX Improvements - -- **Emil-style polish pass** — consistent transitions/animations across cards, hover states, and drawer sheets; shared components for toolbars and section headers -- **Planner drag-and-drop jank fix** — dragging places across days is smooth again on long trips -- **Unified toolbar header** — dashboard, planner, vacay, and journey share a single toolbar style for visual consistency -- **Places sidebar polish** — filter counts, compact select UI, tooltip component, "No Category" / "Uncategorized" filter (#607) -- **Dayplan toolbar polish** — cleaner alignment, weather archive fallback for past trips -- **Unplanned filter sync** — unplanned filter properly syncs with map markers (#385) -- **Place notes** — notes textarea in place edit form with proper display in inspector (#596) -- **Place deduplication** — Google Maps list re-import skips existing places (#543) -- **File download button** — all file views now include a download button -- **Note modal** — no longer closes on outside click (#480) -- **Google Maps links** — use place name + google_place_id for accurate links (#554) -- **Packing list menu** — no longer cut off by overflow (#557) -- **Trip date change** — preserving day content when date range changes -- **PDF export** — render restaurant, event, tour, and other reservation types - ---- - -## Admin Panel Improvements - -- **Collab sub-feature toggles** — individual toggles for Chat, Notes, Polls, What's Next -- **Photo provider icons** — Immich and Synology Photos SVG brand icons in addon manager -- **Bag tracking icon** — Luggage icon for the bag tracking sub-toggle -- **Naver List Import** — now always enabled, removed from addon toggles -- **Shared PageSidebar** — admin pages use the same sidebar layout as Settings - ---- - -## Mobile Improvements - -- **Bottom nav fix** — prevent clipping of scrollable content and dialogs -- **Journey mobile** — compact add-entry button, scrollable settings dialog, iOS PWA fixes, drop hero / inline tab-bar, eager map tiles, trimmed picker labels -- **Dashboard mobile** — spotlight trip in hero, smaller badges, check icon for completed -- **Bottom nav dark mode** — consistent dark mode styling -- **Safe area support** — proper insets for iOS PWA - ---- - -## Documentation & Wiki - -- **Full GitHub Wiki** — 74 pages covering setup, deployment, addon docs, troubleshooting, API reference, and MCP -- **CI sync workflow** — `./wiki/**` in the main repo is auto-synced to the GitHub Wiki on push to `main` -- **README redesign** — Apple-style hero with animated video, feature tiles, and a screenshot gallery; hero video hosted externally so the repo stays lightweight -- **MCP compound tools doc** — `MCP.md` documents the compound / multi-step tools - ---- - -## Security - -Fifth-pass internal audit. Critical + High + Medium findings addressed in one bundled PR: - -- **JWT password_version gate** — a single `verifyJwtAndLoadUser` helper is now used by every auth surface (web session, MCP bearer, file download token, photo route, MFA policy). A password reset bumps `password_version` and invalidates every outstanding session/token for the user in one shot. -- **MFA policy via cookie** — `require_mfa` now applies to cookie-authenticated SPA sessions too (previously only the `Authorization` header was checked, so the whole SPA bypassed it). -- **OIDC id_token verification** — full JWKS-based signature verification (iss, aud, exp, nbf) plus `userinfo.sub == id_token.sub` cross-check. `kid` match is strict — no fallback to an arbitrary key. -- **OIDC invite redemption** — invite-token increment and user INSERT run in a single `db.transaction`; concurrent callbacks cannot double-redeem a single-use invite. -- **OAuth 2.1 DCR** — redirect_uri allowlist rejects `javascript:` / `data:` / `vbscript:` / `file:` / `blob:` / `about:` / `chrome:` and requires private-use schemes to be reverse-DNS (RFC 8252 §7.1). -- **OAuth audience binding** — `audience` defaults to the MCP endpoint when no `resource` parameter is sent, so new tokens always carry the correct audience claim. -- **HSTS on in production** — `NODE_ENV=production` is enough to enable HSTS (previously required `FORCE_HTTPS=true`). `includeSubDomains` stays off by default to avoid breaking apex-domain setups; opt in with `HSTS_INCLUDE_SUBDOMAINS=true`. -- **Cookie Secure behind proxies** — `trek_session` Secure flag is now derived from `req.secure` (Express's `trust proxy`-aware field), so instances behind Traefik / Caddy / Cloudflare Tunnel get Secure cookies without `FORCE_HTTPS`. -- **Share-token expiry** — public share tokens default to 90-day TTL. Existing tokens stay NULL (no expiry) so already-distributed links keep working. -- **Photo route scoping** — share tokens can only unlock photos that belong to the same trip as the token. -- **Bcrypt MFA backup codes** — backup codes are now bcrypt-hashed at rest. Legacy SHA-256 codes keep working until the user regenerates. -- **Demo-mode guards** — single `DEMO_EMAILS` registry fixes the drift where `demoUploadBlock` only matched the pre-rename `demo@nomad.app` string. -- **Filesystem safety** — `permanentDeleteFile` / `emptyTrash` / avatar cleanup use async `fs.promises.rm({ force: true })` and only drop the DB row when the on-disk unlink actually succeeded. -- **Idempotency store hardening** — key length capped at 128 chars, response bodies over 256 KiB not cached, primary key widened to `(key, user_id, method, path)` so the same key on a different endpoint does not replay an unrelated response. -- **Permissions cache invalidation** — `restoreFromZip` now drops the permissions cache after a DB swap. -- **Reset-URL source** — password-reset email URL is built from server-side `APP_URL` / `ALLOWED_ORIGINS`, never from request headers. -- **Critical DB indexes** — added `trips(user_id)`, `trips(created_at DESC)`, `photos(day_id/place_id)`, `reservations(day_id)`, `share_tokens(token)` and conditional `day_accommodations` / `notifications` indexes. - -Upstream CVEs patched: - -- **hono** 4.12.9 to 4.12.12 — directory traversal (CVE-2026-39407, CVE-2026-39408), HTTP response splitting, improper input validation (CVE-2026-39410), IP restriction bypass (CVE-2026-39409) -- **@hono/node-server** 1.19.11 to 1.19.13 — directory traversal (CVE-2026-39406) -- **nodemailer** 8.0.4 to 8.0.5 — CRLF injection - ---- - -## Bug Fixes - -- Fixed OIDC-only mode login/logout loop (#491) -- Fixed dayplan duplicate reservation display, date off-by-one, and missing day_id on edit -- Fixed booking date handling and file auth bugs -- Fixed dayplan time-based auto-sort for places and free reorder for untimed -- Fixed streaming response end on client disconnect during asset pipe -- Fixed per-day transport positions for multi-day reservations -- Fixed stale budget category reset when category no longer exists -- Fixed trip redirect to plan tab when active tab addon is disabled -- Fixed reservation price/budget field visibility when budget addon disabled -- Fixed HEIC photo rendering on non-Safari browsers -- Fixed CSP path matching for paths ending in / -- Fixed avatar URLs in notifications, admin panel, and budget -- Fixed budget member avatars lost after updating item fields -- Fixed budget table column alignment broken by `display: flex` on `` (#759) -- Fixed collab notes line break preservation (#608) -- Fixed weather archive date handling for future trips (#599) -- Fixed duplicate skeleton entries for multi-day places (#606) -- Fixed ghost Gallery / `[Trip Photos]` entries in journal timeline and public share (#764) -- Fixed journey reorder arrows rendering on skeleton suggestions (#763) -- Fixed journey map OSM tile warning (#627) -- Fixed journey gallery picker grid collapse on Safari (#717) -- Fixed content divider placement in journal entries (#624) -- Fixed local photos wrong provider label (#625) -- Fixed Synology pagination and album scroll leak (#644) -- Fixed Stadia Maps 401 on journey and atlas maps (#640) -- Fixed Nominatim User-Agent and error diagnostics -- Fixed map tooltips, journey creation, and contributor avatars -- Fixed notifications SMTP error surfacing, webhook button label, backup timestamp (#537) -- Fixed stale accommodation_id on reservation update (#522) -- Fixed hardcoded Immich in toast — now uses provider_name -- Fixed MCP safeBroadcast recursive call bug -- Fixed MCP Zod v4 `z.record()` API compatibility in transport tool schemas -- Fixed Vite module preload polyfill CSP inline script violation -- Fixed PWA offline session redirect and file download auth (#505, #541) -- Fixed `FORCE_HTTPS` redirect applying to `/api/health`, breaking container health-checks -- Fixed journey bugs reported by @roel-de-vries (#722–#736) - ---- - -## Infrastructure - -- **Prerelease workflow** — automated prerelease pipeline with major version support, version propagation, and race/orphan tag protection -- **Helm chart** — moved to `charts/trek/`, published via helm-publisher action to `gh-pages`, `appVersion` used as default image tag -- **Docker** — workflow improvements, tag management cleanup, `server/data/airports.json` properly included in image after assets refactor -- **CI** — contributor workflow automation, `npm audit` removal from install steps, manual trigger for prerelease, client test job added alongside server tests with split coverage artifacts - ---- - -## Test Coverage - -- **Backend** — expanded to ~87% coverage with comprehensive tests for OAuth, MCP tools, addon gating, services, and session management -- **Frontend** — expanded to ~82% coverage with tests for dashboard, planner, settings, admin panels, and component interactions -- **Journey** — 89.5% new code coverage - ---- - -## Contributors - -Thanks to everyone who contributed to this release: - -- @mauriceboe -- @jubnl -- @gravitysc -- @luojiyin1987 -- @marco783 -- @isaiastavares -- @tiquis0290 -- @xenocent -- @gfrcsd -- @roel-de-vries - ---- - -## Stats - -| Metric | Value | -|--------|-------| -| Commits | 500+ | -| Merged PRs | 130+ | -| Files changed | 700+ | -| Lines added | 120,000+ | -| Contributors | 12+ | - ---- - -## Upgrading - -```bash -docker pull mauriceboe/trek:3.0.0 -docker compose up -d -``` - -Migrations run automatically on startup. No manual steps required. - -**Checklist:** -1. Update your Immich API key to include `asset.upload` (optional, only needed for Journey upload sync) -2. If using `OIDC_ONLY`, migrate to `DISABLE_LOCAL_LOGIN` + `DISABLE_LOCAL_REGISTRATION` -3. Enable the Journey addon in Settings > Addons to start using the travel journal -4. Try the Mapbox GL renderer in Settings > Map if you want 3D terrain and a proper globe view (requires a free Mapbox access token) diff --git a/.issue-scratch/release-draft.md b/.issue-scratch/release-draft.md deleted file mode 100644 index 9595d730..00000000 --- a/.issue-scratch/release-draft.md +++ /dev/null @@ -1,405 +0,0 @@ - -Release 2 9 0 (2) - -# TREK 3.0.0 - -> **This is the biggest TREK release to date.** Journey turns your trips into rich travel journals. MCP gets full OAuth 2.1 security. The dashboard has been redesigned for mobile-first. And every corner of the app now speaks 15 languages natively. - ---- - -## Breaking Changes - -### Photos moved from Trip Planner to Journey - -In previous versions, Immich and Synology Photos were integrated directly into the Trip Planner via a "Photos" tab. **This tab has been removed.** Photos are now part of the new **Journey addon**, which is purpose-built for documenting your travels with stories, photos, and maps. - -**What this means for you:** -- **No photos are lost.** The previous integration was read-only — TREK never uploaded to or deleted from your Immich/Synology library. Your photos remain untouched in your photo provider. -- **Previously linked trip photos are no longer displayed in the Trip Planner.** To view and organize your travel photos, enable the Journey addon (Settings > Addons) and create a Journey linked to your trip. -- **Journey brings a much richer photo experience:** upload photos directly to TREK, browse and import from Immich/Synology with duplicate detection, reorder photos, view EXIF metadata, and export everything as a PDF photo book. - -### New Immich API Key Permissions Required - -Journey introduces **photo upload sync** — when you upload a photo to a Journey entry, TREK can optionally sync it to your Immich library. This requires an additional Immich API permission that was not needed before. - -**Previous versions required:** -| Permission | Used for | -|---|---| -| `user.read` | Connection test | -| `asset.read` | Browse photos by date, search | -| `asset.view` | Stream thumbnails | -| `asset.download` | Stream originals | -| `album.read` | List and browse albums | -| `timeline.read` | Browse timeline buckets | - -**New in 3.0.0 — additionally required:** -| Permission | Used for | -|---|---| -| `asset.upload` | Sync uploaded Journey photos to Immich | - -> **How to update your Immich API key:** Go to your Immich instance > User Settings > API Keys. Edit your existing TREK key (or create a new one) and ensure `asset.upload` is enabled in addition to the existing permissions. If you don't plan to use Journey's upload sync, the old key will continue to work — the upload simply won't sync to Immich. - -**No changes needed for Synology Photos** — Synology uses session-based authentication which inherits the user's full permissions. - -### OIDC_ONLY deprecated - -The `OIDC_ONLY` environment variable is deprecated. Replace with `DISABLE_LOCAL_LOGIN=true` + `DISABLE_LOCAL_REGISTRATION=true` for equivalent behavior. The old variable still works but will be removed in a future release. - ---- -Release 2 9 0 (3) - -## Journey Addon — Travel Journal - -The headline feature of 3.0.0. Journey is a new global addon that transforms your trips into magazine-style travel stories. - -### Core -- **5-table schema** — journeys, entries, photos, trips, contributors with full relational integrity -- **Trip-to-Journey sync engine** — link one or more trips to a journey; skeleton entries and photos are synced automatically -- **Timeline, Gallery, and Map views** — browse entries chronologically, as a photo grid, or on an interactive map with SVG pin markers -- **Entry editor** — markdown toolbar, custom date picker, location search (Nominatim/Google Maps), mood (Amazing/Good/Neutral/Rough), weather (Sunny to Snowy), and Pros & Cons sections - -### Photos -- **Immich & Synology browser** — browse by trip dates, custom range, or album with duplicate detection -- **Photo upload** — direct upload with drag-and-drop, reorder (Make 1st), and delete -- **EXIF metadata** — displayed in lightbox for Immich photos -- **Thumbnail to original fallback** — seamless resolution upgrade everywhere -- **HEIC rendering fix** — serve fullsize thumbnail for original to fix HEIC rendering on non-Safari browsers -- **Contributor photo access** — invited contributors can view all journey photos even without their own Immich/Synology connection (owner credentials are used for the proxy) - -### Sharing & Export -- **Public share links** — token-based access with language picker, no login required -- **Public photo proxy** — validates share token instead of auth for photo streaming -- **PDF photo book export** — Polarsteps-inspired layout with cover, day chapters, photo grids, and stories - -### Collaboration -- **Contributors** — invite users as editors or viewers -- **Trip linking/unlinking** — manage synced trips from Journey Settings and Desktop Sidebar -- **Cover image** — upload or pick from journey photos - -### Frontend -- **JourneyPage** — frontpage with hero card, active journey stats, trip suggestions ("Trip just ended — turn it into a Journey") -- **JourneyDetailPage** — full timeline/gallery/map with inline entry editing -- **JourneyPublicPage** — public share view with language picker and read-only timeline - ---- - -## MCP: OAuth 2.1 & Granular Scopes - -MCP authentication has been completely rebuilt around the OAuth 2.1 specification. - -- **OAuth 2.1 authorization server** — full PKCE flow with authorization codes, access tokens, refresh tokens, and token rotation with replay detection -- **Granular scopes** — 24 scopes across 11 groups (trips, places, atlas, packing, todos, budget, reservations, collab, notifications, vacay, geo/weather) with per-scope read/write/delete control -- **Dynamic Client Registration (DCR)** — RFC 7591 endpoint at POST /oauth/register for browser-initiated and public clients -- **Consent screen** — user-facing scope selection with grouped permission display -- **Admin panel** — OAuth sessions management in MCP Access panel with collapsible scope lists -- **Per-client rate limiting** — configurable rate limits per OAuth client -- **Addon gating** — MCP tools are only registered when their corresponding addon is enabled -- **Static token deprecation** — existing MCP tokens still work but surface deprecation notices; migration path to OAuth is documented -- **Security hardening** — Critical + High + Medium findings addressed (token storage, PKCE enforcement, scope validation) - ---- - -## Dashboard Redesign - -The dashboard has been rebuilt with a mobile-first design language. - -### Mobile -- **Greeting header** — "Good morning, {username}" with notification bell and avatar -- **Spotlight hero card** — the next upcoming or ongoing trip as a full-width hero with cover image, progress bar (for live trips), stats grid, and frosted-glass action buttons -- **Quick Actions** — New Trip, Currency Converter, Timezone as icon cards -- **Trip cards** — cover image with title overlay, status badge (In X days / Starts today / Ongoing / Completed), bottom stats (starts, duration, places, buddies) - -### Desktop -- **Unified card design** — desktop grid cards now match the mobile card style (cover + title overlay + stats) -- **Hero card** — SpotlightCard with progress bar for ongoing trips, countdown for upcoming, stats grid -- **Hover actions** — edit/copy/archive/delete buttons appear on hover as frosted-glass icons -- **Status badges** — CircleCheck icon for completed trips, Clock for upcoming, pulsing dot for ongoing - -### Both -- **BottomNav profile sheet** — slide-up sheet with user info, settings, admin, and logout -- **Dark mode** — full dark mode support across all new components - ---- - -## PWA Offline Mode - -TREK now works offline as a Progressive Web App with full data synchronization. - -- **IndexedDB (Dexie) storage** — trips, places, assignments, categories, tags, accommodations, reservations, budget items, packing items, files, and trip members cached locally -- **Offline mutation queue** — changes made offline are queued with monotonic timestamps and replayed on reconnect (FIFO) -- **Offline dashboard** — trip list loaded from Dexie when network is unavailable -- **Offline trip planner** — full planner functionality with cached data -- **Repo layer** — all data access routed through repository layer that falls back to offline storage -- **Offline banner** — visible indicator with safe-area-inset support for iOS PWA -- **Idempotency keys** — prevents duplicate mutations on replay (Migration 100) - ---- - -## Reservations Redesign - -The reservations panel has been completely redesigned with a modern, unified layout. - -- **Unified toolbar** — title, type filter pills with count badges, and add button in one row with muted background -- **Type filters** — multi-select filter buttons (Flight, Hotel, Restaurant, etc.) with per-type count badges, persisted in sessionStorage -- **Responsive grid** — auto-fill layout with max 3 columns that fills full width -- **Card redesign** — status + type badge in header, labeled fields in rounded boxes, hover shadow -- **Check-in time ranges** — hotel bookings now support a check-in window (e.g. "15:00 -- 22:00") with a new check_in_end field (#366) -- **Mobile responsive** — filters hidden on mobile, booking code on separate row, weekday hidden in dates, reduced padding - ---- - -## Collab Sub-Feature Toggles - -Individual collab sections can now be toggled on/off from the admin addons page (#604). - -- **Admin UI** — sub-toggles for Chat, Notes, Polls, and What's Next under the Collab addon, with icons matching the collab panel tabs -- **Dynamic desktop layout** — Chat always stays at fixed 380px width; remaining active panels share space equally -- **Mobile** — disabled tabs are hidden from the tab bar -- **API** — GET/PUT /admin/collab-features endpoints stored in app_settings - ---- - -## Place Import: KMZ/KML & Naver Maps - -Two new ways to import places into your trips. - -### KMZ/KML Import -- **Unified file import modal** — drag-and-drop or file picker for KML, KMZ, and GPX files -- **KMZ unpacking** — extracts KML from ZIP archive with 50MB decompressed size limit -- **Folder-to-category mapping** — KML folders are automatically matched to TREK categories -- **Place deduplication** — skips places that already exist in the trip (by name + coordinates) - -### Naver Maps List Import -- **Always enabled** — no longer requires addon toggle, available alongside Google Maps list import -- **Shortlink resolution** — resolves naver.me shortlinks to full list URLs -- **Pagination support** — handles large Naver Maps lists with automatic pagination - ---- - -## Search Autocomplete - -- **Real-time suggestions** — autocomplete suggestions appear as you type in the place search field -- **Google Places API** — primary autocomplete provider with location bias -- **Nominatim fallback** — free fallback when Google API key is not configured -- **Bounding box bias** — search results biased to the current map viewport - ---- - -## ntfy Notification Channel - -- **ntfy as first-class channel** — push notifications via any ntfy server (self-hosted or ntfy.sh) -- **Admin configuration** — server URL and topic configuration in admin panel with clear token button -- **Per-user opt-in** — users can enable/disable ntfy in their notification preferences -- **Full i18n** — ntfy strings translated in all 15 languages - ---- - -## Login & Language - -- **Language dropdown on login page** — users can select their preferred language before logging in -- **Browser auto-detection** — language is automatically detected from browser settings on first visit -- **DEFAULT_LANGUAGE env var** — configurable default language for the instance, documented across all deployment configs (Docker, Helm, Synology) - ---- - -## Granular Auth Toggles - -- **OIDC_ONLY replaced** — split into DISABLE_LOCAL_LOGIN, DISABLE_LOCAL_REGISTRATION, and DISABLE_PASSWORD_CHANGE for fine-grained control over authentication methods -- Allows mixed setups (e.g., OIDC + local admin account, or OIDC-only with no local registration) - ---- - -## Synology Photos: OTP, SSL Skip & Session Management - -- **OTP support** — one-time password field for 2FA-enabled Synology NAS -- **Skip SSL verification** — toggle for self-signed certificates -- **Device ID persistence** — prevents repeated 2FA prompts -- **Session-cleared notification** — routed through unified notification system -- **Provider URL hint** — contextual help text for Synology URL format - ---- - -## Atlas Improvements - -- **Scoped region matching** — region name matching is now scoped by country to prevent cross-country false matches -- **Expanded country lookup tables** — more countries and regions recognized correctly, including A3 fallback for invalid ISO_A2 codes -- **Nominatim rate limiting** — shared throttle prevents 429 errors, background region fill, fetch timeout -- **Stadia Maps fix** — resolved 401 errors on journey and atlas maps - ---- - -## i18n: Full 15-Language Coverage - -- **Indonesian added** — complete translation with full parity to English, bringing the total to 15 languages (EN, DE, FR, ES, IT, NL, PL, RU, ZH, ZH-TW, BR, CS, HU, AR, ID) -- **Comprehensive audit** — every key translated natively, no English fallbacks -- **OAuth scope labels** — all 24 scopes have localized names and descriptions -- **Journey addon** — complete coverage for all journal, editor, sharing, and PDF export strings -- **Ellipsis standardization** — all ellipsis characters normalized to three dots (...) - ---- - -## Vacay Improvements - -- **Trip indicator dots** — small blue dots on calendar days where trips are scheduled -- **Configurable week start** — choose Monday or Sunday as first day of the week (#224) -- **Holiday overlap** — vacations can now be placed on public holidays -- **Today marker** — visual indicator for the current day in the calendar -- **Bottom padding fix** — toolbar no longer overlaps the last row (#533) - ---- - -## iCal Export Improvements - -- **Day activities and notes** — iCal export now includes daily activities and notes, not just the trip dates (#375) - ---- - -## Budget Improvements - -- **Drag-and-drop reorder** — budget categories and individual items can be reordered via drag-and-drop (#479) -- **Category legend redesign** — prevents overflow on small screens (#564) -- **Comma decimal support** — pasting numbers with comma separators works correctly - ---- - -## Planner & UX Improvements - -- **Collapsible day detail panel** — day detail panel can be collapsed/expanded in the planner -- **Uncategorized filter** — "No Category" option in category dropdown to find places without a category (#607) -- **Map multi-category filter** — filter syncs with map view for uncategorized places -- **Unplanned filter sync** — unplanned filter properly syncs with map markers (#385) -- **Place notes** — notes textarea in place edit form with proper display in inspector (#596) -- **Place deduplication** — Google Maps list re-import skips existing places (#543) -- **File download button** — all file views now include a download button -- **Note modal** — no longer closes on outside click (#480) -- **Google Maps links** — use place name + google_place_id for accurate links (#554) -- **Packing list menu** — no longer cut off by overflow (#557) -- **Trip date change** — preserving day content when date range changes -- **PDF export** — render restaurant, event, tour, and other reservation types - ---- - -## Admin Panel Improvements - -- **Collab sub-feature toggles** — individual toggles for Chat, Notes, Polls, What's Next -- **Photo provider icons** — Immich and Synology Photos SVG brand icons in addon manager -- **Bag tracking icon** — Luggage icon for the bag tracking sub-toggle -- **Naver List Import** — now always enabled, removed from addon toggles - ---- - -## Mobile Improvements - -- **Bottom nav fix** — prevent clipping of scrollable content and dialogs -- **Journey mobile** — compact add-entry button, scrollable settings dialog, iOS PWA fixes -- **Dashboard mobile** — spotlight trip in hero, smaller badges, check icon for completed -- **Bottom nav dark mode** — consistent dark mode styling -- **Safe area support** — proper insets for iOS PWA - ---- - -## Test Coverage - -- **Backend** — expanded to ~87% coverage with comprehensive tests for OAuth, MCP tools, addon gating, services, and session management -- **Frontend** — expanded to ~82% coverage with tests for dashboard, planner, settings, admin panels, and component interactions -- **Journey** — 89.5% new code coverage -- **CI** — client test job added alongside server tests with split coverage artifacts - ---- - -## Bug Fixes - -- Fixed OIDC-only mode login/logout loop (#491) -- Fixed dayplan duplicate reservation display, date off-by-one, and missing day_id on edit -- Fixed booking date handling and file auth bugs -- Fixed dayplan time-based auto-sort for places and free reorder for untimed -- Fixed streaming response end on client disconnect during asset pipe -- Fixed per-day transport positions for multi-day reservations -- Fixed stale budget category reset when category no longer exists -- Fixed trip redirect to plan tab when active tab addon is disabled -- Fixed reservation price/budget field visibility when budget addon disabled -- Fixed HEIC photo rendering on non-Safari browsers -- Fixed CSP path matching for paths ending in / -- Fixed avatar URLs in notifications, admin panel, and budget -- Fixed budget member avatars lost after updating item fields -- Fixed collab notes line break preservation (#608) -- Fixed weather archive date handling for future trips (#599) -- Fixed duplicate skeleton entries for multi-day places (#606) -- Fixed ghost Gallery entries in journal timeline and public share -- Fixed journey map OSM tile warning (#627) -- Fixed content divider placement in journal entries (#624) -- Fixed local photos wrong provider label (#625) -- Fixed Synology pagination and album scroll leak (#644) -- Fixed Stadia Maps 401 on journey and atlas maps (#640) -- Fixed Nominatim User-Agent and error diagnostics -- Fixed map tooltips, journey creation, and contributor avatars -- Fixed notifications SMTP error surfacing, webhook button label, backup timestamp (#537) -- Fixed stale accommodation_id on reservation update (#522) -- Fixed hardcoded Immich in toast — now uses provider_name -- Fixed MCP safeBroadcast recursive call bug -- Fixed Vite module preload polyfill CSP inline script violation -- Fixed PWA offline session redirect and file download auth (#505, #541) - ---- - -## Security - -- **hono** 4.12.9 to 4.12.12 — fixes directory traversal (CVE-2026-39407, CVE-2026-39408), HTTP response splitting, improper input validation (CVE-2026-39410), and IP restriction bypass (CVE-2026-39409) -- **@hono/node-server** 1.19.11 to 1.19.13 — fixes directory traversal (CVE-2026-39406) -- **nodemailer** 8.0.4 to 8.0.5 — fixes CRLF injection -- **OAuth 2.1 hardening** — token storage, PKCE enforcement, scope intersection validation -- **Google Maps regex** — replaced too-permissive regex with safer utility function - ---- - -## Infrastructure - -- **Prerelease workflow** — automated prerelease pipeline with major version support, version propagation, and race/orphan tag protection -- **Helm chart** — moved to charts/trek/, published via helm-publisher action to gh-pages, appVersion used as default image tag -- **Docker** — workflow improvements, tag management cleanup -- **CI** — contributor workflow automation, npm audit removal from install steps, manual trigger for prerelease - ---- - -## Contributors - -Thanks to everyone who contributed to this release: - -- @mauriceboe -- @jubnl -- @gravitysc -- @luojiyin1987 -- @marco783 -- @isaiastavares -- @tiquis0290 -- @xenocent -- @gfrcsd - ---- - -## Stats - -| Metric | Value | -|--------|-------| -| Commits | 280+ | -| Merged PRs | 49 | -| Files changed | 500+ | -| Lines added | 108,000+ | -| Contributors | 12 | - ---- - -## Upgrading - -```bash -docker pull mauriceboe/trek:3.0.0 -docker compose up -d -``` - -Migrations run automatically on startup. No manual steps required. - -**Checklist:** -1. Update your Immich API key to include `asset.upload` (optional, only needed for Journey upload sync) -2. If using `OIDC_ONLY`, migrate to `DISABLE_LOCAL_LOGIN` + `DISABLE_LOCAL_REGISTRATION` -3. Enable the Journey addon in Settings > Addons to start using the travel journal - diff --git a/client/src/api/client.ts b/client/src/api/client.ts index 106a7f36..248792f2 100644 --- a/client/src/api/client.ts +++ b/client/src/api/client.ts @@ -2,31 +2,33 @@ import axios, { AxiosInstance } from 'axios' import type { WeatherResult } from '@trek/shared' import { getSocketId } from './websocket' import { isReachable, probeNow } from '../sync/connectivity' -import en from '../i18n/translations/en' -import br from '../i18n/translations/br' -import de from '../i18n/translations/de' -import es from '../i18n/translations/es' -import fr from '../i18n/translations/fr' -import it from '../i18n/translations/it' -import nl from '../i18n/translations/nl' -import pl from '../i18n/translations/pl' -import cs from '../i18n/translations/cs' -import hu from '../i18n/translations/hu' -import ru from '../i18n/translations/ru' -import zh from '../i18n/translations/zh' -import zhTw from '../i18n/translations/zhTw' -import ar from '../i18n/translations/ar' - -const rateLimitTranslations: Record> = { - en, br, de, es, fr, it, nl, pl, cs, hu, ru, zh, 'zh-TW': zhTw, ar, +const RATE_LIMIT_MESSAGES: Record = { + en: 'Too many attempts. Please try again later.', + de: 'Zu viele Versuche. Bitte versuchen Sie es später erneut.', + es: 'Demasiados intentos. Inténtelo de nuevo más tarde.', + fr: 'Trop de tentatives. Veuillez réessayer plus tard.', + hu: 'Túl sok próbálkozás. Kérjük, próbálja újra később.', + nl: 'Te veel pogingen. Probeer het later opnieuw.', + br: 'Muitas tentativas. Tente novamente mais tarde.', + cs: 'Příliš mnoho pokusů. Zkuste to prosím znovu.', + pl: 'Zbyt wiele prób. Spróbuj ponownie później.', + ru: 'Слишком много попыток. Попробуйте позже.', + zh: '尝试次数过多,请稍后再试。', + 'zh-TW': '嘗試次數過多,請稍後再試。', + it: 'Troppi tentativi. Riprova più tardi.', + tr: 'Çok fazla deneme. Lütfen daha sonra tekrar deneyin.', + ar: 'محاولات كثيرة جدًا. يرجى المحاولة لاحقًا.', + id: 'Terlalu banyak percobaan. Coba lagi nanti.', + ja: '試行回数が多すぎます。時間をおいて再度お試しください。', + ko: '시도 횟수가 너무 많습니다. 잠시 후 다시 시도해 주세요.', + uk: 'Занадто багато спроб. Спробуйте пізніше.', } function translateRateLimit(): string { - const fallback = 'Too many attempts. Please try again later.' + const fallback = RATE_LIMIT_MESSAGES['en']! try { const lang = localStorage.getItem('app_language') || 'en' - const table = rateLimitTranslations[lang] || rateLimitTranslations.en - return (table['common.tooManyAttempts'] as string) || (rateLimitTranslations.en['common.tooManyAttempts'] as string) || fallback + return RATE_LIMIT_MESSAGES[lang] ?? fallback } catch { return fallback } diff --git a/client/src/components/Layout/BottomNav.test.tsx b/client/src/components/Layout/BottomNav.test.tsx index 9244efc9..bda3c40a 100644 --- a/client/src/components/Layout/BottomNav.test.tsx +++ b/client/src/components/Layout/BottomNav.test.tsx @@ -102,19 +102,19 @@ describe('BottomNav', () => { expect(screen.queryByText('testuser')).not.toBeInTheDocument(); }); - it('FE-COMP-BOTTOMNAV-010: Trips label translates when language is fr', () => { + it('FE-COMP-BOTTOMNAV-010: Trips label translates when language is fr', async () => { seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); render(); - expect(screen.getByText('Mes voyages')).toBeInTheDocument(); + expect(await screen.findByText('Mes voyages')).toBeInTheDocument(); }); - it('FE-COMP-BOTTOMNAV-011: Profile label translates when language is fr', () => { + it('FE-COMP-BOTTOMNAV-011: Profile label translates when language is fr', async () => { seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); render(); - expect(screen.getByText('Profil')).toBeInTheDocument(); + expect(await screen.findByText('Profil')).toBeInTheDocument(); }); - it('FE-COMP-BOTTOMNAV-012: addon labels translate when language is fr', () => { + it('FE-COMP-BOTTOMNAV-012: addon labels translate when language is fr', async () => { seedStore(useSettingsStore, { settings: buildSettings({ language: 'fr' }) }); seedStore(useAddonStore, { addons: [ @@ -124,9 +124,9 @@ describe('BottomNav', () => { ], }); render(); - expect(screen.getByText('Vacances')).toBeInTheDocument(); - expect(screen.getByText('Atlas')).toBeInTheDocument(); - expect(screen.getByText('Journal de voyage')).toBeInTheDocument(); + expect(await screen.findByText('Vacances')).toBeInTheDocument(); + expect(await screen.findByText('Atlas')).toBeInTheDocument(); + expect(await screen.findByText('Journal de voyage')).toBeInTheDocument(); }); it('FE-COMP-BOTTOMNAV-013: unknown addon id is not rendered', () => { diff --git a/client/src/i18n/TranslationContext.tsx b/client/src/i18n/TranslationContext.tsx index e0b61d1b..699ddfaa 100644 --- a/client/src/i18n/TranslationContext.tsx +++ b/client/src/i18n/TranslationContext.tsx @@ -1,56 +1,47 @@ -import React, { createContext, useContext, useEffect, useMemo, ReactNode } from 'react' +import React, { createContext, useContext, useEffect, useMemo, useState, ReactNode } from 'react' import { useSettingsStore } from '../store/settingsStore' -import de from './translations/de' -import en from './translations/en' -import es from './translations/es' -import fr from './translations/fr' -import hu from './translations/hu' -import it from './translations/it' -import tr from './translations/tr' -import ru from './translations/ru' -import zh from './translations/zh' -import zhTw from './translations/zhTw' -import nl from './translations/nl' -import id from './translations/id' -import ar from './translations/ar' -import br from './translations/br' -import cs from './translations/cs' -import pl from './translations/pl' -import ja from './translations/ja' -import ko from './translations/ko' -import uk from './translations/uk' -import { SUPPORTED_LANGUAGES, SupportedLanguageCode } from './supportedLanguages' +import en from '@trek/shared/i18n/en' +import type { SupportedLanguageCode } from '@trek/shared' +import { + SUPPORTED_LANGUAGES, + getLocaleForLanguage, + getIntlLanguage, + isRtlLanguage, +} from '@trek/shared' +import type { TranslationStrings } from '@trek/shared/i18n' export { SUPPORTED_LANGUAGES } -type TranslationStrings = Record - -// Keyed by SupportedLanguageCode so TypeScript enforces all languages have a translation. -const translations: Record = { - de, en, es, fr, hu, it, tr, ru, zh, 'zh-TW': zhTw, nl, id, ar, br, cs, pl, ja, ko, uk, +// One explicit dynamic import per locale — Vite code-splits a separate chunk per locale. +// Only the active locale is fetched; en is always available synchronously as the fallback. +const localeLoaders: Record Promise<{ default: TranslationStrings }>> = { + en: () => Promise.resolve({ default: en }), + de: () => import('@trek/shared/i18n/de'), + es: () => import('@trek/shared/i18n/es'), + fr: () => import('@trek/shared/i18n/fr'), + hu: () => import('@trek/shared/i18n/hu'), + it: () => import('@trek/shared/i18n/it'), + tr: () => import('@trek/shared/i18n/tr'), + ru: () => import('@trek/shared/i18n/ru'), + zh: () => import('@trek/shared/i18n/zh'), + 'zh-TW': () => import('@trek/shared/i18n/zh-TW'), + nl: () => import('@trek/shared/i18n/nl'), + id: () => import('@trek/shared/i18n/id'), + ar: () => import('@trek/shared/i18n/ar'), + br: () => import('@trek/shared/i18n/br'), + cs: () => import('@trek/shared/i18n/cs'), + pl: () => import('@trek/shared/i18n/pl'), + ja: () => import('@trek/shared/i18n/ja'), + ko: () => import('@trek/shared/i18n/ko'), + uk: () => import('@trek/shared/i18n/uk'), } -// Derived from SUPPORTED_LANGUAGES — add new languages there, not here. -const LOCALES: Record = Object.fromEntries( - SUPPORTED_LANGUAGES.map(l => [l.value, l.locale]) -) -const RTL_LANGUAGES = new Set(['ar']) +// Re-export pure helpers that live in shared so downstream consumers can import them +// through this module without changing their import path. +export { getLocaleForLanguage, getIntlLanguage, isRtlLanguage } -export function getLocaleForLanguage(language: string): string { - return LOCALES[language] || LOCALES.en -} - -export function getIntlLanguage(language: string): string { - if (language === 'br') return 'pt-BR' - return ['de', 'es', 'fr', 'hu', 'it', 'tr', 'ru', 'zh', 'zh-TW', 'nl', 'ar', 'cs', 'pl', 'id', 'ja', 'ko', 'uk'].includes(language) ? language : 'en' -} - -export function isRtlLanguage(language: string): boolean { - return RTL_LANGUAGES.has(language) -} - -// Detects the user's preferred language from the browser/OS settings and maps -// it to one of the supported language codes. Returns null if no match is found. +// Detects the user's preferred language from browser/OS settings. +// Returns null if no supported language matches. export function detectBrowserLanguage(): string | null { if (typeof navigator === 'undefined') return null const browserLangs = navigator.languages?.length @@ -59,17 +50,14 @@ export function detectBrowserLanguage(): string | null { const supported = SUPPORTED_LANGUAGES.map(l => l.value) for (const lang of browserLangs) { - // Exact match (e.g. 'de', 'zh-TW') — case-insensitive const exactMatch = supported.find(s => s.toLowerCase() === lang.toLowerCase()) if (exactMatch) return exactMatch - // pt-BR has no exact match (our code is 'br', not 'pt-BR'), so map it explicitly. - // pt-PT and bare 'pt' are NOT mapped — they fall through to null and let the - // server default or 'en' fallback apply instead. + // pt-BR has no exact match (our code is 'br'), so map it explicitly. + // pt-PT and bare 'pt' are NOT mapped — they fall through to null. if (lang.toLowerCase() === 'pt-br') return 'br' - // Prefix match (e.g. 'de-AT' → 'de', 'zh-CN' → 'zh') — case-insensitive - const prefix = lang.split('-')[0].toLowerCase() + const prefix = lang.split('-')[0]?.toLowerCase() const prefixMatch = supported.find(s => s.toLowerCase() === prefix) if (prefixMatch) return prefixMatch } @@ -91,18 +79,27 @@ interface TranslationProviderProps { export function TranslationProvider({ children }: TranslationProviderProps) { const language = useSettingsStore((s) => s.settings.language) || 'en' + const [strings, setStrings] = useState(en) useEffect(() => { document.documentElement.lang = language document.documentElement.dir = isRtlLanguage(language) ? 'rtl' : 'ltr' }, [language]) - const value = useMemo((): TranslationContextValue => { - const strings = translations[language] || translations.en - const fallback = translations.en + useEffect(() => { + const loader = localeLoaders[language as SupportedLanguageCode] + if (!loader) return + let cancelled = false + loader().then(mod => { + if (!cancelled) setStrings(mod.default) + }) + return () => { cancelled = true } + }, [language]) + + const value = useMemo((): TranslationContextValue => { function t(key: string, params?: Record): string { - let val: string = (strings[key] ?? fallback[key] ?? key) as string + let val: string = (strings[key] ?? en[key] ?? key) as string if (params) { Object.entries(params).forEach(([k, v]) => { val = val.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v)) @@ -112,7 +109,7 @@ export function TranslationProvider({ children }: TranslationProviderProps) { } return { t, language, locale: getLocaleForLanguage(language) } - }, [language]) + }, [strings, language]) return {children} } diff --git a/client/src/i18n/supportedLanguages.ts b/client/src/i18n/supportedLanguages.ts index e85747ab..d8f105d8 100644 --- a/client/src/i18n/supportedLanguages.ts +++ b/client/src/i18n/supportedLanguages.ts @@ -1,25 +1,4 @@ -export const SUPPORTED_LANGUAGES = [ - { value: 'de', label: 'Deutsch', locale: 'de-DE' }, - { value: 'en', label: 'English', locale: 'en-US' }, - { value: 'es', label: 'Español', locale: 'es-ES' }, - { value: 'fr', label: 'Français', locale: 'fr-FR' }, - { value: 'hu', label: 'Magyar', locale: 'hu-HU' }, - { value: 'nl', label: 'Nederlands', locale: 'nl-NL' }, - { value: 'br', label: 'Português (Brasil)', locale: 'pt-BR' }, - { value: 'cs', label: 'Česky', locale: 'cs-CZ' }, - { value: 'pl', label: 'Polski', locale: 'pl-PL' }, - { value: 'ru', label: 'Русский', locale: 'ru-RU' }, - { value: 'zh', label: '简体中文', locale: 'zh-CN' }, - { value: 'zh-TW', label: '繁體中文', locale: 'zh-TW' }, - { value: 'it', label: 'Italiano', locale: 'it-IT' }, - { value: 'tr', label: 'Türkçe', locale: 'tr-TR' }, - { value: 'ar', label: 'العربية', locale: 'ar-SA' }, - { value: 'id', label: 'Bahasa Indonesia', locale: 'id-ID' }, - { value: 'ja', label: '日本語', locale: 'ja-JP' }, - { value: 'ko', label: '한국어', locale: 'ko-KR' }, - { value: 'uk', label: 'Українська', locale: 'uk-UA' }, -] as const - -export type SupportedLanguageCode = typeof SUPPORTED_LANGUAGES[number]['value'] - -export const SUPPORTED_LANGUAGE_CODES: string[] = SUPPORTED_LANGUAGES.map(l => l.value) +// Canonical language registry now lives in @trek/shared. Re-exported here so +// existing imports of './supportedLanguages' continue to work unchanged. +export { SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES } from '@trek/shared' +export type { SupportedLanguageCode } from '@trek/shared' diff --git a/client/src/i18n/translations/ar.ts b/client/src/i18n/translations/ar.ts deleted file mode 100644 index 5e81722e..00000000 --- a/client/src/i18n/translations/ar.ts +++ /dev/null @@ -1,2164 +0,0 @@ -import en from './en' - -const ar: Record = { - ...en, - - // Common - 'common.save': 'حفظ', - 'common.showMore': 'عرض المزيد', - 'common.showLess': 'عرض أقل', - 'common.cancel': 'إلغاء', - 'common.clear': 'مسح', - 'common.delete': 'حذف', - 'common.edit': 'تعديل', - 'common.add': 'إضافة', - 'common.loading': 'جارٍ التحميل...', - 'common.import': 'استيراد', - 'common.select': 'تحديد', - 'common.selectAll': 'تحديد الكل', - 'common.deselectAll': 'إلغاء تحديد الكل', - 'common.error': 'خطأ', - 'common.unknownError': 'خطأ غير معروف', - 'common.tooManyAttempts': 'محاولات كثيرة جدًا. يرجى المحاولة لاحقًا.', - 'common.back': 'رجوع', - 'common.all': 'الكل', - 'common.close': 'إغلاق', - 'common.open': 'فتح', - 'common.upload': 'رفع', - 'common.search': 'بحث', - 'common.confirm': 'تأكيد', - 'common.ok': 'حسنًا', - 'common.yes': 'نعم', - 'common.no': 'لا', - 'common.or': 'أو', - 'common.none': 'لا شيء', - 'common.date': 'التاريخ', - 'common.rename': 'إعادة تسمية', - 'common.discardChanges': 'تجاهل التغييرات', - 'common.discard': 'تجاهل', - 'common.name': 'الاسم', - 'common.email': 'البريد الإلكتروني', - 'common.password': 'كلمة المرور', - 'common.saving': 'جارٍ الحفظ...', - 'common.saved': 'تم الحفظ', - 'common.expand': 'توسيع', - 'common.collapse': 'طي', - 'trips.memberRemoved': '{username} تمت إزالته', - 'trips.memberRemoveError': 'فشل في الإزالة', - 'trips.memberAdded': '{username} تمت إضافته', - 'trips.memberAddError': 'فشل في الإضافة', - 'trips.reminder': 'تذكير', - 'trips.reminderNone': 'بدون', - 'trips.reminderDay': 'يوم', - 'trips.reminderDays': 'أيام', - 'trips.reminderCustom': 'مخصص', - 'trips.reminderDaysBefore': 'أيام قبل المغادرة', - 'trips.reminderDisabledHint': 'تذكيرات الرحلة معطلة. قم بتفعيلها من الإدارة > الإعدادات > الإشعارات.', - 'common.update': 'تحديث', - 'common.change': 'تغيير', - 'common.uploading': 'جارٍ الرفع...', - 'common.backToPlanning': 'العودة إلى التخطيط', - 'common.reset': 'إعادة تعيين', - - // Navbar - 'nav.trip': 'الرحلة', - 'nav.share': 'مشاركة', - 'nav.settings': 'الإعدادات', - 'nav.admin': 'الإدارة', - 'nav.logout': 'تسجيل الخروج', - 'nav.lightMode': 'الوضع الفاتح', - 'nav.darkMode': 'الوضع الداكن', - 'nav.autoMode': 'الوضع التلقائي', - 'nav.administrator': 'المسؤول', - 'nav.myTrips': 'رحلاتي', - - // Dashboard - 'dashboard.title': 'رحلاتي', - 'dashboard.subtitle.loading': 'جارٍ تحميل الرحلات...', - 'dashboard.subtitle.trips': '{count} رحلة ({archived} مؤرشفة)', - 'dashboard.subtitle.empty': 'ابدأ رحلتك الأولى', - 'dashboard.subtitle.activeOne': '{count} رحلة نشطة', - 'dashboard.subtitle.activeMany': '{count} رحلات نشطة', - 'dashboard.subtitle.archivedSuffix': ' · {count} مؤرشفة', - 'dashboard.newTrip': 'رحلة جديدة', - 'dashboard.gridView': 'عرض شبكي', - 'dashboard.listView': 'عرض قائمة', - 'dashboard.currency': 'العملة', - 'dashboard.timezone': 'المناطق الزمنية', - 'dashboard.localTime': 'المحلي', - 'dashboard.timezoneCustomTitle': 'منطقة زمنية مخصصة', - 'dashboard.timezoneCustomLabelPlaceholder': 'الاسم (اختياري)', - 'dashboard.timezoneCustomTzPlaceholder': 'مثال: Asia/Riyadh', - 'dashboard.timezoneCustomAdd': 'إضافة', - 'dashboard.timezoneCustomErrorEmpty': 'أدخل معرّف منطقة زمنية', - 'dashboard.timezoneCustomErrorInvalid': 'منطقة زمنية غير صالحة. استخدم صيغة مثل Asia/Riyadh', - 'dashboard.timezoneCustomErrorDuplicate': 'مضافة بالفعل', - 'dashboard.emptyTitle': 'لا توجد رحلات بعد', - 'dashboard.emptyText': 'أنشئ رحلتك الأولى وابدأ التخطيط', - 'dashboard.emptyButton': 'إنشاء أول رحلة', - 'dashboard.nextTrip': 'الرحلة القادمة', - 'dashboard.shared': 'مشتركة', - 'dashboard.sharedBy': 'شاركها {name}', - 'dashboard.days': 'الأيام', - 'dashboard.places': 'الأماكن', - 'dashboard.members': 'ال חברים', - 'dashboard.archive': 'أرشفة', - 'dashboard.copyTrip': 'نسخ', - 'dashboard.copySuffix': 'نسخة', - 'dashboard.restore': 'استعادة', - 'dashboard.archived': 'مؤرشفة', - 'dashboard.status.ongoing': 'جارية', - 'dashboard.status.today': 'اليوم', - 'dashboard.status.tomorrow': 'غدًا', - 'dashboard.status.past': 'منتهية', - 'dashboard.status.daysLeft': 'متبقي {count} يوم', - 'dashboard.toast.loadError': 'فشل تحميل الرحلات', - 'dashboard.toast.created': 'تم إنشاء الرحلة بنجاح', - 'dashboard.toast.createError': 'فشل إنشاء الرحلة', - 'dashboard.toast.updated': 'تم تحديث الرحلة', - 'dashboard.toast.updateError': 'فشل تحديث الرحلة', - 'dashboard.toast.deleted': 'تم حذف الرحلة', - 'dashboard.toast.deleteError': 'فشل حذف الرحلة', - 'dashboard.toast.archived': 'تمت أرشفة الرحلة', - 'dashboard.toast.archiveError': 'فشل الأرشفة', - 'dashboard.toast.restored': 'تمت استعادة الرحلة', - 'dashboard.toast.restoreError': 'فشل الاستعادة', - 'dashboard.toast.copied': 'تم نسخ الرحلة!', - 'dashboard.toast.copyError': 'فشل نسخ الرحلة', - 'dashboard.confirm.delete': 'حذف الرحلة "{title}"؟ سيتم حذف جميع الأماكن والخطط نهائيًا.', - 'dashboard.editTrip': 'تعديل الرحلة', - 'dashboard.createTrip': 'إنشاء رحلة جديدة', - 'dashboard.tripTitle': 'العنوان', - 'dashboard.tripTitlePlaceholder': 'مثال: صيف في اليابان', - 'dashboard.tripDescription': 'الوصف', - 'dashboard.tripDescriptionPlaceholder': 'عمّ تتحدث هذه الرحلة؟', - 'dashboard.startDate': 'تاريخ البداية', - 'dashboard.endDate': 'تاريخ النهاية', - 'dashboard.dayCount': 'عدد الأيام', - 'dashboard.dayCountHint': 'عدد الأيام المراد التخطيط لها عندما لا يتم تحديد تواريخ السفر.', - 'dashboard.noDateHint': 'لا يوجد تاريخ محدد. سيتم إنشاء 7 أيام افتراضية ويمكنك تغيير ذلك لاحقًا.', - 'dashboard.coverImage': 'صورة الغلاف', - 'dashboard.addCoverImage': 'إضافة صورة غلاف', - 'dashboard.addMembers': 'رفاق السفر', - 'dashboard.addMember': 'إضافة عضو', - 'dashboard.coverSaved': 'تم حفظ صورة الغلاف', - 'dashboard.coverUploadError': 'فشل الرفع', - 'dashboard.coverRemoveError': 'فشل الإزالة', - 'dashboard.titleRequired': 'العنوان مطلوب', - 'dashboard.endDateError': 'يجب أن يكون تاريخ النهاية بعد البداية', - - // Settings - 'settings.title': 'الإعدادات', - 'settings.subtitle': 'ضبط إعداداتك الشخصية', - 'settings.tabs.display': 'العرض', - 'settings.tabs.map': 'الخريطة', - 'settings.tabs.notifications': 'الإشعارات', - 'settings.tabs.integrations': 'التكاملات', - 'settings.tabs.account': 'الحساب', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'حول', - 'settings.map': 'الخريطة', - 'settings.mapTemplate': 'قالب الخريطة', - 'settings.mapTemplatePlaceholder.select': 'اختر قالبًا...', - 'settings.mapDefaultHint': 'اتركه فارغًا لاستخدام OpenStreetMap افتراضيًا', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'قالب URL لبلاطات الخريطة', - 'settings.mapProvider': 'مزود الخريطة', - 'settings.mapProviderHint': 'يؤثر على خرائط Trip Planner و Journey. يستخدم Atlas دائمًا Leaflet.', - 'settings.mapLeafletSubtitle': '2D كلاسيكي، أي بلاطات نقطية', - 'settings.mapMapboxSubtitle': 'بلاطات متجهية ومبانٍ ثلاثية الأبعاد وتضاريس', - 'settings.mapExperimental': 'تجريبي', - 'settings.mapMapboxToken': 'رمز وصول Mapbox', - 'settings.mapMapboxTokenHint': 'الرمز العام (pk.*) من', - 'settings.mapMapboxTokenLink': 'mapbox.com ← رموز الوصول', - 'settings.mapStyle': 'نمط الخريطة', - 'settings.mapStylePlaceholder': 'اختر نمط Mapbox', - 'settings.mapStyleHint': 'إعداد مسبق أو عنوان URL mapbox://styles/USER/ID خاص بك', - 'settings.map3dBuildings': 'مبانٍ ثلاثية الأبعاد وتضاريس', - 'settings.map3dHint': 'إمالة + مبانٍ ثلاثية الأبعاد حقيقية — يعمل مع كل نمط بما في ذلك الأقمار الصناعية.', - 'settings.mapHighQuality': 'وضع الجودة العالية', - 'settings.mapHighQualityHint': 'تحسين الحواف + إسقاط كروي لحواف أكثر حدة وعرض واقعي للعالم.', - 'settings.mapHighQualityWarning': 'قد يؤثر على الأداء في الأجهزة الأقل قدرة.', - 'settings.mapTipLabel': 'نصيحة:', - 'settings.mapTip': 'انقر بزر الماوس الأيمن واسحب لتدوير/إمالة الخريطة. النقر الأوسط لإضافة مكان (النقر الأيمن مخصص للتدوير).', - 'settings.latitude': 'خط العرض', - 'settings.longitude': 'خط الطول', - 'settings.saveMap': 'حفظ الخريطة', - 'settings.apiKeys': 'مفاتيح API', - 'settings.mapsKey': 'مفتاح Google Maps API', - 'settings.mapsKeyHint': 'للبحث عن الأماكن. يتطلب Places API (New).', - 'settings.weatherKey': 'مفتاح OpenWeatherMap API', - 'settings.weatherKeyHint': 'لبيانات الطقس.', - 'settings.keyPlaceholder': 'أدخل المفتاح...', - 'settings.configured': 'مُعدّ', - 'settings.saveKeys': 'حفظ المفاتيح', - 'settings.display': 'العرض', - 'settings.colorMode': 'نمط الألوان', - 'settings.light': 'فاتح', - 'settings.dark': 'داكن', - 'settings.auto': 'تلقائي', - 'settings.language': 'اللغة', - 'settings.temperature': 'وحدة الحرارة', - 'settings.timeFormat': 'تنسيق الوقت', - 'settings.blurBookingCodes': 'إخفاء رموز الحجز', - 'settings.notifications': 'الإشعارات', - 'settings.notifyTripInvite': 'دعوات الرحلات', - 'settings.notifyBookingChange': 'تغييرات الحجز', - 'settings.notifyTripReminder': 'تذكيرات الرحلات', - 'settings.notifyTodoDue': 'مهمة مستحقة', - 'settings.notifyVacayInvite': 'دعوات دمج الإجازات', - 'settings.notifyPhotosShared': 'صور مشتركة (Immich)', - 'settings.notifyCollabMessage': 'رسائل الدردشة (Collab)', - 'settings.notifyPackingTagged': 'قائمة الأمتعة: التعيينات', - 'settings.notifyWebhook': 'إشعارات Webhook', - 'settings.notificationsDisabled': 'الإشعارات غير مكوّنة. اطلب من المسؤول تفعيل إشعارات البريد الإلكتروني أو Webhook.', - 'settings.notificationsActive': 'القناة النشطة', - 'settings.notificationsManagedByAdmin': 'يتم تكوين أحداث الإشعارات بواسطة المسؤول.', - 'admin.notifications.title': 'الإشعارات', - 'admin.notifications.hint': 'اختر قناة إشعارات واحدة. يمكن تفعيل واحدة فقط في كل مرة.', - 'admin.notifications.none': 'معطّل', - 'admin.notifications.email': 'البريد الإلكتروني (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'حفظ إعدادات الإشعارات', - 'admin.notifications.saved': 'تم حفظ إعدادات الإشعارات', - 'admin.notifications.testWebhook': 'إرسال webhook تجريبي', - 'admin.notifications.testWebhookSuccess': 'تم إرسال webhook التجريبي بنجاح', - 'admin.notifications.testWebhookFailed': 'فشل إرسال webhook التجريبي', - 'admin.smtp.title': 'البريد والإشعارات', - 'admin.smtp.hint': 'تكوين SMTP لإرسال إشعارات البريد الإلكتروني.', - 'admin.smtp.testButton': 'إرسال بريد تجريبي', - 'admin.webhook.hint': 'إرسال الإشعارات إلى webhook خارجي (Discord، Slack، إلخ).', - 'admin.smtp.testSuccess': 'تم إرسال البريد التجريبي بنجاح', - 'admin.smtp.testFailed': 'فشل إرسال البريد التجريبي', - 'dayplan.icsTooltip': 'تصدير التقويم (ICS)', - 'share.linkTitle': 'رابط عام', - 'share.linkHint': 'أنشئ رابطًا يمكن لأي شخص استخدامه لعرض هذه الرحلة بدون تسجيل الدخول. للقراءة فقط — لا يمكن التعديل.', - 'share.createLink': 'إنشاء رابط', - 'share.deleteLink': 'حذف الرابط', - 'share.createError': 'تعذر إنشاء الرابط', - 'common.copy': 'نسخ', - 'common.copied': 'تم النسخ', - 'share.permMap': 'الخريطة والخطة', - 'share.permBookings': 'الحجوزات', - 'share.permPacking': 'الأمتعة', - 'shared.expired': 'الرابط منتهي أو غير صالح', - 'shared.expiredHint': 'رابط الرحلة المشترك لم يعد نشطًا.', - 'shared.readOnly': 'عرض للقراءة فقط', - 'shared.tabPlan': 'الخطة', - 'shared.tabBookings': 'الحجوزات', - 'shared.tabPacking': 'قائمة التعبئة', - 'shared.tabBudget': 'الميزانية', - 'shared.tabChat': 'الدردشة', - 'shared.days': 'أيام', - 'shared.places': 'أماكن', - 'shared.other': 'أخرى', - 'shared.totalBudget': 'إجمالي الميزانية', - 'shared.messages': 'رسائل', - 'shared.sharedVia': 'تمت المشاركة عبر', - 'shared.confirmed': 'مؤكد', - 'shared.pending': 'قيد الانتظار', - 'share.permBudget': 'الميزانية', - 'share.permCollab': 'الدردشة', - 'settings.on': 'تشغيل', - 'settings.off': 'إيقاف', - 'settings.mcp.title': 'إعداد MCP', - 'settings.mcp.endpoint': 'نقطة نهاية MCP', - 'settings.mcp.clientConfig': 'إعداد العميل', - 'settings.mcp.clientConfigHint': 'استبدل برمز API من القائمة أدناه. قد يحتاج مسار npx إلى ضبط وفق نظامك (مثلاً C:\\PROGRA~1\\nodejs\\npx.cmd على Windows).', - 'settings.mcp.clientConfigHintOAuth': 'استبدل و ببيانات الاعتماد المعروضة في عميل OAuth 2.1 الذي أنشأته أعلاه. سيفتح mcp-remote متصفحك لإتمام التفويض في أول اتصال. قد يحتاج مسار npx إلى تعديل حسب نظامك (مثال: C:\PROGRA~1\nodejs\npx.cmd على Windows).', - 'settings.mcp.copy': 'نسخ', - 'settings.mcp.copied': 'تم النسخ!', - 'settings.mcp.apiTokens': 'رموز API', - 'settings.mcp.createToken': 'إنشاء رمز جديد', - 'settings.mcp.noTokens': 'لا توجد رموز بعد. أنشئ رمزاً للاتصال بعملاء MCP.', - 'settings.mcp.tokenCreatedAt': 'أُنشئ', - 'settings.mcp.tokenUsedAt': 'استُخدم', - 'settings.mcp.deleteTokenTitle': 'حذف الرمز', - 'settings.mcp.deleteTokenMessage': 'سيتوقف هذا الرمز عن العمل فوراً. أي عميل MCP يستخدمه سيفقد الوصول.', - 'settings.mcp.modal.createTitle': 'إنشاء رمز API', - 'settings.mcp.modal.tokenName': 'اسم الرمز', - 'settings.mcp.modal.tokenNamePlaceholder': 'مثال: Claude Desktop، حاسوب العمل', - 'settings.mcp.modal.creating': 'جارٍ الإنشاء…', - 'settings.mcp.modal.create': 'إنشاء الرمز', - 'settings.mcp.modal.createdTitle': 'تم إنشاء الرمز', - 'settings.mcp.modal.createdWarning': 'سيُعرض هذا الرمز مرة واحدة فقط. انسخه واحفظه الآن — لا يمكن استرداده.', - 'settings.mcp.modal.done': 'تم', - 'settings.mcp.toast.created': 'تم إنشاء الرمز', - 'settings.mcp.toast.createError': 'فشل إنشاء الرمز', - 'settings.mcp.toast.deleted': 'تم حذف الرمز', - 'settings.mcp.toast.deleteError': 'فشل حذف الرمز', - 'settings.mcp.apiTokensDeprecated': 'رموز API قديمة وستُزال في إصدار مستقبلي. يُرجى استخدام عملاء OAuth 2.1 بدلاً منها.', - 'settings.oauth.clients': 'عملاء OAuth 2.1', - 'settings.oauth.clientsHint': 'سجّل عملاء OAuth 2.1 للسماح لتطبيقات MCP الخارجية (Claude Web وCursor وغيرها) بالاتصال دون رموز ثابتة.', - 'settings.oauth.createClient': 'عميل جديد', - 'settings.oauth.noClients': 'لا يوجد عملاء OAuth مسجلون.', - 'settings.oauth.clientId': 'معرّف العميل', - 'settings.oauth.clientSecret': 'سر العميل', - 'settings.oauth.deleteClient': 'حذف العميل', - 'settings.oauth.deleteClientMessage': 'سيتم حذف هذا العميل وجميع الجلسات النشطة بشكل دائم. ستفقد أي تطبيق يستخدمه وصوله فوراً.', - 'settings.oauth.rotateSecret': 'تجديد السر', - 'settings.oauth.rotateSecretMessage': 'سيتم إنشاء سر عميل جديد وإبطال جميع الجلسات الحالية فوراً. حدّث تطبيقك قبل إغلاق هذا الحوار.', - 'settings.oauth.rotateSecretConfirm': 'تجديد', - 'settings.oauth.rotateSecretConfirming': 'جارٍ التجديد…', - 'settings.oauth.rotateSecretDoneTitle': 'تم إنشاء سر جديد', - 'settings.oauth.rotateSecretDoneWarning': 'يُعرض هذا السر مرة واحدة فقط. انسخه الآن وحدّث تطبيقك — تم إبطال جميع الجلسات السابقة.', - 'settings.oauth.activeSessions': 'جلسات OAuth النشطة', - 'settings.oauth.sessionScopes': 'النطاقات', - 'settings.oauth.sessionExpires': 'تنتهي', - 'settings.oauth.revoke': 'إلغاء', - 'settings.oauth.revokeSession': 'إلغاء الجلسة', - 'settings.oauth.revokeSessionMessage': 'سيؤدي هذا إلى إلغاء الوصول لهذه الجلسة OAuth فوراً.', - 'settings.oauth.modal.createTitle': 'تسجيل عميل OAuth', - 'settings.oauth.modal.presets': 'إعدادات سريعة', - 'settings.oauth.modal.clientName': 'اسم التطبيق', - 'settings.oauth.modal.clientNamePlaceholder': 'مثال: Claude Web، تطبيق MCP الخاص بي', - 'settings.oauth.modal.redirectUris': 'عناوين URI لإعادة التوجيه', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'عنوان URI واحد لكل سطر. يُطلب HTTPS (localhost مستثنى). يُطبق تطابق دقيق.', - 'settings.oauth.modal.scopes': 'النطاقات المسموح بها', - 'settings.oauth.modal.scopesHint': 'list_trips وget_trip_summary متاحان دائماً — لا يُطلب نطاق. يساعدان الذكاء الاصطناعي في اكتشاف معرّفات الرحلات.', - 'settings.oauth.modal.selectAll': 'تحديد الكل', - 'settings.oauth.modal.deselectAll': 'إلغاء تحديد الكل', - 'settings.oauth.modal.creating': 'جارٍ التسجيل…', - 'settings.oauth.modal.create': 'تسجيل العميل', - 'settings.oauth.modal.createdTitle': 'تم تسجيل العميل', - 'settings.oauth.modal.createdWarning': 'يُعرض سر العميل مرة واحدة فقط. انسخه الآن — لا يمكن استرداده.', - 'settings.oauth.toast.createError': 'فشل تسجيل عميل OAuth', - 'settings.oauth.toast.deleted': 'تم حذف عميل OAuth', - 'settings.oauth.toast.deleteError': 'فشل حذف عميل OAuth', - 'settings.oauth.toast.revoked': 'تم إلغاء الجلسة', - 'settings.oauth.toast.revokeError': 'فشل إلغاء الجلسة', - 'settings.oauth.toast.rotateError': 'فشل تجديد سر العميل', - 'settings.oauth.modal.machineClient': 'عميل آلي (بدون تسجيل دخول عبر المتصفح)', - 'settings.oauth.modal.machineClientHint': 'استخدام منحة client_credentials — لا تحتاج إلى عناوين إعادة التوجيه. يُصدر الرمز المميز مباشرةً عبر client_id + client_secret ويعمل بصلاحياتك ضمن النطاقات المحددة.', - 'settings.oauth.modal.machineClientUsage': 'للحصول على رمز مميز: POST /oauth/token مع grant_type=client_credentials وclient_id وclient_secret. بدون متصفح، بدون رمز تحديث.', - 'settings.oauth.badge.machine': 'آلي', - 'settings.account': 'الحساب', - 'settings.about': 'حول', - 'settings.about.reportBug': 'الإبلاغ عن خطأ', - 'settings.about.reportBugHint': 'وجدت مشكلة؟ أخبرنا', - 'settings.about.featureRequest': 'اقتراح ميزة', - 'settings.about.featureRequestHint': 'اقترح ميزة جديدة', - 'settings.about.wikiHint': 'التوثيق والأدلة', - 'settings.about.supporters.badge': 'الداعمون الشهريون', - 'settings.about.supporters.title': 'رفاق رحلة TREK', - 'settings.about.supporters.subtitle': 'بينما تخطّط لمسارك التالي، يساعد هؤلاء الأشخاص في التخطيط لمستقبل TREK. تذهب مساهمتهم الشهرية مباشرةً إلى التطوير والساعات الفعلية المبذولة — حتى يظلّ TREK مفتوح المصدر.', - 'settings.about.supporters.since': 'داعم منذ {date}', - 'settings.about.supporters.tierEmpty': 'كن الأول', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK هو مخطط سفر مستضاف ذاتيًا يساعدك على تنظيم رحلاتك من أول فكرة حتى آخر ذكرى. تخطيط يومي، ميزانية، قوائم تعبئة، صور والمزيد — كل شيء في مكان واحد، على خادمك الخاص.', - 'settings.about.madeWith': 'صُنع بـ', - 'settings.about.madeBy': 'بواسطة موريس ومجتمع مفتوح المصدر متنامٍ.', - 'settings.username': 'اسم المستخدم', - 'settings.email': 'البريد الإلكتروني', - 'settings.role': 'الدور', - 'settings.roleAdmin': 'مسؤول', - 'settings.oidcLinked': 'مرتبط مع', - 'settings.changePassword': 'تغيير كلمة المرور', - 'settings.mustChangePassword': 'يجب عليك تغيير كلمة المرور قبل المتابعة. يرجى تعيين كلمة مرور جديدة أدناه.', - 'settings.currentPassword': 'كلمة المرور الحالية', - 'settings.currentPasswordRequired': 'كلمة المرور الحالية مطلوبة', - 'settings.newPassword': 'كلمة المرور الجديدة', - 'settings.confirmPassword': 'تأكيد كلمة المرور الجديدة', - 'settings.updatePassword': 'تحديث كلمة المرور', - 'settings.passwordRequired': 'أدخل كلمة المرور الحالية والجديدة', - 'settings.passwordTooShort': 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', - 'settings.passwordMismatch': 'كلمتا المرور غير متطابقتين', - 'settings.passwordWeak': 'يجب أن تحتوي كلمة المرور على حرف كبير وحرف صغير ورقم ورمز خاص', - 'settings.passwordChanged': 'تم تغيير كلمة المرور بنجاح', - 'settings.deleteAccount': 'حذف الحساب', - 'settings.deleteAccountTitle': 'هل تريد حذف حسابك؟', - 'settings.deleteAccountWarning': 'سيتم حذف حسابك وجميع رحلاتك وأماكنك وملفاتك نهائيًا. لا يمكن التراجع عن ذلك.', - 'settings.deleteAccountConfirm': 'حذف نهائي', - 'settings.deleteBlockedTitle': 'الحذف غير ممكن', - 'settings.deleteBlockedMessage': 'أنت المسؤول الوحيد. قم بترقية مستخدم آخر إلى مسؤول قبل حذف حسابك.', - 'settings.roleUser': 'مستخدم', - 'settings.saveProfile': 'حفظ الملف الشخصي', - 'settings.toast.mapSaved': 'تم حفظ إعدادات الخريطة', - 'settings.toast.keysSaved': 'تم حفظ مفاتيح API', - 'settings.toast.displaySaved': 'تم حفظ إعدادات العرض', - 'settings.toast.profileSaved': 'تم حفظ الملف الشخصي', - 'settings.uploadAvatar': 'رفع صورة الملف الشخصي', - 'settings.removeAvatar': 'إزالة صورة الملف الشخصي', - 'settings.avatarUploaded': 'تم تحديث صورة الملف الشخصي', - 'settings.avatarRemoved': 'تمت إزالة صورة الملف الشخصي', - 'settings.avatarError': 'فشل الرفع', - 'settings.mfa.title': 'المصادقة الثنائية (2FA)', - 'settings.mfa.description': 'تضيف خطوة ثانية عند تسجيل الدخول. استخدم تطبيق مصادقة (Google Authenticator، Authy، إلخ).', - 'settings.mfa.requiredByPolicy': 'المسؤول يتطلب المصادقة الثنائية. اضبط تطبيق المصادقة أدناه قبل المتابعة.', - 'settings.mfa.backupTitle': 'رموز النسخ الاحتياطي', - 'settings.mfa.backupDescription': 'استخدم هذه الرموز لمرة واحدة إذا فقدت الوصول إلى تطبيق المصادقة.', - 'settings.mfa.backupWarning': 'احفظ هذه الرموز الآن. كل رمز يمكن استخدامه مرة واحدة فقط.', - 'settings.mfa.backupCopy': 'نسخ الرموز', - 'settings.mfa.backupDownload': 'تنزيل TXT', - 'settings.mfa.backupPrint': 'طباعة / PDF', - 'settings.mfa.backupCopied': 'تم نسخ رموز النسخ الاحتياطي', - 'settings.mfa.enabled': 'المصادقة الثنائية مفعّلة على حسابك.', - 'settings.mfa.disabled': 'المصادقة الثنائية غير مفعّلة.', - 'settings.mfa.setup': 'إعداد المصادقة', - 'settings.mfa.scanQr': 'امسح رمز QR بتطبيقك أو أدخل المفتاح يدويًا.', - 'settings.mfa.secretLabel': 'المفتاح السري (إدخال يدوي)', - 'settings.mfa.codePlaceholder': 'رمز من 6 أرقام', - 'settings.mfa.enable': 'تفعيل 2FA', - 'settings.mfa.cancelSetup': 'إلغاء', - 'settings.mfa.disableTitle': 'تعطيل 2FA', - 'settings.mfa.disableHint': 'أدخل كلمة مرور حسابك ورمزًا حاليًا من المصادقة.', - 'settings.mfa.disable': 'تعطيل 2FA', - 'settings.mfa.toastEnabled': 'تم تفعيل المصادقة الثنائية', - 'settings.mfa.toastDisabled': 'تم تعطيل المصادقة الثنائية', - 'settings.mfa.demoBlocked': 'غير متاح في الوضع التجريبي', - - // Login - 'login.error': 'فشل تسجيل الدخول. يرجى التحقق من بياناتك.', - 'login.tagline': 'رحلاتك.\nخطتك.', - 'login.description': 'خطط لرحلاتك بشكل تعاوني مع خرائط تفاعلية وميزانيات ومزامنة لحظية.', - 'login.features.maps': 'خرائط تفاعلية', - 'login.features.mapsDesc': 'Google Places ومسارات وتجميع', - 'login.features.realtime': 'مزامنة فورية', - 'login.features.realtimeDesc': 'خططوا معًا عبر WebSocket', - 'login.features.budget': 'تتبع الميزانية', - 'login.features.budgetDesc': 'فئات ورسوم وتقسيم لكل شخص', - 'login.features.collab': 'تعاون', - 'login.features.collabDesc': 'عدة مستخدمين مع رحلات مشتركة', - 'login.features.packing': 'قوائم تجهيز', - 'login.features.packingDesc': 'فئات وتقدم واقتراحات', - 'login.features.bookings': 'الحجوزات', - 'login.features.bookingsDesc': 'رحلات وفنادق ومطاعم وغير ذلك', - 'login.features.files': 'المستندات', - 'login.features.filesDesc': 'رفع الملفات وإدارتها', - 'login.features.routes': 'مسارات ذكية', - 'login.features.routesDesc': 'تحسين تلقائي وتصدير إلى Google Maps', - 'login.selfHosted': 'استضافة ذاتية · مفتوح المصدر · بياناتك تبقى ملكك', - 'login.title': 'تسجيل الدخول', - 'login.subtitle': 'مرحبًا بعودتك', - 'login.signingIn': 'جارٍ تسجيل الدخول…', - 'login.signIn': 'دخول', - 'login.createAdmin': 'إنشاء حساب مسؤول', - 'login.createAdminHint': 'أعد إعداد أول حساب مسؤول لـ TREK.', - 'login.setNewPassword': 'تعيين كلمة مرور جديدة', - 'login.setNewPasswordHint': 'يجب عليك تغيير كلمة المرور قبل المتابعة.', - 'login.createAccount': 'إنشاء حساب', - 'login.createAccountHint': 'سجّل حسابًا جديدًا.', - 'login.creating': 'جارٍ الإنشاء…', - 'login.noAccount': 'ليس لديك حساب؟', - 'login.hasAccount': 'لديك حساب بالفعل؟', - 'login.register': 'تسجيل', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': 'اسم المستخدم', - 'login.oidc.registrationDisabled': 'التسجيل معطّل. تواصل مع المسؤول.', - 'login.oidc.noEmail': 'لم يتم استلام بريد إلكتروني من المزوّد.', - 'login.oidc.tokenFailed': 'فشلت المصادقة.', - 'login.oidc.invalidState': 'جلسة غير صالحة. حاول مرة أخرى.', - 'login.demoFailed': 'فشل الدخول إلى العرض التجريبي', - 'login.oidcSignIn': 'تسجيل الدخول عبر {name}', - 'login.oidcOnly': 'تم تعطيل المصادقة بكلمة المرور. يرجى تسجيل الدخول عبر مزود SSO.', - 'login.oidcLoggedOut': 'تم تسجيل خروجك. سجّل الدخول مجدداً عبر مزود SSO.', - 'login.demoHint': 'جرّب العرض التجريبي دون الحاجة للتسجيل', - 'login.mfaTitle': 'المصادقة الثنائية', - 'login.mfaSubtitle': 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة.', - 'login.mfaCodeLabel': 'رمز التحقق', - 'login.mfaCodeRequired': 'أدخل الرمز من تطبيق المصادقة.', - 'login.mfaHint': 'افتح Google Authenticator أو Authy أو أي تطبيق TOTP آخر.', - 'login.mfaBack': '← العودة لتسجيل الدخول', - 'login.mfaVerify': 'تحقق', - 'login.invalidInviteLink': 'رابط الدعوة غير صالح أو منتهي الصلاحية', - 'login.oidcFailed': 'فشل تسجيل الدخول عبر OIDC', - 'login.usernameRequired': 'اسم المستخدم مطلوب', - 'login.passwordMinLength': 'يجب أن تكون كلمة المرور 8 أحرف على الأقل', - 'login.forgotPassword': 'نسيت كلمة المرور؟', - 'login.forgotPasswordTitle': 'إعادة تعيين كلمة المرور', - 'login.forgotPasswordBody': 'أدخل عنوان البريد الإلكتروني المسجَّل. إذا كان الحساب موجودًا، سنرسل رابط إعادة التعيين.', - 'login.forgotPasswordSubmit': 'إرسال الرابط', - 'login.forgotPasswordSentTitle': 'تحقق من بريدك', - 'login.forgotPasswordSentBody': 'إذا كان هناك حساب مرتبط بهذا البريد، فإن الرابط في الطريق. تنتهي صلاحيته خلال 60 دقيقة.', - 'login.forgotPasswordSmtpHintOff': 'ملاحظة: لم يقم المسؤول بتكوين SMTP، لذا سيتم كتابة رابط إعادة التعيين في وحدة تحكم الخادم بدلاً من إرساله عبر البريد الإلكتروني.', - 'login.backToLogin': 'العودة إلى تسجيل الدخول', - 'login.newPassword': 'كلمة المرور الجديدة', - 'login.confirmPassword': 'تأكيد كلمة المرور الجديدة', - 'login.passwordsDontMatch': 'كلمتا المرور غير متطابقتين', - 'login.mfaCode': 'رمز 2FA', - 'login.resetPasswordTitle': 'ضبط كلمة مرور جديدة', - 'login.resetPasswordBody': 'اختر كلمة مرور قوية لم تستخدمها هنا من قبل. 8 أحرف على الأقل.', - 'login.resetPasswordMfaBody': 'أدخل رمز 2FA أو رمز النسخ الاحتياطي لإتمام إعادة التعيين.', - 'login.resetPasswordSubmit': 'إعادة تعيين كلمة المرور', - 'login.resetPasswordVerify': 'تحقق وأعد التعيين', - 'login.resetPasswordSuccessTitle': 'تم تحديث كلمة المرور', - 'login.resetPasswordSuccessBody': 'يمكنك الآن تسجيل الدخول بكلمة المرور الجديدة.', - 'login.resetPasswordInvalidLink': 'رابط إعادة تعيين غير صالح', - 'login.resetPasswordInvalidLinkBody': 'هذا الرابط مفقود أو تالف. اطلب رابطًا جديدًا للمتابعة.', - 'login.resetPasswordFailed': 'فشلت إعادة التعيين. ربما انتهت صلاحية الرابط.', - - // Register - 'register.passwordMismatch': 'كلمتا المرور غير متطابقتين', - 'register.passwordTooShort': 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', - 'register.failed': 'فشل التسجيل', - 'register.getStarted': 'ابدأ الآن', - 'register.subtitle': 'أنشئ حسابًا وابدأ التخطيط لرحلات أحلامك.', - 'register.feature1': 'خطط رحلات غير محدودة', - 'register.feature2': 'عرض خريطة تفاعلي', - 'register.feature3': 'إدارة الأماكن والفئات', - 'register.feature4': 'تتبع الحجوزات', - 'register.feature5': 'إنشاء قوائم تجهيز', - 'register.feature6': 'حفظ الصور والملفات', - 'register.createAccount': 'إنشاء حساب', - 'register.startPlanning': 'ابدأ تخطيط رحلتك', - 'register.minChars': '6 أحرف على الأقل', - 'register.confirmPassword': 'تأكيد كلمة المرور', - 'register.repeatPassword': 'إعادة كلمة المرور', - 'register.registering': 'جارٍ التسجيل...', - 'register.register': 'تسجيل', - 'register.hasAccount': 'لديك حساب بالفعل؟', - 'register.signIn': 'تسجيل الدخول', - - // Admin - 'admin.title': 'الإدارة', - 'admin.subtitle': 'إدارة المستخدمين وإعدادات النظام', - 'admin.tabs.users': 'المستخدمون', - 'admin.tabs.categories': 'الفئات', - 'admin.tabs.backup': 'النسخ الاحتياطي', - 'admin.tabs.audit': 'تدقيق', - 'admin.tabs.settings': 'الإعدادات', - 'admin.tabs.config': 'التخصيص', - 'admin.tabs.defaults': 'الإعدادات الافتراضية', - 'admin.defaultSettings.title': 'إعدادات المستخدم الافتراضية', - 'admin.defaultSettings.description': 'تعيين الإعدادات الافتراضية على مستوى النظام. سيرى المستخدمون الذين لم يغيروا إعدادًا هذه القيم. تحظى تغييراتهم دائمًا بالأولوية.', - 'admin.defaultSettings.saved': 'تم حفظ الإعداد الافتراضي', - 'admin.defaultSettings.reset': 'إعادة التعيين إلى الإعداد الافتراضي المدمج', - 'admin.defaultSettings.resetToBuiltIn': 'إعادة تعيين', - 'admin.tabs.templates': 'قوالب التعبئة', - 'admin.tabs.addons': 'الإضافات', - 'admin.tabs.mcpTokens': 'وصول MCP', - 'admin.mcpTokens.title': 'وصول MCP', - 'admin.mcpTokens.subtitle': 'إدارة جلسات OAuth ورموز API لجميع المستخدمين', - 'admin.mcpTokens.sectionTitle': 'رموز API', - 'admin.mcpTokens.owner': 'المالك', - 'admin.mcpTokens.tokenName': 'اسم الرمز', - 'admin.mcpTokens.created': 'تاريخ الإنشاء', - 'admin.mcpTokens.lastUsed': 'آخر استخدام', - 'admin.mcpTokens.never': 'أبداً', - 'admin.mcpTokens.empty': 'لم يتم إنشاء أي رموز MCP بعد', - 'admin.mcpTokens.deleteTitle': 'حذف الرمز', - 'admin.mcpTokens.deleteMessage': 'سيتم إلغاء هذا الرمز فوراً. سيفقد المستخدم وصوله إلى MCP عبر هذا الرمز.', - 'admin.mcpTokens.deleteSuccess': 'تم حذف الرمز', - 'admin.mcpTokens.deleteError': 'فشل حذف الرمز', - 'admin.mcpTokens.loadError': 'فشل تحميل الرموز', - 'admin.oauthSessions.sectionTitle': 'جلسات OAuth', - 'admin.oauthSessions.clientName': 'العميل', - 'admin.oauthSessions.owner': 'المالك', - 'admin.oauthSessions.scopes': 'الصلاحيات', - 'admin.oauthSessions.created': 'تاريخ الإنشاء', - 'admin.oauthSessions.empty': 'لا توجد جلسات OAuth نشطة', - 'admin.oauthSessions.revokeTitle': 'إلغاء الجلسة', - 'admin.oauthSessions.revokeMessage': 'سيتم إلغاء جلسة OAuth هذه فوراً. سيفقد العميل وصوله إلى MCP.', - 'admin.oauthSessions.revokeSuccess': 'تم إلغاء الجلسة', - 'admin.oauthSessions.revokeError': 'فشل إلغاء الجلسة', - 'admin.oauthSessions.loadError': 'فشل تحميل جلسات OAuth', - 'admin.tabs.github': 'GitHub', - 'admin.stats.users': 'المستخدمون', - 'admin.stats.trips': 'الرحلات', - 'admin.stats.places': 'الأماكن', - 'admin.stats.photos': 'الصور', - 'admin.stats.files': 'الملفات', - 'admin.table.user': 'المستخدم', - 'admin.table.email': 'البريد الإلكتروني', - 'admin.table.role': 'الدور', - 'admin.table.created': 'تم الإنشاء', - 'admin.table.lastLogin': 'آخر تسجيل دخول', - 'admin.table.actions': 'الإجراءات', - 'admin.you': '(أنت)', - 'admin.editUser': 'تعديل المستخدم', - 'admin.newPassword': 'كلمة مرور جديدة', - 'admin.newPasswordHint': 'اتركه فارغًا للاحتفاظ بالحالية', - 'admin.deleteUser': 'حذف المستخدم "{name}"؟ سيتم حذف جميع الرحلات نهائيًا.', - 'admin.deleteUserTitle': 'حذف المستخدم', - 'admin.newPasswordPlaceholder': 'أدخل كلمة مرور جديدة…', - 'admin.toast.loadError': 'فشل تحميل بيانات الإدارة', - 'admin.toast.userUpdated': 'تم تحديث المستخدم', - 'admin.toast.updateError': 'فشل التحديث', - 'admin.toast.userDeleted': 'تم حذف المستخدم', - 'admin.toast.deleteError': 'فشل الحذف', - 'admin.toast.cannotDeleteSelf': 'لا يمكنك حذف حسابك الخاص', - 'admin.toast.userCreated': 'تم إنشاء المستخدم', - 'admin.toast.createError': 'فشل إنشاء المستخدم', - 'admin.toast.fieldsRequired': 'اسم المستخدم والبريد الإلكتروني وكلمة المرور مطلوبة', - 'admin.createUser': 'إنشاء مستخدم', - 'admin.invite.title': 'روابط الدعوة', - 'admin.invite.subtitle': 'إنشاء روابط تسجيل للاستخدام المحدود', - 'admin.invite.create': 'إنشاء رابط', - 'admin.invite.createAndCopy': 'إنشاء ونسخ', - 'admin.invite.empty': 'لم يتم إنشاء روابط دعوة بعد', - 'admin.invite.maxUses': 'الحد الأقصى للاستخدام', - 'admin.invite.expiry': 'تنتهي بعد', - 'admin.invite.uses': 'مستخدم', - 'admin.invite.expiresAt': 'تنتهي في', - 'admin.invite.createdBy': 'بواسطة', - 'admin.invite.active': 'نشط', - 'admin.invite.expired': 'منتهي', - 'admin.invite.usedUp': 'مستنفد', - 'admin.invite.copied': 'تم نسخ رابط الدعوة', - 'admin.invite.copyLink': 'نسخ الرابط', - 'admin.invite.deleted': 'تم حذف رابط الدعوة', - 'admin.invite.createError': 'فشل إنشاء رابط الدعوة', - 'admin.invite.deleteError': 'فشل حذف رابط الدعوة', - 'admin.allowRegistration': 'السماح بالتسجيل', - 'admin.allowRegistrationHint': 'يمكن للمستخدمين الجدد التسجيل بأنفسهم', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'فرض المصادقة الثنائية (2FA)', - 'admin.requireMfaHint': 'يجب على المستخدمين الذين لا يملكون 2FA إكمال الإعداد في الإعدادات قبل استخدام التطبيق.', - 'admin.apiKeys': 'مفاتيح API', - 'admin.apiKeysHint': 'اختياري. يُفعّل بيانات الأماكن الموسعة مثل الصور والطقس.', - 'admin.mapsKey': 'مفتاح Google Maps API', - 'admin.mapsKeyHint': 'مطلوب للبحث عن الأماكن. احصل عليه من console.cloud.google.com', - 'admin.mapsKeyHintLong': 'بدون مفتاح API، يُستخدم OpenStreetMap للبحث. مع مفتاح Google يمكن تحميل الصور والتقييمات وساعات العمل أيضًا. احصل عليه من console.cloud.google.com.', - 'admin.recommended': 'مُوصى به', - 'admin.weatherKey': 'مفتاح OpenWeatherMap API', - 'admin.weatherKeyHint': 'لبيانات الطقس. مجاني من openweathermap.org', - 'admin.validateKey': 'اختبار', - 'admin.keyValid': 'متصل', - 'admin.keyInvalid': 'غير صالح', - 'admin.keySaved': 'تم حفظ مفاتيح API', - 'admin.oidcTitle': 'تسجيل الدخول الموحد (OIDC)', - 'admin.oidcSubtitle': 'السماح بتسجيل الدخول عبر مزودين خارجيين مثل Google أو Apple أو Authentik أو Keycloak.', - 'admin.oidcDisplayName': 'الاسم المعروض', - 'admin.oidcIssuer': 'عنوان URL للمُصدر', - 'admin.oidcIssuerHint': 'عنوان OpenID Connect Issuer URL للمزود. مثال: https://accounts.google.com', - 'admin.oidcSaved': 'تم حفظ إعدادات OIDC', - 'admin.oidcOnlyMode': 'تعطيل المصادقة بكلمة المرور', - 'admin.oidcOnlyModeHint': 'عند التفعيل، يُسمح فقط بتسجيل الدخول عبر SSO. سيتم حظر تسجيل الدخول والتسجيل بكلمة المرور.', - - // File Types - 'admin.fileTypes': 'أنواع الملفات المسموح بها', - 'admin.fileTypesHint': 'حدد أنواع الملفات التي يمكن للمستخدمين رفعها.', - 'admin.fileTypesFormat': 'امتدادات مفصولة بفواصل (مثل jpg,png,pdf,doc). استخدم * للسماح بجميع الأنواع.', - 'admin.fileTypesSaved': 'تم حفظ إعدادات أنواع الملفات', - - // Packing Templates & Bag Tracking - 'admin.placesPhotos.title': 'صور الأماكن', - 'admin.placesPhotos.subtitle': 'جلب الصور من Google Places API. عطّلها للحفاظ على حصة API. صور Wikimedia غير متأثرة.', - 'admin.placesAutocomplete.title': 'الإكمال التلقائي للأماكن', - 'admin.placesAutocomplete.subtitle': 'استخدام Google Places API لاقتراحات البحث. عطّلها للحفاظ على حصة API.', - 'admin.placesDetails.title': 'تفاصيل الأماكن', - 'admin.placesDetails.subtitle': 'جلب معلومات تفصيلية عن الأماكن (الساعات، التقييم، الموقع) من Google Places API. عطّلها للحفاظ على حصة API.', - 'admin.bagTracking.title': 'تتبع الأمتعة', - 'admin.bagTracking.subtitle': 'تفعيل الوزن وتعيين الأمتعة للعناصر', - 'admin.collab.chat.title': 'الدردشة', - 'admin.collab.chat.subtitle': 'المراسلة في الوقت الفعلي للتعاون', - 'admin.collab.notes.title': 'الملاحظات', - 'admin.collab.notes.subtitle': 'ملاحظات ومستندات مشتركة', - 'admin.collab.polls.title': 'الاستطلاعات', - 'admin.collab.polls.subtitle': 'استطلاعات وتصويت جماعي', - 'admin.collab.whatsnext.title': 'ما التالي', - 'admin.collab.whatsnext.subtitle': 'اقتراحات الأنشطة والخطوات التالية', - 'admin.packingTemplates.title': 'قوالب التعبئة', - 'admin.packingTemplates.subtitle': 'إنشاء قوائم تعبئة قابلة لإعادة الاستخدام', - 'admin.packingTemplates.create': 'قالب جديد', - 'admin.packingTemplates.namePlaceholder': 'اسم القالب (مثال: عطلة شاطئية)', - 'admin.packingTemplates.empty': 'لم يتم إنشاء قوالب بعد', - 'admin.packingTemplates.items': 'عناصر', - 'admin.packingTemplates.categories': 'فئات', - 'admin.packingTemplates.itemName': 'اسم العنصر', - 'admin.packingTemplates.itemCategory': 'الفئة', - 'admin.packingTemplates.categoryName': 'اسم الفئة (مثال: ملابس)', - 'admin.packingTemplates.addCategory': 'إضافة فئة', - 'admin.packingTemplates.created': 'تم إنشاء القالب', - 'admin.packingTemplates.deleted': 'تم حذف القالب', - 'admin.packingTemplates.loadError': 'فشل تحميل القوالب', - 'admin.packingTemplates.createError': 'فشل إنشاء القالب', - 'admin.packingTemplates.deleteError': 'فشل حذف القالب', - 'admin.packingTemplates.saveError': 'فشل الحفظ', - - // Addons - 'admin.addons.title': 'الإضافات', - 'admin.addons.subtitle': 'فعّل أو عطّل الميزات لتخصيص تجربة TREK.', - 'admin.addons.catalog.memories.name': 'صور (Immich)', - 'admin.addons.catalog.memories.description': 'شارك صور رحلتك عبر Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'بروتوكول سياق النموذج لتكامل مساعد الذكاء الاصطناعي', - 'admin.addons.catalog.packing.name': 'القوائم', - 'admin.addons.catalog.packing.description': 'قوائم التعبئة والمهام لرحلاتك', - 'admin.addons.catalog.budget.name': 'الميزانية', - 'admin.addons.catalog.budget.description': 'تتبع النفقات وخطط ميزانية الرحلة', - 'admin.addons.catalog.documents.name': 'المستندات', - 'admin.addons.catalog.documents.description': 'حفظ وإدارة وثائق السفر', - 'admin.addons.catalog.vacay.name': 'الإجازة', - 'admin.addons.catalog.vacay.description': 'مخطط إجازات شخصي مع عرض تقويم', - 'admin.addons.catalog.atlas.name': 'الأطلس', - 'admin.addons.catalog.atlas.description': 'خريطة العالم مع الدول التي تمت زيارتها وإحصائيات السفر', - 'admin.addons.catalog.collab.name': 'التعاون', - 'admin.addons.catalog.collab.description': 'ملاحظات واستطلاعات ودردشة لحظية لتخطيط الرحلة', - 'admin.addons.subtitleBefore': 'فعّل أو عطّل الميزات لتخصيص تجربة ', - 'admin.addons.subtitleAfter': '.', - 'admin.addons.enabled': 'مفعّل', - 'admin.addons.disabled': 'معطّل', - 'admin.addons.type.trip': 'رحلة', - 'admin.addons.type.global': 'عام', - 'admin.addons.type.integration': 'تكامل', - 'admin.addons.tripHint': 'متاح كعلامة تبويب داخل كل رحلة', - 'admin.addons.globalHint': 'متاح كقسم مستقل في التنقل الرئيسي', - 'admin.addons.integrationHint': 'خدمات الواجهة الخلفية وتكاملات API بدون صفحة مخصصة', - 'admin.addons.toast.updated': 'تم تحديث الإضافة', - 'admin.addons.toast.error': 'فشل تحديث الإضافة', - 'admin.addons.noAddons': 'لا توجد إضافات متاحة', - - // Weather info - 'admin.weather.title': 'بيانات الطقس', - 'admin.weather.badge': 'منذ 24 مارس 2026', - 'admin.weather.description': 'يستخدم TREK خدمة Open-Meteo كمصدر لبيانات الطقس. وهي خدمة مجانية ومفتوحة المصدر ولا تتطلب مفتاح API.', - 'admin.weather.forecast': 'توقعات 16 يومًا', - 'admin.weather.forecastDesc': 'سابقًا 5 أيام (OpenWeatherMap)', - 'admin.weather.climate': 'بيانات المناخ التاريخية', - 'admin.weather.climateDesc': 'متوسطات آخر 85 سنة للأيام بعد توقعات الـ 16 يومًا', - 'admin.weather.requests': '10,000 طلب / يوم', - 'admin.weather.requestsDesc': 'مجاني، بدون مفتاح API', - 'admin.weather.locationHint': 'يعتمد الطقس على أول مكان بإحداثيات في كل يوم. إذا لم يكن هناك مكان مخصص ليوم ما، يُستخدم أي مكان من قائمة الأماكن كمرجع.', - - // GitHub - 'admin.audit.subtitle': 'أحداث الأمان والإدارة (النسخ الاحتياطية، المستخدمون، المصادقة الثنائية، الإعدادات).', - 'admin.audit.empty': 'لا توجد سجلات تدقيق بعد.', - 'admin.audit.refresh': 'تحديث', - 'admin.audit.loadMore': 'تحميل المزيد', - 'admin.audit.showing': 'تم تحميل {count} · الإجمالي {total}', - 'admin.audit.col.time': 'الوقت', - 'admin.audit.col.user': 'المستخدم', - 'admin.audit.col.action': 'الإجراء', - 'admin.audit.col.resource': 'المورد', - 'admin.audit.col.ip': 'عنوان IP', - 'admin.audit.col.details': 'التفاصيل', - - 'admin.github.title': 'سجل الإصدارات', - 'admin.github.subtitle': 'آخر التحديثات من {repo}', - 'admin.github.latest': 'الأحدث', - 'admin.github.prerelease': 'إصدار تجريبي', - 'admin.github.showDetails': 'إظهار التفاصيل', - 'admin.github.hideDetails': 'إخفاء التفاصيل', - 'admin.github.loadMore': 'تحميل المزيد', - 'admin.github.loading': 'جارٍ التحميل...', - 'admin.github.error': 'فشل تحميل الإصدارات', - 'admin.github.by': 'بواسطة', - 'admin.github.support': 'يساعدني في تطوير TREK', - - 'admin.update.available': 'يتوفر تحديث', - 'admin.update.text': 'TREK {version} متوفر. أنت تستخدم {current}.', - 'admin.update.button': 'عرض على GitHub', - 'admin.update.install': 'تثبيت التحديث', - 'admin.update.confirmTitle': 'تثبيت التحديث؟', - 'admin.update.confirmText': 'سيتم تحديث TREK من {current} إلى {version}. سيُعاد تشغيل الخادم تلقائيًا بعد ذلك.', - 'admin.update.dataInfo': 'جميع بياناتك (الرحلات، المستخدمون، مفاتيح API، المرفوعات، الإجازة، الأطلس، الميزانيات) ستبقى محفوظة.', - 'admin.update.warning': 'سيكون التطبيق غير متاح لفترة وجيزة أثناء إعادة التشغيل.', - 'admin.update.confirm': 'حدّث الآن', - 'admin.update.installing': 'جارٍ التحديث…', - 'admin.update.success': 'تم تثبيت التحديث. ستتم إعادة تشغيل الخادم…', - 'admin.update.failed': 'فشل التحديث', - 'admin.update.backupHint': 'نوصي بإنشاء نسخة احتياطية قبل التحديث.', - 'admin.update.backupLink': 'الذهاب إلى النسخ الاحتياطي', - 'admin.update.howTo': 'كيفية التحديث', - 'admin.update.dockerText': 'يعمل TREK الخاص بك في Docker. للتحديث إلى {version}، نفّذ الأوامر التالية على الخادم:', - 'admin.update.reloadHint': 'يرجى إعادة تحميل الصفحة بعد بضع ثوانٍ.', - - // Vacay addon - 'vacay.subtitle': 'خطط وأدر أيام الإجازة', - 'vacay.settings': 'الإعدادات', - 'vacay.year': 'السنة', - 'vacay.addYear': 'إضافة السنة التالية', - 'vacay.addPrevYear': 'إضافة السنة السابقة', - 'vacay.removeYear': 'إزالة السنة', - 'vacay.removeYearConfirm': 'إزالة {year}؟', - 'vacay.removeYearHint': 'سيتم حذف كل إدخالات الإجازات والعطل الخاصة بهذه السنة نهائيًا.', - 'vacay.remove': 'إزالة', - 'vacay.persons': 'الأشخاص', - 'vacay.noPersons': 'لم تتم إضافة أشخاص بعد', - 'vacay.addPerson': 'إضافة شخص', - 'vacay.editPerson': 'تعديل الشخص', - 'vacay.removePerson': 'إزالة الشخص', - 'vacay.removePersonConfirm': 'إزالة {name}؟', - 'vacay.removePersonHint': 'سيتم حذف جميع إدخالات الإجازة لهذا الشخص نهائيًا.', - 'vacay.personName': 'الاسم', - 'vacay.personNamePlaceholder': 'أدخل الاسم', - 'vacay.color': 'اللون', - 'vacay.add': 'إضافة', - 'vacay.legend': 'المفتاح', - 'vacay.publicHoliday': 'عطلة رسمية', - 'vacay.companyHoliday': 'عطلة شركة', - 'vacay.weekend': 'نهاية الأسبوع', - 'vacay.modeVacation': 'إجازة', - 'vacay.modeCompany': 'عطلة شركة', - 'vacay.entitlement': 'الاستحقاق', - 'vacay.entitlementDays': 'الأيام', - 'vacay.used': 'المستخدم', - 'vacay.remaining': 'المتبقي', - 'vacay.carriedOver': 'من {year}', - 'vacay.blockWeekends': 'حظر عطلة نهاية الأسبوع', - 'vacay.blockWeekendsHint': 'منع إدخالات الإجازة يومي السبت والأحد', - 'vacay.weekendDays': 'أيام عطلة نهاية الأسبوع', - 'vacay.mon': 'الاثنين', - 'vacay.tue': 'الثلاثاء', - 'vacay.wed': 'الأربعاء', - 'vacay.thu': 'الخميس', - 'vacay.fri': 'الجمعة', - 'vacay.sat': 'السبت', - 'vacay.sun': 'الأحد', - 'vacay.publicHolidays': 'العطل الرسمية', - 'vacay.publicHolidaysHint': 'وضع علامة على العطل الرسمية في التقويم', - 'vacay.selectCountry': 'اختر الدولة', - 'vacay.selectRegion': 'اختر المنطقة (اختياري)', - 'vacay.addCalendar': 'إضافة تقويم', - 'vacay.calendarLabel': 'التسمية', - 'vacay.calendarColor': 'اللون', - 'vacay.noCalendars': 'لا توجد تقويمات', - 'vacay.companyHolidays': 'عطل الشركة', - 'vacay.companyHolidaysHint': 'السماح بوضع علامة على أيام عطلات الشركة', - 'vacay.companyHolidaysNoDeduct': 'لا تُخصم عطل الشركة من أيام الإجازة.', - 'vacay.weekStart': 'يبدأ الأسبوع في', - 'vacay.weekStartHint': 'اختر ما إذا كان الأسبوع يبدأ يوم الاثنين أو الأحد', - 'vacay.carryOver': 'الترحيل', - 'vacay.carryOverHint': 'ترحيل أيام الإجازة المتبقية تلقائيًا إلى السنة التالية', - 'vacay.sharing': 'المشاركة', - 'vacay.sharingHint': 'شارك خطة إجازاتك مع مستخدمي TREK الآخرين', - 'vacay.owner': 'المالك', - 'vacay.shareEmailPlaceholder': 'البريد الإلكتروني لمستخدم TREK', - 'vacay.shareSuccess': 'تمت مشاركة الخطة بنجاح', - 'vacay.shareError': 'تعذرت مشاركة الخطة', - 'vacay.dissolve': 'فك الدمج', - 'vacay.dissolveHint': 'افصل التقويمات مرة أخرى. سيتم الاحتفاظ بإدخالاتك.', - 'vacay.dissolveAction': 'فك', - 'vacay.dissolved': 'تم فصل التقويم', - 'vacay.fusedWith': 'مُدمج مع', - 'vacay.you': 'أنت', - 'vacay.noData': 'لا توجد بيانات', - 'vacay.changeColor': 'تغيير اللون', - 'vacay.inviteUser': 'دعوة مستخدم', - 'vacay.inviteHint': 'ادعُ مستخدم TREK آخرًا لمشاركة تقويم إجازة مشترك.', - 'vacay.selectUser': 'اختر مستخدمًا', - 'vacay.sendInvite': 'إرسال الدعوة', - 'vacay.inviteSent': 'تم إرسال الدعوة', - 'vacay.inviteError': 'تعذر إرسال الدعوة', - 'vacay.pending': 'قيد الانتظار', - 'vacay.noUsersAvailable': 'لا يوجد مستخدمون متاحون', - 'vacay.accept': 'قبول', - 'vacay.decline': 'رفض', - 'vacay.acceptFusion': 'قبول ودمج', - 'vacay.inviteTitle': 'طلب دمج', - 'vacay.inviteWantsToFuse': 'يريد مشاركة تقويم إجازة معك.', - 'vacay.fuseInfo1': 'سيرى كلاكما جميع إدخالات الإجازة في تقويم مشترك واحد.', - 'vacay.fuseInfo2': 'يمكن لكلا الطرفين إنشاء وتعديل الإدخالات لبعضهما البعض.', - 'vacay.fuseInfo3': 'يمكن لكلا الطرفين حذف الإدخالات وتغيير مستحقات الإجازة.', - 'vacay.fuseInfo4': 'تتم مشاركة الإعدادات مثل العطل الرسمية وعطل الشركة.', - 'vacay.fuseInfo5': 'يمكن فك الدمج في أي وقت من قبل أي طرف. ستبقى إدخالاتك محفوظة.', - - // Atlas addon - 'atlas.subtitle': 'بصمتك السفرية حول العالم', - 'atlas.countries': 'الدول', - 'atlas.trips': 'الرحلات', - 'atlas.places': 'الأماكن', - 'atlas.unmark': 'إزالة', - 'atlas.confirmMark': 'تعيين هذا البلد كمُزار؟', - 'atlas.confirmUnmark': 'إزالة هذا البلد من قائمة المُزارة؟', - 'atlas.confirmUnmarkRegion': 'إزالة هذه المنطقة من قائمة المُزارة؟', - 'atlas.markVisited': 'تعيين كمُزار', - 'atlas.markVisitedHint': 'إضافة هذا البلد إلى قائمة المُزارة', - 'atlas.markRegionVisitedHint': 'إضافة هذه المنطقة إلى قائمة المُزارة', - 'atlas.addToBucket': 'إضافة إلى قائمة الأمنيات', - 'atlas.addPoi': 'إضافة مكان', - 'atlas.searchCountry': 'ابحث عن دولة...', - 'atlas.bucketNamePlaceholder': 'الاسم (بلد، مدينة، مكان…)', - 'atlas.month': 'الشهر', - 'atlas.year': 'السنة', - 'atlas.addToBucketHint': 'حفظ كمكان تريد زيارته', - 'atlas.bucketWhen': 'متى تخطط للزيارة؟', - 'atlas.statsTab': 'الإحصائيات', - 'atlas.bucketTab': 'قائمة الأمنيات', - 'atlas.addBucket': 'إضافة إلى قائمة الأمنيات', - 'atlas.bucketNotesPlaceholder': 'ملاحظات (اختياري)', - 'atlas.bucketEmpty': 'قائمة أمنياتك فارغة', - 'atlas.bucketEmptyHint': 'أضف أماكن تحلم بزيارتها', - 'atlas.days': 'الأيام', - 'atlas.visitedCountries': 'الدول التي تمت زيارتها', - 'atlas.cities': 'المدن', - 'atlas.noData': 'لا توجد بيانات سفر بعد', - 'atlas.noDataHint': 'أنشئ رحلة وأضف أماكن لرؤية خريطتك العالمية', - 'atlas.lastTrip': 'آخر رحلة', - 'atlas.nextTrip': 'الرحلة القادمة', - 'atlas.daysLeft': 'يوم متبقٍ', - 'atlas.streak': 'سلسلة', - 'atlas.years': 'سنوات', - 'atlas.yearInRow': 'سنة متتالية', - 'atlas.yearsInRow': 'سنوات متتالية', - 'atlas.tripIn': 'رحلة في', - 'atlas.tripsIn': 'رحلات في', - 'atlas.since': 'منذ', - 'atlas.europe': 'أوروبا', - 'atlas.asia': 'آسيا', - 'atlas.northAmerica': 'أمريكا الشمالية', - 'atlas.southAmerica': 'أمريكا الجنوبية', - 'atlas.africa': 'أفريقيا', - 'atlas.oceania': 'أوقيانوسيا', - 'atlas.other': 'أخرى', - 'atlas.firstVisit': 'أول رحلة', - 'atlas.lastVisitLabel': 'آخر رحلة', - 'atlas.tripSingular': 'رحلة', - 'atlas.tripPlural': 'رحلات', - 'atlas.placeVisited': 'مكان تمت زيارته', - 'atlas.placesVisited': 'أماكن تمت زيارتها', - - // Trip Planner - 'trip.tabs.plan': 'الخطة', - 'trip.tabs.transports': 'المواصلات', - 'trip.tabs.reservations': 'الحجوزات', - 'trip.tabs.reservationsShort': 'حجز', - 'trip.tabs.packing': 'قائمة التجهيز', - 'trip.tabs.packingShort': 'تجهيز', - 'trip.tabs.lists': 'القوائم', - 'trip.tabs.listsShort': 'القوائم', - 'trip.tabs.budget': 'الميزانية', - 'trip.tabs.files': 'الملفات', - 'trip.loading': 'جارٍ تحميل الرحلة...', - 'trip.loadingPhotos': 'جارٍ تحميل صور الأماكن...', - 'trip.mobilePlan': 'الخطة', - 'trip.mobilePlaces': 'الأماكن', - 'trip.toast.placeUpdated': 'تم تحديث المكان', - 'trip.toast.placeAdded': 'تمت إضافة المكان', - 'trip.toast.placeDeleted': 'تم حذف المكان', - 'trip.toast.selectDay': 'يرجى اختيار يوم أولًا', - 'trip.toast.assignedToDay': 'تم إسناد المكان إلى اليوم', - 'trip.toast.reorderError': 'فشل إعادة الترتيب', - 'trip.toast.reservationUpdated': 'تم تحديث الحجز', - 'trip.toast.reservationAdded': 'تمت إضافة الحجز', - 'trip.toast.deleted': 'تم الحذف', - 'trip.confirm.deletePlace': 'هل تريد حذف هذا المكان؟', - 'trip.confirm.deletePlaces': 'حذف {count} أماكن؟', - 'trip.toast.placesDeleted': 'تم حذف {count} أماكن', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'لا توجد أماكن مخططة لهذا اليوم', - 'dayplan.addNote': 'إضافة ملاحظة', - 'dayplan.editNote': 'تعديل الملاحظة', - 'dayplan.noteAdd': 'إضافة ملاحظة', - 'dayplan.noteEdit': 'تعديل الملاحظة', - 'dayplan.noteTitle': 'ملاحظة', - 'dayplan.noteSubtitle': 'ملاحظة يومية', - 'dayplan.totalCost': 'إجمالي التكلفة', - 'dayplan.days': 'الأيام', - 'dayplan.dayN': 'اليوم {n}', - 'dayplan.calculating': 'جارٍ الحساب...', - 'dayplan.route': 'المسار', - 'dayplan.optimize': 'تحسين', - 'dayplan.optimized': 'تم تحسين المسار', - 'dayplan.routeError': 'فشل حساب المسار', - 'dayplan.toast.needTwoPlaces': 'يلزم مكانان على الأقل لتحسين المسار', - 'dayplan.toast.routeOptimized': 'تم تحسين المسار', - 'dayplan.toast.noGeoPlaces': 'لم يتم العثور على أماكن بإحداثيات لحساب المسار', - 'dayplan.confirmed': 'مؤكد', - 'dayplan.pendingRes': 'قيد الانتظار', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'تصدير خطة اليوم بصيغة PDF', - 'dayplan.pdfError': 'فشل تصدير PDF', - 'dayplan.cannotReorderTransport': 'لا يمكن إعادة ترتيب الحجوزات ذات الوقت الثابت', - 'dayplan.confirmRemoveTimeTitle': 'إزالة الوقت؟', - 'dayplan.confirmRemoveTimeBody': 'هذا المكان له وقت ثابت ({time}). نقله سيزيل الوقت ويسمح بالترتيب الحر.', - 'dayplan.confirmRemoveTimeAction': 'إزالة الوقت ونقل', - 'dayplan.cannotDropOnTimed': 'لا يمكن وضع العناصر بين الإدخالات المرتبطة بوقت', - 'dayplan.cannotBreakChronology': 'سيؤدي هذا إلى كسر الترتيب الزمني للعناصر والحجوزات المجدولة', - - // Places Sidebar - 'places.addPlace': 'إضافة مكان/نشاط', - 'places.importFile': 'استيراد ملف', - 'places.sidebarDrop': 'أفلت للاستيراد', - 'places.importFileHint': 'استورد ملفات .gpx أو .kml أو .kmz من أدوات مثل Google My Maps وGoogle Earth أو جهاز تتبع GPS.', - 'places.importFileDropHere': 'انقر لاختيار ملف أو اسحبه وأفلته هنا', - 'places.importFileDropActive': 'أفلت الملف للاختيار', - 'places.importFileUnsupported': 'نوع الملف غير مدعوم. استخدم .gpx أو .kml أو .kmz.', - 'places.importFileTooLarge': 'الملف كبير جدًا. الحد الأقصى لحجم الرفع هو {maxMb} MB.', - 'places.importFileError': 'فشل الاستيراد', - 'places.importAllSkipped': 'جميع الأماكن موجودة بالفعل في الرحلة.', - 'places.gpxImported': 'تم استيراد {count} مكان من GPX', - 'places.gpxImportTypes': 'ما الذي تريد استيراده؟', - 'places.gpxImportWaypoints': 'نقاط الطريق', - 'places.gpxImportRoutes': 'المسارات', - 'places.gpxImportTracks': 'المسارات (مع هندسة الطريق)', - 'places.gpxImportNoneSelected': 'اختر نوعاً واحداً على الأقل للاستيراد.', - 'places.kmlImportTypes': 'ما الذي تريد استيراده؟', - 'places.kmlImportPoints': 'نقاط (Placemarks)', - 'places.kmlImportPaths': 'مسارات (LineStrings)', - 'places.kmlImportNoneSelected': 'اختر نوعًا واحدًا على الأقل.', - 'places.selectionCount': '{count} محدد', - 'places.deleteSelected': 'حذف المحدد', - 'places.kmlKmzImported': 'تم استيراد {count} مكان من KMZ/KML', - 'places.urlResolved': 'تم استيراد المكان من الرابط', - 'places.importList': 'استيراد قائمة', - 'places.kmlKmzSummaryValues': 'علامات المواضع: {total} • تم الاستيراد: {created} • تم التجاوز: {skipped}', - 'places.importGoogleList': 'قائمة Google', - 'places.importNaverList': 'قائمة Naver', - 'places.googleListHint': 'الصق رابط قائمة Google Maps المشتركة لاستيراد جميع الأماكن.', - 'places.googleListImported': 'تم استيراد {count} أماكن من "{list}"', - 'places.googleListError': 'فشل استيراد قائمة Google Maps', - 'places.naverListHint': 'الصق رابط قائمة Naver Maps مشتركة لاستيراد جميع الأماكن.', - 'places.naverListImported': 'تم استيراد {count} مكان من "{list}"', - 'places.naverListError': 'فشل استيراد قائمة Naver Maps', - 'places.viewDetails': 'عرض التفاصيل', - 'places.assignToDay': 'إلى أي يوم تريد الإضافة؟', - 'places.all': 'الكل', - 'places.unplanned': 'غير مخطط', - 'places.filterTracks': 'المسارات', - 'places.search': 'ابحث عن أماكن...', - 'places.allCategories': 'كل الفئات', - 'places.categoriesSelected': 'فئات', - 'places.clearFilter': 'مسح الفلتر', - 'places.count': '{count} أماكن', - 'places.countSingular': 'مكان واحد', - 'places.allPlanned': 'تم تخطيط جميع الأماكن', - 'places.noneFound': 'لم يتم العثور على أماكن', - 'places.editPlace': 'تعديل المكان', - 'places.formName': 'الاسم', - 'places.formNamePlaceholder': 'مثال: برج إيفل', - 'places.formDescription': 'الوصف', - 'places.formDescriptionPlaceholder': 'وصف مختصر...', - 'places.formAddress': 'العنوان', - 'places.formAddressPlaceholder': 'الشارع، المدينة، البلد', - 'places.formLat': 'خط العرض (مثال: 48.8566)', - 'places.formLng': 'خط الطول (مثال: 2.3522)', - 'places.formCategory': 'الفئة', - 'places.noCategory': 'بلا فئة', - 'places.categoryNamePlaceholder': 'اسم الفئة', - 'places.formTime': 'الوقت', - 'places.startTime': 'البداية', - 'places.endTime': 'النهاية', - 'places.endTimeBeforeStart': 'وقت النهاية قبل وقت البداية', - 'places.timeCollision': 'تداخل في الوقت مع:', - 'places.formWebsite': 'الموقع الإلكتروني', - 'places.formNotes': 'ملاحظات', - 'places.formNotesPlaceholder': 'ملاحظات شخصية...', - 'places.formReservation': 'حجز', - 'places.reservationNotesPlaceholder': 'ملاحظات الحجز، رقم التأكيد...', - 'places.mapsSearchPlaceholder': 'ابحث عن أماكن...', - 'places.mapsSearchError': 'فشل البحث عن المكان.', - 'places.loadingDetails': 'جارٍ تحميل تفاصيل المكان…', - 'places.osmHint': 'يتم البحث عبر OpenStreetMap (بدون صور أو ساعات عمل أو تقييمات). أضف مفتاح Google API في الإعدادات للحصول على جميع التفاصيل.', - 'places.osmActive': 'البحث عبر OpenStreetMap (بدون صور أو تقييمات أو ساعات عمل). أضف مفتاح Google API في الإعدادات لبيانات موسعة.', - 'places.categoryCreateError': 'فشل إنشاء الفئة', - 'places.nameRequired': 'يرجى إدخال اسم', - 'places.saveError': 'فشل الحفظ', - - // Place Inspector - 'inspector.opened': 'مفتوح', - 'inspector.closed': 'مغلق', - 'inspector.openingHours': 'ساعات العمل', - 'inspector.showHours': 'عرض ساعات العمل', - 'inspector.files': 'الملفات', - 'inspector.filesCount': '{count} ملفات', - 'inspector.removeFromDay': 'إزالة من اليوم', - 'inspector.remove': 'إزالة', - 'inspector.addToDay': 'إضافة إلى اليوم', - 'inspector.confirmedRes': 'حجز مؤكد', - 'inspector.pendingRes': 'حجز قيد الانتظار', - 'inspector.google': 'فتح في Google Maps', - 'inspector.website': 'فتح الموقع الإلكتروني', - 'inspector.addRes': 'حجز', - 'inspector.editRes': 'تعديل الحجز', - 'inspector.participants': 'المشاركون', - 'inspector.trackStats': 'بيانات المسار', - - // Reservations - 'reservations.title': 'الحجوزات', - 'reservations.empty': 'لا توجد حجوزات بعد', - 'reservations.emptyHint': 'أضف حجوزات للرحلات الجوية والفنادق وغير ذلك', - 'reservations.add': 'إضافة حجز', - 'reservations.addManual': 'حجز يدوي', - 'reservations.placeHint': 'نصيحة: يُفضل إنشاء الحجوزات مباشرة من مكان لربطها بخطة اليوم.', - 'reservations.confirmed': 'مؤكد', - 'reservations.pending': 'قيد الانتظار', - 'reservations.summary': '{confirmed} مؤكدة، {pending} قيد الانتظار', - 'reservations.fromPlan': 'من الخطة', - 'reservations.showFiles': 'عرض الملفات', - 'reservations.editTitle': 'تعديل الحجز', - 'reservations.status': 'الحالة', - 'reservations.datetime': 'التاريخ والوقت', - 'reservations.startTime': 'وقت البداية', - 'reservations.endTime': 'وقت النهاية', - 'reservations.date': 'التاريخ', - 'reservations.time': 'الوقت', - 'reservations.timeAlt': 'الوقت (بديل، مثل 19:30)', - 'reservations.notes': 'ملاحظات', - 'reservations.notesPlaceholder': 'ملاحظات إضافية...', - 'reservations.meta.airline': 'شركة الطيران', - 'reservations.meta.flightNumber': 'رقم الرحلة', - 'reservations.meta.from': 'من', - 'reservations.meta.to': 'إلى', - 'reservations.needsReview': 'مراجعة', - 'reservations.needsReviewHint': 'تعذّر مطابقة المطار تلقائياً — يرجى تأكيد الموقع.', - 'reservations.searchLocation': 'ابحث عن محطة، ميناء، عنوان...', - 'airport.searchPlaceholder': 'رمز المطار أو المدينة (مثل FRA)', - 'map.connections': 'الاتصالات', - 'map.showConnections': 'عرض مسارات الحجوزات', - 'map.hideConnections': 'إخفاء مسارات الحجوزات', - 'settings.bookingLabels': 'تسميات مسارات الحجوزات', - 'settings.bookingLabelsHint': 'عرض أسماء المحطات/المطارات على الخريطة. عند الإيقاف، يتم عرض الرمز فقط.', - 'reservations.meta.trainNumber': 'رقم القطار', - 'reservations.meta.platform': 'المنصة', - 'reservations.meta.seat': 'المقعد', - 'reservations.meta.checkIn': 'تسجيل الوصول', - 'reservations.meta.checkInUntil': 'تسجيل الدخول حتى', - 'reservations.meta.checkOut': 'تسجيل المغادرة', - 'reservations.meta.linkAccommodation': 'الإقامة', - 'reservations.meta.pickAccommodation': 'ربط بالإقامة', - 'reservations.meta.noAccommodation': 'لا يوجد', - 'reservations.meta.hotelPlace': 'الإقامة', - 'reservations.meta.pickHotel': 'اختر الإقامة', - 'reservations.meta.fromDay': 'من', - 'reservations.meta.toDay': 'إلى', - 'reservations.meta.selectDay': 'اختر يومًا', - 'reservations.type.flight': 'رحلة جوية', - 'reservations.type.hotel': 'إقامة', - 'reservations.type.restaurant': 'مطعم', - 'reservations.type.train': 'قطار', - 'reservations.type.car': 'سيارة', - 'reservations.type.cruise': 'رحلة بحرية', - 'reservations.type.event': 'فعالية', - 'reservations.type.tour': 'جولة', - 'reservations.type.other': 'أخرى', - 'reservations.confirm.delete': 'هل تريد حذف الحجز "{name}"؟', - 'reservations.confirm.deleteTitle': 'حذف الحجز؟', - 'reservations.confirm.deleteBody': 'سيتم حذف "{name}" نهائيًا.', - 'reservations.toast.updated': 'تم تحديث الحجز', - 'reservations.toast.removed': 'تم حذف الحجز', - 'reservations.toast.fileUploaded': 'تم رفع الملف', - 'reservations.toast.uploadError': 'فشل الرفع', - 'reservations.newTitle': 'حجز جديد', - 'reservations.bookingType': 'نوع الحجز', - 'reservations.titleLabel': 'العنوان', - 'reservations.titlePlaceholder': 'مثال: Lufthansa LH123، فندق أدلون، ...', - 'reservations.locationAddress': 'الموقع / العنوان', - 'reservations.locationPlaceholder': 'العنوان، المطار، الفندق...', - 'reservations.confirmationCode': 'رمز الحجز', - 'reservations.confirmationPlaceholder': 'مثال: ABC12345', - 'reservations.day': 'اليوم', - 'reservations.noDay': 'بلا يوم', - 'reservations.place': 'المكان', - 'reservations.noPlace': 'بلا مكان', - 'reservations.pendingSave': 'سيتم الحفظ…', - 'reservations.uploading': 'جارٍ الرفع...', - 'reservations.attachFile': 'إرفاق ملف', - 'reservations.linkExisting': 'ربط ملف موجود', - 'reservations.toast.saveError': 'فشل الحفظ', - 'reservations.toast.updateError': 'فشل التحديث', - 'reservations.toast.deleteError': 'فشل الحذف', - 'reservations.confirm.remove': 'إزالة الحجز "{name}"؟', - 'reservations.linkAssignment': 'ربط بخطة اليوم', - 'reservations.pickAssignment': 'اختر عنصرًا من خطتك...', - 'reservations.noAssignment': 'بلا ربط', - 'reservations.price': 'السعر', - 'reservations.budgetCategory': 'فئة الميزانية', - 'reservations.budgetCategoryPlaceholder': 'مثال: المواصلات، الإقامة', - 'reservations.budgetCategoryAuto': 'تلقائي (حسب نوع الحجز)', - 'reservations.budgetHint': 'سيتم إنشاء إدخال في الميزانية تلقائيًا عند الحفظ.', - 'reservations.departureDate': 'المغادرة', - 'reservations.arrivalDate': 'الوصول', - 'reservations.departureTime': 'وقت المغادرة', - 'reservations.arrivalTime': 'وقت الوصول', - 'reservations.pickupDate': 'الاستلام', - 'reservations.returnDate': 'الإرجاع', - 'reservations.pickupTime': 'وقت الاستلام', - 'reservations.returnTime': 'وقت الإرجاع', - 'reservations.endDate': 'تاريخ الانتهاء', - 'reservations.meta.departureTimezone': 'TZ المغادرة', - 'reservations.meta.arrivalTimezone': 'TZ الوصول', - 'reservations.span.departure': 'المغادرة', - 'reservations.span.arrival': 'الوصول', - 'reservations.span.inTransit': 'في الطريق', - 'reservations.span.pickup': 'الاستلام', - 'reservations.span.return': 'الإرجاع', - 'reservations.span.active': 'نشط', - 'reservations.span.start': 'البداية', - 'reservations.span.end': 'النهاية', - 'reservations.span.ongoing': 'جارٍ', - 'reservations.validation.endBeforeStart': 'يجب أن يكون تاريخ/وقت الانتهاء بعد تاريخ/وقت البدء', - 'reservations.addBooking': 'إضافة حجز', - - // Budget - 'budget.title': 'الميزانية', - 'budget.exportCsv': 'تصدير CSV', - 'budget.emptyTitle': 'لم يتم إنشاء ميزانية بعد', - 'budget.emptyText': 'أنشئ فئات وإدخالات لتخطيط ميزانية سفرك', - 'budget.emptyPlaceholder': 'أدخل اسم الفئة...', - 'budget.createCategory': 'إنشاء فئة', - 'budget.category': 'الفئة', - 'budget.categoryName': 'اسم الفئة', - 'budget.table.name': 'الاسم', - 'budget.table.total': 'الإجمالي', - 'budget.table.persons': 'الأشخاص', - 'budget.table.days': 'الأيام', - 'budget.table.perPerson': 'لكل شخص', - 'budget.table.perDay': 'لكل يوم', - 'budget.table.perPersonDay': 'لكل شخص / يوم', - 'budget.table.note': 'ملاحظة', - 'budget.table.date': 'التاريخ', - 'budget.newEntry': 'إدخال جديد', - 'budget.defaultEntry': 'إدخال جديد', - 'budget.defaultCategory': 'فئة جديدة', - 'budget.total': 'الإجمالي', - 'budget.totalBudget': 'إجمالي الميزانية', - 'budget.byCategory': 'حسب الفئة', - 'budget.editTooltip': 'انقر للتعديل', - 'budget.linkedToReservation': 'مرتبط بحجز — عدّل الاسم هناك', - 'budget.confirm.deleteCategory': 'هل تريد حذف الفئة "{name}" مع {count} إدخالات؟', - 'budget.deleteCategory': 'حذف الفئة', - 'budget.perPerson': 'لكل شخص', - 'budget.paid': 'مدفوع', - 'budget.open': 'مفتوح', - 'budget.noMembers': 'لا أعضاء معينون', - 'budget.settlement': 'التسوية', - 'budget.settlementInfo': 'انقر على صورة العضو في بند الميزانية لتحديده باللون الأخضر — وهذا يعني أنه دفع. ثم تُظهر التسوية من يدين لمن وبكم.', - 'budget.netBalances': 'الأرصدة الصافية', - - // Files - 'files.title': 'الملفات', - 'files.pageTitle': 'الملفات والمستندات', - 'files.subtitle': '{count} ملف لـ {trip}', - 'files.download': 'تنزيل', - 'files.openError': 'تعذر فتح الملف', - 'files.downloadPdf': 'تنزيل PDF', - 'files.count': '{count} ملفات', - 'files.countSingular': 'ملف واحد', - 'files.uploaded': 'تم رفع {count}', - 'files.uploadError': 'فشل الرفع', - 'files.dropzone': 'أسقط الملفات هنا', - 'files.dropzoneHint': 'أو انقر للتصفح', - 'files.allowedTypes': 'صور، PDF، DOC، DOCX، XLS، XLSX، TXT، CSV · حد أقصى 50 ميغابايت', - 'files.uploading': 'جارٍ الرفع...', - 'files.filterAll': 'الكل', - 'files.filterPdf': 'ملفات PDF', - 'files.filterImages': 'الصور', - 'files.filterDocs': 'المستندات', - 'files.filterCollab': 'ملاحظات Collab', - 'files.sourceCollab': 'من ملاحظات Collab', - 'files.empty': 'لا توجد ملفات بعد', - 'files.emptyHint': 'ارفع ملفات لإرفاقها برحلتك', - 'files.openTab': 'فتح في تبويب جديد', - 'files.confirm.delete': 'هل تريد حذف هذا الملف؟', - 'files.toast.deleted': 'تم حذف الملف', - 'files.toast.deleteError': 'فشل حذف الملف', - 'files.sourcePlan': 'خطة اليوم', - 'files.sourceBooking': 'الحجز', - 'files.sourceTransport': 'النقل', - 'files.attach': 'إرفاق', - 'files.pasteHint': 'يمكنك أيضًا لصق الصور من الحافظة (Ctrl+V)', - 'files.trash': 'سلة المهملات', - 'files.trashEmpty': 'سلة المهملات فارغة', - 'files.emptyTrash': 'إفراغ السلة', - 'files.restore': 'استعادة', - 'files.star': 'تمييز', - 'files.unstar': 'إلغاء التمييز', - 'files.assign': 'إسناد', - 'files.assignTitle': 'إسناد ملف', - 'files.assignPlace': 'المكان', - 'files.assignBooking': 'الحجز', - 'files.assignTransport': 'النقل', - 'files.unassigned': 'غير مسند', - 'files.unlink': 'إزالة الرابط', - 'files.toast.trashed': 'تم النقل إلى سلة المهملات', - 'files.toast.restored': 'تمت استعادة الملف', - 'files.toast.trashEmptied': 'تم إفراغ سلة المهملات', - 'files.toast.assigned': 'تم إسناد الملف', - 'files.toast.assignError': 'فشل الإسناد', - 'files.toast.restoreError': 'فشلت الاستعادة', - 'files.confirm.permanentDelete': 'حذف هذا الملف نهائيًا؟ لا يمكن التراجع عن ذلك.', - 'files.confirm.emptyTrash': 'حذف جميع ملفات سلة المهملات نهائيًا؟ لا يمكن التراجع عن ذلك.', - 'files.noteLabel': 'ملاحظة', - 'files.notePlaceholder': 'أضف ملاحظة...', - - // Packing - 'packing.title': 'قائمة التجهيز', - 'packing.empty': 'قائمة التجهيز فارغة', - 'packing.import': 'استيراد', - 'packing.importTitle': 'استيراد قائمة التعبئة', - 'packing.importHint': 'عنصر واحد لكل سطر. يمكن إضافة الفئة والكمية مفصولة بفاصلة أو فاصلة منقوطة أو علامة تبويب: الاسم، الفئة، الكمية', - 'packing.importPlaceholder': 'فرشاة أسنان\nواقي شمس، نظافة\nقمصان، ملابس، 5\nجواز سفر، مستندات', - 'packing.importCsv': 'تحميل CSV/TXT', - 'packing.importAction': 'استيراد {count}', - 'packing.importSuccess': 'تم استيراد {count} عنصر', - 'packing.importError': 'فشل الاستيراد', - 'packing.importEmpty': 'لا توجد عناصر للاستيراد', - 'packing.progress': '{packed} من {total} جُهّز ({percent}%)', - 'packing.clearChecked': 'إزالة {count} محدد', - 'packing.clearCheckedShort': 'إزالة {count}', - 'packing.suggestions': 'اقتراحات', - 'packing.suggestionsTitle': 'إضافة اقتراحات', - 'packing.allSuggested': 'تمت إضافة جميع الاقتراحات', - 'packing.allPacked': 'تم تجهيز الكل!', - 'packing.addPlaceholder': 'إضافة عنصر جديد...', - 'packing.categoryPlaceholder': 'الفئة...', - 'packing.filterAll': 'الكل', - 'packing.filterOpen': 'مفتوح', - 'packing.filterDone': 'تم', - 'packing.emptyTitle': 'قائمة التجهيز فارغة', - 'packing.emptyHint': 'أضف عناصر أو استخدم الاقتراحات', - 'packing.emptyFiltered': 'لا توجد عناصر مطابقة لهذا الفلتر', - 'packing.menuRename': 'إعادة تسمية', - 'packing.menuCheckAll': 'تحديد الكل', - 'packing.menuUncheckAll': 'إلغاء تحديد الكل', - 'packing.menuDeleteCat': 'حذف الفئة', - 'packing.noMembers': 'لا أعضاء', - 'packing.addItem': 'إضافة عنصر', - 'packing.addItemPlaceholder': 'اسم العنصر...', - 'packing.addCategory': 'إضافة فئة', - 'packing.newCategoryPlaceholder': 'اسم الفئة (مثال: ملابس)', - 'packing.applyTemplate': 'تطبيق قالب', - 'packing.template': 'قالب', - 'packing.templateApplied': 'تمت إضافة {count} عنصر من القالب', - 'packing.templateError': 'فشل تطبيق القالب', - 'packing.saveAsTemplate': 'حفظ كقالب', - 'packing.templateName': 'اسم القالب', - 'packing.templateSaved': 'تم حفظ قائمة الحقائب كقالب', - 'packing.bags': 'أمتعة', - 'packing.noBag': 'غير معيّن', - 'packing.totalWeight': 'الوزن الإجمالي', - 'packing.bagName': 'الاسم...', - 'packing.addBag': 'إضافة أمتعة', - 'packing.changeCategory': 'تغيير الفئة', - 'packing.confirm.clearChecked': 'هل تريد إزالة {count} عنصر محدد؟', - 'packing.confirm.deleteCat': 'هل تريد حذف الفئة "{name}" مع {count} عنصر؟', - 'packing.defaultCategory': 'أخرى', - 'packing.toast.saveError': 'فشل الحفظ', - 'packing.toast.deleteError': 'فشل الحذف', - 'packing.toast.renameError': 'فشلت إعادة التسمية', - 'packing.toast.addError': 'فشلت الإضافة', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'جواز السفر', category: 'المستندات' }, - { name: 'بطاقة الهوية', category: 'المستندات' }, - { name: 'تأمين السفر', category: 'المستندات' }, - { name: 'تذاكر الطيران', category: 'المستندات' }, - { name: 'بطاقة ائتمان', category: 'المالية' }, - { name: 'نقد', category: 'المالية' }, - { name: 'تأشيرة', category: 'المستندات' }, - { name: 'قمصان', category: 'الملابس' }, - { name: 'بنطلونات', category: 'الملابس' }, - { name: 'ملابس داخلية', category: 'الملابس' }, - { name: 'جوارب', category: 'الملابس' }, - { name: 'جاكيت', category: 'الملابس' }, - { name: 'ملابس نوم', category: 'الملابس' }, - { name: 'ملابس سباحة', category: 'الملابس' }, - { name: 'معطف مطر', category: 'الملابس' }, - { name: 'أحذية مريحة', category: 'الملابس' }, - { name: 'فرشاة أسنان', category: 'أدوات العناية' }, - { name: 'معجون أسنان', category: 'أدوات العناية' }, - { name: 'شامبو', category: 'أدوات العناية' }, - { name: 'مزيل عرق', category: 'أدوات العناية' }, - { name: 'واقي شمس', category: 'أدوات العناية' }, - { name: 'شفرة حلاقة', category: 'أدوات العناية' }, - { name: 'شاحن', category: 'الإلكترونيات' }, - { name: 'بطارية محمولة', category: 'الإلكترونيات' }, - { name: 'سماعات', category: 'الإلكترونيات' }, - { name: 'محول سفر', category: 'الإلكترونيات' }, - { name: 'كاميرا', category: 'الإلكترونيات' }, - { name: 'مسكنات ألم', category: 'الصحة' }, - { name: 'لاصقات جروح', category: 'الصحة' }, - { name: 'مطهر', category: 'الصحة' }, - ], - - // Members / Sharing - 'members.shareTrip': 'مشاركة الرحلة', - 'members.inviteUser': 'دعوة مستخدم', - 'members.selectUser': 'اختر مستخدمًا…', - 'members.invite': 'دعوة', - 'members.allHaveAccess': 'جميع المستخدمين لديهم صلاحية الوصول بالفعل.', - 'members.access': 'الصلاحية', - 'members.person': 'شخص', - 'members.persons': 'أشخاص', - 'members.you': 'أنت', - 'members.owner': 'المالك', - 'members.leaveTrip': 'مغادرة الرحلة', - 'members.removeAccess': 'إزالة الصلاحية', - 'members.confirmLeave': 'مغادرة الرحلة؟ ستفقد صلاحية الوصول.', - 'members.confirmRemove': 'إزالة صلاحية هذا المستخدم؟', - 'members.loadError': 'فشل تحميل الأعضاء', - 'members.added': 'تمت الإضافة', - 'members.addError': 'فشلت الإضافة', - 'members.removed': 'تمت إزالة العضو', - 'members.removeError': 'فشلت الإزالة', - - // Categories (Admin) - 'categories.title': 'الفئات', - 'categories.subtitle': 'إدارة فئات الأماكن', - 'categories.new': 'فئة جديدة', - 'categories.empty': 'لا توجد فئات بعد', - 'categories.namePlaceholder': 'اسم الفئة', - 'categories.icon': 'الأيقونة', - 'categories.color': 'اللون', - 'categories.customColor': 'اختيار لون مخصص', - 'categories.preview': 'معاينة', - 'categories.defaultName': 'فئة', - 'categories.update': 'تحديث', - 'categories.create': 'إنشاء', - 'categories.confirm.delete': 'حذف الفئة؟ لن يتم حذف الأماكن التابعة لهذه الفئة.', - 'categories.toast.loadError': 'فشل تحميل الفئات', - 'categories.toast.nameRequired': 'يرجى إدخال اسم', - 'categories.toast.updated': 'تم تحديث الفئة', - 'categories.toast.created': 'تم إنشاء الفئة', - 'categories.toast.saveError': 'فشل الحفظ', - 'categories.toast.deleted': 'تم حذف الفئة', - 'categories.toast.deleteError': 'فشل الحذف', - - // Backup (Admin) - 'backup.title': 'النسخ الاحتياطي', - 'backup.subtitle': 'قاعدة البيانات وجميع الملفات المرفوعة', - 'backup.refresh': 'تحديث', - 'backup.upload': 'رفع نسخة احتياطية', - 'backup.uploading': 'جارٍ الرفع…', - 'backup.create': 'إنشاء نسخة', - 'backup.creating': 'جارٍ الإنشاء…', - 'backup.empty': 'لا توجد نسخ احتياطية بعد', - 'backup.createFirst': 'إنشاء أول نسخة', - 'backup.download': 'تنزيل', - 'backup.restore': 'استعادة', - 'backup.confirm.restore': 'استعادة النسخة "{name}"؟\n\nسيتم استبدال جميع البيانات الحالية بالنسخة.', - 'backup.confirm.uploadRestore': 'رفع واستعادة النسخة "{name}"؟\n\nسيتم الكتابة فوق جميع البيانات الحالية.', - 'backup.confirm.delete': 'حذف النسخة "{name}"؟', - 'backup.toast.loadError': 'فشل تحميل النسخ الاحتياطية', - 'backup.toast.created': 'تم إنشاء النسخة الاحتياطية بنجاح', - 'backup.toast.createError': 'فشل إنشاء النسخة', - 'backup.toast.restored': 'تمت الاستعادة. ستُعاد تحميل الصفحة…', - 'backup.toast.restoreError': 'فشلت الاستعادة', - 'backup.toast.uploadError': 'فشل الرفع', - 'backup.toast.deleted': 'تم حذف النسخة', - 'backup.toast.deleteError': 'فشل الحذف', - 'backup.toast.downloadError': 'فشل التنزيل', - 'backup.toast.settingsSaved': 'تم حفظ إعدادات النسخ الاحتياطي التلقائي', - 'backup.toast.settingsError': 'فشل حفظ الإعدادات', - 'backup.auto.title': 'النسخ الاحتياطي التلقائي', - 'backup.auto.subtitle': 'نسخ احتياطي تلقائي وفق جدول زمني', - 'backup.auto.enable': 'تفعيل النسخ التلقائي', - 'backup.auto.enableHint': 'سيتم إنشاء نسخ احتياطية تلقائيًا وفق الجدول المختار', - 'backup.auto.interval': 'الفترة', - 'backup.auto.hour': 'التنفيذ في الساعة', - 'backup.auto.hourHint': 'التوقيت المحلي للخادم (تنسيق {format})', - 'backup.auto.dayOfWeek': 'يوم الأسبوع', - 'backup.auto.dayOfMonth': 'يوم الشهر', - 'backup.auto.dayOfMonthHint': 'محدود بين 1–28 للتوافق مع جميع الأشهر', - 'backup.auto.scheduleSummary': 'الجدول', - 'backup.auto.summaryDaily': 'كل يوم الساعة {hour}:00', - 'backup.auto.summaryWeekly': 'كل {day} الساعة {hour}:00', - 'backup.auto.summaryMonthly': 'اليوم {day} من كل شهر الساعة {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'النسخ الاحتياطي التلقائي مُعدّ عبر متغيرات بيئة Docker. لتعديل الإعدادات، حدّث docker-compose.yml وأعد تشغيل الحاوية.', - 'backup.auto.copyEnv': 'نسخ متغيرات بيئة Docker', - 'backup.auto.envCopied': 'تم نسخ متغيرات بيئة Docker إلى الحافظة', - 'backup.auto.keepLabel': 'حذف النسخ القديمة بعد', - 'backup.dow.sunday': 'أحد', - 'backup.dow.monday': 'إثن', - 'backup.dow.tuesday': 'ثلا', - 'backup.dow.wednesday': 'أرب', - 'backup.dow.thursday': 'خمي', - 'backup.dow.friday': 'جمع', - 'backup.dow.saturday': 'سبت', - 'backup.interval.hourly': 'كل ساعة', - 'backup.interval.daily': 'يوميًا', - 'backup.interval.weekly': 'أسبوعيًا', - 'backup.interval.monthly': 'شهريًا', - 'backup.keep.1day': 'يوم واحد', - 'backup.keep.3days': '3 أيام', - 'backup.keep.7days': '7 أيام', - 'backup.keep.14days': '14 يومًا', - 'backup.keep.30days': '30 يومًا', - 'backup.keep.forever': 'الاحتفاظ للأبد', - - // Photos - 'photos.title': 'صور', - 'photos.subtitle': '{count} صورة لـ {trip}', - 'photos.dropHere': 'أسقط الصور هنا...', - 'photos.dropHereActive': 'أسقط الصور هنا', - 'photos.captionForAll': 'تعليق (للجميع)', - 'photos.captionPlaceholder': 'تعليق اختياري...', - 'photos.addCaption': 'إضافة تعليق...', - 'photos.allDays': 'كل الأيام', - 'photos.noPhotos': 'لا توجد صور بعد', - 'photos.uploadHint': 'ارفع صور رحلتك', - 'photos.clickToSelect': 'أو انقر للاختيار', - 'photos.linkPlace': 'ربط بمكان', - 'photos.noPlace': 'بلا مكان', - 'photos.uploadN': 'رفع {n} صورة', - 'photos.linkDay': 'ربط اليوم', - 'photos.noDay': 'لا يوم', - 'photos.dayLabel': 'اليوم {number}', - 'photos.photoSelected': 'صورة محددة', - 'photos.photosSelected': 'صور محددة', - 'photos.fileTypeHint': 'JPG, PNG, WebP · الحد الأقصى 10 ميغابايت · حتى 30 صورة', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'استعادة النسخة الاحتياطية؟', - 'backup.restoreWarning': 'سيتم استبدال جميع البيانات الحالية (الرحلات، الأماكن، المستخدمون، المرفوعات) بالنسخة نهائيًا. لا يمكن التراجع عن ذلك.', - 'backup.restoreTip': 'نصيحة: أنشئ نسخة احتياطية للحالة الحالية قبل الاستعادة.', - 'backup.restoreConfirm': 'نعم، استعادة', - - // PDF - 'pdf.travelPlan': 'خطة السفر', - 'pdf.planned': 'مخطط', - 'pdf.costLabel': 'التكلفة EUR', - 'pdf.preview': 'معاينة PDF', - 'pdf.saveAsPdf': 'حفظ كـ PDF', - - // Planner - 'planner.places': 'الأماكن', - 'planner.bookings': 'الحجوزات', - 'planner.packingList': 'قائمة التجهيز', - 'planner.documents': 'المستندات', - 'planner.dayPlan': 'خطة اليوم', - 'planner.reservations': 'الحجوزات', - 'planner.minTwoPlaces': 'يلزم مكانان على الأقل مع إحداثيات', - 'planner.noGeoPlaces': 'لا توجد أماكن بإحداثيات', - 'planner.routeCalculated': 'تم حساب المسار', - 'planner.routeCalcFailed': 'تعذر حساب المسار', - 'planner.routeError': 'خطأ أثناء حساب المسار', - 'planner.icsExportFailed': 'فشل تصدير ICS', - 'planner.routeOptimized': 'تم تحسين المسار', - 'planner.reservationUpdated': 'تم تحديث الحجز', - 'planner.reservationAdded': 'تمت إضافة الحجز', - 'planner.confirmDeleteReservation': 'حذف الحجز؟', - 'planner.reservationDeleted': 'تم حذف الحجز', - 'planner.days': 'الأيام', - 'planner.allPlaces': 'كل الأماكن', - 'planner.totalPlaces': 'إجمالي {n} أماكن', - 'planner.noDaysPlanned': 'لا توجد أيام مخططة بعد', - 'planner.editTrip': 'تعديل الرحلة ←', - 'planner.placeOne': 'مكان واحد', - 'planner.placeN': '{n} أماكن', - 'planner.addNote': 'إضافة ملاحظة', - 'planner.noEntries': 'لا توجد عناصر لهذا اليوم', - 'planner.addPlace': 'إضافة مكان/نشاط', - 'planner.addPlaceShort': '+ إضافة مكان/نشاط', - 'planner.resPending': 'حجز قيد الانتظار · ', - 'planner.resConfirmed': 'حجز مؤكد · ', - 'planner.notePlaceholder': 'ملاحظة…', - 'planner.noteTimePlaceholder': 'الوقت (اختياري)', - 'planner.noteExamplePlaceholder': 'مثال: S3 الساعة 14:30 من المحطة المركزية، عبّارة من الرصيف 7، استراحة غداء…', - 'planner.totalCost': 'إجمالي التكلفة', - 'planner.searchPlaces': 'ابحث عن أماكن…', - 'planner.allCategories': 'كل الفئات', - 'planner.noPlacesFound': 'لم يتم العثور على أماكن', - 'planner.addFirstPlace': 'أضف أول مكان', - 'planner.noReservations': 'لا توجد حجوزات', - 'planner.addFirstReservation': 'أضف أول حجز', - 'planner.new': 'جديد', - 'planner.addToDay': '+ يوم', - 'planner.calculating': 'جارٍ الحساب…', - 'planner.route': 'المسار', - 'planner.optimize': 'تحسين', - 'planner.openGoogleMaps': 'فتح في Google Maps', - 'planner.selectDayHint': 'اختر يومًا من القائمة اليسرى لعرض خطة اليوم', - 'planner.noPlacesForDay': 'لا توجد أماكن لهذا اليوم بعد', - 'planner.addPlacesLink': 'إضافة أماكن ←', - 'planner.minTotal': 'دقيقة إجمالًا', - 'planner.noReservation': 'لا يوجد حجز', - 'planner.removeFromDay': 'إزالة من اليوم', - 'planner.addToThisDay': 'إضافة إلى اليوم', - 'planner.overview': 'نظرة عامة', - 'planner.noDays': 'لا توجد أيام بعد', - 'planner.editTripToAddDays': 'عدّل الرحلة لإضافة أيام', - 'planner.dayCount': '{n} أيام', - 'planner.clickToUnlock': 'انقر لفتح القفل', - 'planner.keepPosition': 'الحفاظ على الموضع أثناء تحسين المسار', - 'planner.dayDetails': 'تفاصيل اليوم', - 'planner.dayN': 'اليوم {n}', - - // Dashboard Stats - 'stats.countries': 'الدول', - 'stats.cities': 'المدن', - 'stats.trips': 'الرحلات', - 'stats.places': 'الأماكن', - 'stats.worldProgress': 'التقدم حول العالم', - 'stats.visited': 'تمت زيارتها', - 'stats.remaining': 'المتبقية', - 'stats.visitedCountries': 'الدول التي تمت زيارتها', - - // Day Detail Panel - 'day.precipProb': 'احتمال هطول الأمطار', - 'day.precipitation': 'الهطول', - 'day.wind': 'الرياح', - 'day.sunrise': 'شروق الشمس', - 'day.sunset': 'غروب الشمس', - 'day.hourlyForecast': 'التوقعات بالساعة', - 'day.climateHint': 'متوسطات تاريخية — التوقعات الفعلية متاحة خلال 16 يومًا من هذا التاريخ.', - 'day.noWeather': 'لا تتوفر بيانات طقس. أضف مكانًا بإحداثيات.', - 'day.overview': 'ملخص اليوم', - 'day.accommodation': 'الإقامة', - 'day.addAccommodation': 'إضافة إقامة', - 'day.hotelDayRange': 'تطبيق على الأيام', - 'day.noPlacesForHotel': 'أضف أماكن إلى رحلتك أولًا', - 'day.allDays': 'الكل', - 'day.checkIn': 'تسجيل الوصول', - 'day.checkInUntil': 'حتى', - 'day.checkOut': 'تسجيل المغادرة', - 'day.confirmation': 'التأكيد', - 'day.editAccommodation': 'تعديل الإقامة', - 'day.reservations': 'الحجوزات', - - // Memories / Immich - 'memories.title': 'صور', - 'memories.notConnected': 'Immich غير متصل', - 'memories.notConnectedHint': 'قم بتوصيل Immich في الإعدادات لعرض صور رحلتك هنا.', - 'memories.notConnectedMultipleHint': 'قم بتوصيل أحد موفري الصور هؤلاء: {provider_names} في الإعدادات لتتمكن من إضافة صور إلى هذه الرحلة.', - 'memories.noDates': 'أضف تواريخ لرحلتك لتحميل الصور.', - 'memories.noPhotos': 'لم يتم العثور على صور', - 'memories.noPhotosHint': 'لم يتم العثور على صور في Immich لفترة هذه الرحلة.', - 'memories.photosFound': 'صور', - 'memories.fromOthers': 'من آخرين', - 'memories.sharePhotos': 'مشاركة الصور', - 'memories.sharing': 'مشترك', - 'memories.reviewTitle': 'مراجعة صورك', - 'memories.reviewHint': 'انقر على الصور لاستبعادها من المشاركة.', - 'memories.shareCount': 'مشاركة {count} صور', - 'memories.providerUrl': 'عنوان URL للخادم', - 'memories.providerApiKey': 'مفتاح API', - 'memories.providerUsername': 'اسم المستخدم', - 'memories.providerPassword': 'كلمة المرور', - 'memories.providerOTP': 'رمز MFA (إذا كان مفعلاً)', - 'memories.skipSSLVerification': 'تخطي التحقق من شهادة SSL', - 'memories.immichAutoUpload': 'نسخ صور الرحلة إلى Immich عند الرفع', - 'memories.providerUrlHintSynology': 'أدرج مسار تطبيق Photos في URL، مثل https://nas:5001/photo', - 'memories.testConnection': 'اختبار الاتصال', - 'memories.testShort': 'اختبار', - 'memories.testFirst': 'اختبر الاتصال أولاً', - 'memories.connected': 'متصل', - 'memories.disconnected': 'غير متصل', - 'memories.connectionSuccess': 'تم الاتصال بـ Immich', - 'memories.connectionError': 'تعذر الاتصال بـ Immich', - 'memories.saved': 'تم حفظ إعدادات {provider_name}', - 'memories.providerDisconnectedBanner': 'اتصالك بـ {provider_name} مفقود. أعد الاتصال في الإعدادات لعرض الصور.', - 'memories.saveError': 'تعذّر حفظ إعدادات {provider_name}', - 'memories.saveRouteNotConfigured': 'مسار الحفظ غير مهيأ لهذا المزود', - 'memories.testRouteNotConfigured': 'مسار الاختبار غير مهيأ لهذا المزود', - 'memories.fillRequiredFields': 'يرجى ملء جميع الحقول المطلوبة', - 'memories.oldest': 'الأقدم أولاً', - 'memories.newest': 'الأحدث أولاً', - 'memories.allLocations': 'جميع المواقع', - 'memories.addPhotos': 'إضافة صور', - 'memories.linkAlbum': 'ربط ألبوم', - 'memories.selectAlbum': 'اختيار ألبوم Immich', - 'memories.selectAlbumMultiple': 'اختيار ألبوم', - 'memories.noAlbums': 'لم يتم العثور على ألبومات', - 'memories.syncAlbum': 'مزامنة الألبوم', - 'memories.unlinkAlbum': 'إلغاء الربط', - 'memories.photos': 'صور', - 'memories.selectPhotos': 'اختيار صور من Immich', - 'memories.selectPhotosMultiple': 'اختيار الصور', - 'memories.selectHint': 'انقر على الصور لتحديدها.', - 'memories.selected': 'محدد', - 'memories.addSelected': 'إضافة {count} صور', - 'memories.alreadyAdded': 'تمت الإضافة', - 'memories.private': 'خاص', - 'memories.stopSharing': 'إيقاف المشاركة', - 'memories.tripDates': 'تواريخ الرحلة', - 'memories.allPhotos': 'جميع الصور', - 'memories.confirmShareTitle': 'مشاركة مع أعضاء الرحلة؟', - 'memories.confirmShareHint': '{count} صور ستكون مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.', - 'memories.confirmShareButton': 'مشاركة الصور', - 'journey.search.placeholder': 'البحث في الرحلات…', - 'journey.search.noResults': 'لا توجد رحلات تطابق "{query}"', - 'journey.status.archived': 'مؤرشف', - 'journey.settings.endJourney': 'أرشفة الرحلة', - 'journey.settings.reopenJourney': 'استعادة الرحلة', - 'journey.settings.archived': 'تم أرشفة الرحلة', - 'journey.settings.reopened': 'تمت إعادة فتح الرحلة', - 'journey.settings.endDescription': 'يخفي شارة البث المباشر. يمكنك إعادة الفتح في أي وقت.', - 'journey.settings.failedToDelete': 'فشل في الحذف', - 'journey.entries.deleteTitle': 'حذف الإدخال', - 'journey.photosUploaded': 'تم رفع {count} صورة', - 'journey.photosUploadFailed': 'فشل رفع بعض الصور', - 'journey.photosAdded': 'تمت إضافة {count} صورة', - 'journey.picker.tripPeriod': 'فترة الرحلة', - 'journey.picker.dateRange': 'نطاق التاريخ', - 'journey.picker.allPhotos': 'كل الصور', - 'journey.picker.albums': 'ألبومات', - 'journey.picker.selected': 'محدد', - 'journey.picker.addTo': 'إضافة إلى', - 'journey.picker.newGallery': 'معرض جديد', - 'journey.picker.selectAll': 'تحديد الكل', - 'journey.picker.deselectAll': 'إلغاء تحديد الكل', - 'journey.picker.noAlbums': 'لم يتم العثور على ألبومات', - 'journey.picker.selectDate': 'اختر تاريخ', - 'journey.picker.search': 'بحث', - - // Journey Detail - 'journey.detail.photos': 'صور', - 'journey.detail.backToJourney': 'العودة للمجلة', - 'journey.detail.day': 'اليوم {number}', - 'journey.detail.places': 'أماكن', - 'journey.skeletons.show': 'إظهار الاقتراحات', - 'journey.skeletons.hide': 'إخفاء الاقتراحات', - - // Journey — Invite - 'journey.invite.role': 'الدور', - 'journey.invite.viewer': 'مشاهد', - 'journey.invite.editor': 'محرر', - 'journey.invite.invite': 'دعوة', - 'journey.invite.inviting': 'جارٍ الدعوة...', - - // Journey Entry Editor - 'journey.editor.discardChangesConfirm': 'لديك تغييرات غير محفوظة. هل تريد تجاهلها؟', - 'journey.editor.uploadFailed': 'فشل رفع الصور', - 'journey.editor.uploadPhotos': 'رفع صور', - 'journey.editor.uploading': '...جارٍ الرفع', - 'journey.editor.uploadingProgress': 'جارٍ الرفع {done}/{total}…', - 'journey.editor.uploadPartialFailed': 'فشل رفع {failed} من {total} — احفظ مجدداً للمحاولة', - 'journey.editor.fromGallery': 'من المعرض', - 'journey.editor.addAnother': 'إضافة آخر', - 'journey.editor.makeFirst': 'جعله الأول', - 'journey.editor.searching': 'جارٍ البحث...', - - // Journey — Share - 'journey.share.copy': 'نسخ', - 'journey.share.copied': 'تم النسخ!', - - // Collab Addon - 'collab.tabs.chat': 'الدردشة', - 'collab.tabs.notes': 'الملاحظات', - 'collab.tabs.polls': 'الاستطلاعات', - 'collab.whatsNext.title': 'ما التالي', - 'collab.whatsNext.today': 'اليوم', - 'collab.whatsNext.tomorrow': 'غدًا', - 'collab.whatsNext.empty': 'لا توجد أنشطة قادمة', - 'collab.whatsNext.until': 'إلى', - 'collab.whatsNext.emptyHint': 'ستظهر الأنشطة التي لها وقت هنا', - 'collab.chat.send': 'إرسال', - 'collab.chat.placeholder': 'اكتب رسالة...', - 'collab.chat.empty': 'ابدأ المحادثة', - 'collab.chat.emptyHint': 'تتم مشاركة الرسائل مع جميع أعضاء الرحلة', - 'collab.chat.emptyDesc': 'شارك الأفكار والخطط والتحديثات مع مجموعة السفر', - 'collab.chat.today': 'اليوم', - 'collab.chat.yesterday': 'أمس', - 'collab.chat.deletedMessage': 'حذف رسالة', - 'collab.chat.reply': 'رد', - 'collab.chat.loadMore': 'تحميل الرسائل الأقدم', - 'collab.chat.justNow': 'الآن', - 'collab.chat.minutesAgo': 'منذ {n} د', - 'collab.chat.hoursAgo': 'منذ {n} س', - 'collab.notes.title': 'الملاحظات', - 'collab.notes.new': 'ملاحظة جديدة', - 'collab.notes.empty': 'لا توجد ملاحظات بعد', - 'collab.notes.emptyHint': 'ابدأ بتسجيل الأفكار والخطط', - 'collab.notes.all': 'الكل', - 'collab.notes.titlePlaceholder': 'عنوان الملاحظة', - 'collab.notes.contentPlaceholder': 'اكتب شيئًا...', - 'collab.notes.categoryPlaceholder': 'الفئة', - 'collab.notes.newCategory': 'فئة جديدة...', - 'collab.notes.category': 'الفئة', - 'collab.notes.noCategory': 'بلا فئة', - 'collab.notes.color': 'اللون', - 'collab.notes.save': 'حفظ', - 'collab.notes.cancel': 'إلغاء', - 'collab.notes.edit': 'تعديل', - 'collab.notes.delete': 'حذف', - 'collab.notes.pin': 'تثبيت', - 'collab.notes.unpin': 'إلغاء التثبيت', - 'collab.notes.daysAgo': 'منذ {n} يوم', - 'collab.notes.categorySettings': 'إدارة الفئات', - 'collab.notes.create': 'إنشاء', - 'collab.notes.website': 'الموقع الإلكتروني', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'إرفاق ملفات', - 'collab.notes.noCategoriesYet': 'لا توجد فئات بعد', - 'collab.notes.emptyDesc': 'أنشئ ملاحظة للبدء', - 'collab.polls.title': 'الاستطلاعات', - 'collab.polls.new': 'استطلاع جديد', - 'collab.polls.empty': 'لا توجد استطلاعات بعد', - 'collab.polls.emptyHint': 'اسأل المجموعة وصوّتوا معًا', - 'collab.polls.question': 'السؤال', - 'collab.polls.questionPlaceholder': 'ماذا ينبغي أن نفعل؟', - 'collab.polls.addOption': '+ إضافة خيار', - 'collab.polls.optionPlaceholder': 'الخيار {n}', - 'collab.polls.create': 'إنشاء استطلاع', - 'collab.polls.close': 'إغلاق', - 'collab.polls.closed': 'مغلق', - 'collab.polls.votes': '{n} أصوات', - 'collab.polls.vote': '{n} صوت', - 'collab.polls.multipleChoice': 'اختيار متعدد', - 'collab.polls.multiChoice': 'اختيار متعدد', - 'collab.polls.deadline': 'الموعد النهائي', - 'collab.polls.option': 'خيار', - 'collab.polls.options': 'الخيارات', - 'collab.polls.delete': 'حذف', - 'collab.polls.closedSection': 'مغلق', - - // Permissions - 'admin.tabs.permissions': 'الصلاحيات', - 'perm.title': 'إعدادات الصلاحيات', - 'perm.subtitle': 'التحكم في من يمكنه تنفيذ الإجراءات عبر التطبيق', - 'perm.saved': 'تم حفظ إعدادات الصلاحيات', - 'perm.resetDefaults': 'إعادة التعيين إلى الافتراضي', - 'perm.customized': 'مخصص', - 'perm.level.admin': 'المسؤول فقط', - 'perm.level.tripOwner': 'مالك الرحلة', - 'perm.level.tripMember': 'أعضاء الرحلة', - 'perm.level.everybody': 'الجميع', - 'perm.cat.trip': 'إدارة الرحلات', - 'perm.cat.members': 'إدارة الأعضاء', - 'perm.cat.files': 'الملفات', - 'perm.cat.content': 'المحتوى والجدول الزمني', - 'perm.cat.extras': 'الميزانية والتعبئة والتعاون', - 'perm.action.trip_create': 'إنشاء رحلات', - 'perm.action.trip_edit': 'تعديل تفاصيل الرحلة', - 'perm.action.trip_delete': 'حذف الرحلات', - 'perm.action.trip_archive': 'أرشفة / إلغاء أرشفة الرحلات', - 'perm.action.trip_cover_upload': 'رفع صورة الغلاف', - 'perm.action.member_manage': 'إضافة / إزالة الأعضاء', - 'perm.action.file_upload': 'رفع الملفات', - 'perm.action.file_edit': 'تعديل بيانات الملف', - 'perm.action.file_delete': 'حذف الملفات', - 'perm.action.place_edit': 'إضافة / تعديل / حذف الأماكن', - 'perm.action.day_edit': 'تعديل الأيام والملاحظات والتعيينات', - 'perm.action.reservation_edit': 'إدارة الحجوزات', - 'perm.action.budget_edit': 'إدارة الميزانية', - 'perm.action.packing_edit': 'إدارة قوائم التعبئة', - 'perm.action.collab_edit': 'التعاون (ملاحظات، استطلاعات، دردشة)', - 'perm.action.share_manage': 'إدارة روابط المشاركة', - 'perm.actionHint.trip_create': 'من يمكنه إنشاء رحلات جديدة', - 'perm.actionHint.trip_edit': 'من يمكنه تغيير اسم الرحلة والتواريخ والوصف والعملة', - 'perm.actionHint.trip_delete': 'من يمكنه حذف رحلة نهائياً', - 'perm.actionHint.trip_archive': 'من يمكنه أرشفة أو إلغاء أرشفة رحلة', - 'perm.actionHint.trip_cover_upload': 'من يمكنه رفع أو تغيير صورة الغلاف', - 'perm.actionHint.member_manage': 'من يمكنه دعوة أو إزالة أعضاء الرحلة', - 'perm.actionHint.file_upload': 'من يمكنه رفع ملفات إلى رحلة', - 'perm.actionHint.file_edit': 'من يمكنه تعديل أوصاف الملفات والروابط', - 'perm.actionHint.file_delete': 'من يمكنه نقل الملفات إلى سلة المهملات أو حذفها نهائياً', - 'perm.actionHint.place_edit': 'من يمكنه إضافة أو تعديل أو حذف الأماكن', - 'perm.actionHint.day_edit': 'من يمكنه تعديل الأيام وملاحظات الأيام وتعيينات الأماكن', - 'perm.actionHint.reservation_edit': 'من يمكنه إنشاء أو تعديل أو حذف الحجوزات', - 'perm.actionHint.budget_edit': 'من يمكنه إنشاء أو تعديل أو حذف عناصر الميزانية', - 'perm.actionHint.packing_edit': 'من يمكنه إدارة عناصر التعبئة والحقائب', - 'perm.actionHint.collab_edit': 'من يمكنه إنشاء ملاحظات واستطلاعات وإرسال رسائل', - 'perm.actionHint.share_manage': 'من يمكنه إنشاء أو حذف روابط المشاركة العامة', - // Undo - 'undo.button': 'تراجع', - 'undo.tooltip': 'تراجع: {action}', - 'undo.assignPlace': 'تم تعيين المكان لليوم', - 'undo.removeAssignment': 'تم إزالة المكان من اليوم', - 'undo.reorder': 'تمت إعادة ترتيب الأماكن', - 'undo.optimize': 'تم تحسين المسار', - 'undo.deletePlace': 'تم حذف المكان', - 'undo.deletePlaces': 'تم حذف الأماكن', - 'undo.moveDay': 'تم نقل المكان إلى يوم آخر', - 'undo.lock': 'تم تبديل قفل المكان', - 'undo.importGpx': 'استيراد GPX', - 'undo.importKeyholeMarkup': 'استيراد KMZ/KML', - 'undo.importGoogleList': 'استيراد خرائط Google', - 'undo.importNaverList': 'استيراد خرائط Naver', - - // Notifications - 'notifications.title': 'الإشعارات', - 'notifications.markAllRead': 'تحديد الكل كمقروء', - 'notifications.deleteAll': 'حذف الكل', - 'notifications.showAll': 'عرض جميع الإشعارات', - 'notifications.empty': 'لا توجد إشعارات', - 'notifications.emptyDescription': 'لقد اطلعت على كل شيء!', - 'notifications.all': 'الكل', - 'notifications.unreadOnly': 'غير مقروء', - 'notifications.markRead': 'تحديد كمقروء', - 'notifications.markUnread': 'تحديد كغير مقروء', - 'notifications.delete': 'حذف', - 'notifications.system': 'النظام', - 'notifications.synologySessionCleared.title': 'تم قطع اتصال Synology Photos', - 'notifications.synologySessionCleared.text': 'تغير خادمك أو حسابك — انتقل إلى الإعدادات لاختبار اتصالك مرة أخرى.', - 'memories.error.loadAlbums': 'فشل تحميل الألبومات', - 'memories.error.linkAlbum': 'فشل ربط الألبوم', - 'memories.error.unlinkAlbum': 'فشل إلغاء ربط الألبوم', - 'memories.error.syncAlbum': 'فشل مزامنة الألبوم', - 'memories.error.loadPhotos': 'فشل تحميل الصور', - 'memories.error.addPhotos': 'فشل إضافة الصور', - 'memories.error.removePhoto': 'فشل حذف الصورة', - 'memories.error.toggleSharing': 'فشل تحديث إعدادات المشاركة', - 'undo.addPlace': 'تمت إضافة المكان', - 'undo.done': 'تم التراجع: {action}', - 'notifications.test.title': 'إشعار تجريبي من {actor}', - 'notifications.test.text': 'هذا إشعار تجريبي بسيط.', - 'notifications.test.booleanTitle': 'يطلب منك {actor} الموافقة', - 'notifications.test.booleanText': 'إشعار تجريبي يتطلب إجابة.', - 'notifications.test.accept': 'موافقة', - 'notifications.test.decline': 'رفض', - 'notifications.test.navigateTitle': 'تحقق من شيء ما', - 'notifications.test.navigateText': 'إشعار تجريبي للتنقل.', - 'notifications.test.goThere': 'اذهب إلى هناك', - 'notifications.test.adminTitle': 'إذاعة المسؤول', - 'notifications.test.adminText': 'أرسل {actor} إشعاراً تجريبياً لجميع المسؤولين.', - 'notifications.test.tripTitle': 'نشر {actor} في رحلتك', - 'notifications.test.tripText': 'إشعار تجريبي للرحلة "{trip}".', - - // Todo - 'todo.subtab.packing': 'قائمة الأمتعة', - 'todo.subtab.todo': 'المهام', - 'todo.completed': 'مكتمل', - 'todo.filter.all': 'الكل', - 'todo.filter.open': 'مفتوح', - 'todo.filter.done': 'منجز', - 'todo.uncategorized': 'بدون تصنيف', - 'todo.namePlaceholder': 'اسم المهمة', - 'todo.descriptionPlaceholder': 'وصف (اختياري)', - 'todo.unassigned': 'غير مُسنَد', - 'todo.noCategory': 'بدون فئة', - 'todo.hasDescription': 'له وصف', - 'todo.addItem': 'إضافة مهمة جديدة', - 'todo.sidebar.sortBy': 'ترتيب حسب', - 'todo.priority': 'الأولوية', - 'todo.newCategoryLabel': 'جديد', - 'budget.categoriesLabel': 'فئات', - 'todo.newCategory': 'اسم الفئة', - 'todo.addCategory': 'إضافة فئة', - 'todo.newItem': 'مهمة جديدة', - 'todo.empty': 'لا توجد مهام بعد. أضف مهمة للبدء!', - 'todo.filter.my': 'مهامي', - 'todo.filter.overdue': 'متأخرة', - 'todo.sidebar.tasks': 'المهام', - 'todo.sidebar.categories': 'الفئات', - 'todo.detail.title': 'مهمة', - 'todo.detail.description': 'وصف', - 'todo.detail.category': 'فئة', - 'todo.detail.dueDate': 'تاريخ الاستحقاق', - 'todo.detail.assignedTo': 'مسند إلى', - 'todo.detail.delete': 'حذف', - 'todo.detail.save': 'حفظ التغييرات', - 'todo.detail.create': 'إنشاء مهمة', - 'todo.detail.priority': 'الأولوية', - 'todo.detail.noPriority': 'لا شيء', - 'todo.sortByPrio': 'الأولوية', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'إصدار جديد متاح', - 'settings.notificationPreferences.noChannels': 'لم يتم تكوين قنوات إشعارات. اطلب من المسؤول إعداد إشعارات البريد الإلكتروني أو webhook.', - 'settings.webhookUrl.label': 'رابط Webhook', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'أدخل رابط Webhook الخاص بـ Discord أو Slack أو المخصص لتلقي الإشعارات.', - 'settings.webhookUrl.saved': 'تم حفظ رابط Webhook', - 'settings.webhookUrl.test': 'اختبار', - 'settings.webhookUrl.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', - 'settings.webhookUrl.testFailed': 'فشل إرسال Webhook الاختباري', - 'settings.ntfyUrl.topicLabel': 'موضوع Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'عنوان URL خادم Ntfy (اختياري)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'أدخل موضوع Ntfy الخاص بك لتلقي الإشعارات الفورية. اترك حقل الخادم فارغاً لاستخدام الإعداد الافتراضي الذي حدده المسؤول.', - 'settings.ntfyUrl.tokenLabel': 'رمز الوصول (اختياري)', - 'settings.ntfyUrl.tokenHint': 'مطلوب للمواضيع المحمية بكلمة مرور.', - 'settings.ntfyUrl.saved': 'تم حفظ إعدادات Ntfy', - 'settings.ntfyUrl.test': 'اختبار', - 'settings.ntfyUrl.testSuccess': 'تم إرسال إشعار Ntfy التجريبي بنجاح', - 'settings.ntfyUrl.testFailed': 'فشل إشعار Ntfy التجريبي', - 'settings.ntfyUrl.tokenCleared': 'تم مسح رمز الوصول', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'الإشعارات داخل التطبيق نشطة دائمًا ولا يمكن تعطيلها بشكل عام.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook المسؤول', - 'admin.notifications.adminWebhookPanel.hint': 'يُستخدم هذا الـ Webhook حصريًا لإشعارات المسؤول (مثل تنبيهات الإصدارات). وهو مستقل عن Webhooks المستخدمين ويُرسل تلقائيًا عند تعيين رابط URL.', - 'admin.notifications.adminWebhookPanel.saved': 'تم حفظ رابط Webhook المسؤول', - 'admin.notifications.adminWebhookPanel.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', - 'admin.notifications.adminWebhookPanel.testFailed': 'فشل إرسال Webhook الاختباري', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'يُرسل Webhook المسؤول تلقائيًا عند تعيين رابط URL', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'تسمح للمستخدمين بإعداد موضوعات ntfy الخاصة لتلقي إشعارات الدفع. قم بتعيين الخادم الافتراضي أدناه لملء إعدادات المستخدم مسبقًا.', - 'admin.notifications.testNtfy': 'إرسال Ntfy تجريبي', - 'admin.notifications.testNtfySuccess': 'تم إرسال Ntfy التجريبي بنجاح', - 'admin.notifications.testNtfyFailed': 'فشل إرسال Ntfy التجريبي', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy المسؤول', - 'admin.notifications.adminNtfyPanel.hint': 'يُستخدم موضوع Ntfy هذا حصريًا لإشعارات المسؤول (مثل تنبيهات الإصدارات). وهو مستقل عن مواضيع المستخدمين ويُرسل دائمًا عند تهيئته.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'عنوان URL خادم Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'يُستخدم أيضًا كخادم افتراضي لإشعارات ntfy للمستخدمين. اتركه فارغًا لاستخدام ntfy.sh. يمكن للمستخدمين تغييره في إعداداتهم الخاصة.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'موضوع المسؤول', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'رمز الوصول (اختياري)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'تم مسح رمز وصول المسؤول', - 'admin.notifications.adminNtfyPanel.saved': 'تم حفظ إعدادات Ntfy للمسؤول', - 'admin.notifications.adminNtfyPanel.test': 'إرسال Ntfy تجريبي', - 'admin.notifications.adminNtfyPanel.testSuccess': 'تم إرسال Ntfy التجريبي بنجاح', - 'admin.notifications.adminNtfyPanel.testFailed': 'فشل إرسال Ntfy التجريبي', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'يُرسل Ntfy للمسؤول دائمًا عند تهيئة موضوع', - 'admin.notifications.adminNotificationsHint': 'حدد القنوات التي تُسلّم إشعارات المسؤول (مثل تنبيهات الإصدارات). يُرسل الـ Webhook تلقائيًا عند تعيين رابط URL لـ Webhook المسؤول.', - 'admin.notifications.tripReminders.title': 'تذكيرات الرحلات', - 'admin.notifications.tripReminders.hint': 'إرسال تذكير قبل بدء الرحلة (يتطلب تعيين أيام التذكير على الرحلة).', - 'admin.notifications.tripReminders.enabled': 'تم تفعيل تذكيرات الرحلات', - 'admin.notifications.tripReminders.disabled': 'تم تعطيل تذكيرات الرحلات', - 'admin.tabs.notifications': 'الإشعارات', - 'notifications.versionAvailable.title': 'تحديث متاح', - 'notifications.versionAvailable.text': 'TREK {version} متاح الآن.', - 'notifications.versionAvailable.button': 'عرض التفاصيل', - 'notif.test.title': '[اختبار] إشعار', - 'notif.test.simple.text': 'هذا إشعار اختبار بسيط.', - 'notif.test.boolean.text': 'هل تقبل هذا الإشعار الاختباري؟', - 'notif.test.navigate.text': 'انقر أدناه للانتقال إلى لوحة التحكم.', - - // Notifications - 'notif.trip_invite.title': 'دعوة للرحلة', - 'notif.trip_invite.text': '{actor} دعاك إلى {trip}', - 'notif.booking_change.title': 'تم تحديث الحجز', - 'notif.booking_change.text': '{actor} حدّث حجزاً في {trip}', - 'notif.trip_reminder.title': 'تذكير بالرحلة', - 'notif.trip_reminder.text': 'رحلتك {trip} تقترب!', - 'notif.todo_due.title': 'مهمة مستحقة', - 'notif.todo_due.text': '{todo} في {trip} مستحقة في {due}', - 'notif.vacay_invite.title': 'دعوة دمج الإجازة', - 'notif.vacay_invite.text': '{actor} يدعوك لدمج خطط الإجازة', - 'notif.photos_shared.title': 'تمت مشاركة الصور', - 'notif.photos_shared.text': '{actor} شارك {count} صورة في {trip}', - 'notif.collab_message.title': 'رسالة جديدة', - 'notif.collab_message.text': '{actor} أرسل رسالة في {trip}', - 'notif.packing_tagged.title': 'مهمة التعبئة', - 'notif.packing_tagged.text': '{actor} عيّنك في {category} في {trip}', - 'notif.version_available.title': 'إصدار جديد متاح', - 'notif.version_available.text': 'TREK {version} متاح الآن', - 'notif.action.view_trip': 'عرض الرحلة', - 'notif.action.view_collab': 'عرض الرسائل', - 'notif.action.view_packing': 'عرض التعبئة', - 'notif.action.view_photos': 'عرض الصور', - 'notif.action.view_vacay': 'عرض Vacay', - 'notif.action.view_admin': 'الذهاب للإدارة', - 'notif.action.view': 'عرض', - 'notif.action.accept': 'قبول', - 'notif.action.decline': 'رفض', - 'notif.generic.title': 'إشعار', - 'notif.generic.text': 'لديك إشعار جديد', - 'notif.dev.unknown_event.title': '[DEV] حدث غير معروف', - 'notif.dev.unknown_event.text': 'نوع الحدث "{event}" غير مسجل في EVENT_NOTIFICATION_CONFIG', - - // OAuth scope groups - 'oauth.scope.group.trips': 'الرحلات', - 'oauth.scope.group.places': 'الأماكن', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'الأمتعة', - 'oauth.scope.group.todos': 'المهام', - 'oauth.scope.group.budget': 'الميزانية', - 'oauth.scope.group.reservations': 'الحجوزات', - 'oauth.scope.group.collab': 'التعاون', - 'oauth.scope.group.notifications': 'الإشعارات', - 'oauth.scope.group.vacay': 'الإجازة', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'الطقس', - 'oauth.scope.group.journey': 'مذكرة السفر', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'عرض الرحلات وخطط السفر', - 'oauth.scope.trips:read.description': 'قراءة الرحلات والأيام والملاحظات والأعضاء', - 'oauth.scope.trips:write.label': 'تحرير الرحلات وخطط السفر', - 'oauth.scope.trips:write.description': 'إنشاء وتحديث الرحلات والأيام والملاحظات وإدارة الأعضاء', - 'oauth.scope.trips:delete.label': 'حذف الرحلات', - 'oauth.scope.trips:delete.description': 'حذف الرحلات بأكملها نهائياً — هذا الإجراء لا يمكن التراجع عنه', - 'oauth.scope.trips:share.label': 'إدارة روابط المشاركة', - 'oauth.scope.trips:share.description': 'إنشاء روابط مشاركة عامة وتحديثها وإلغاؤها', - 'oauth.scope.places:read.label': 'عرض الأماكن وبيانات الخريطة', - 'oauth.scope.places:read.description': 'قراءة الأماكن وتعيينات الأيام والعلامات والفئات', - 'oauth.scope.places:write.label': 'إدارة الأماكن', - 'oauth.scope.places:write.description': 'إنشاء وتحديث وحذف الأماكن والتعيينات والعلامات', - 'oauth.scope.atlas:read.label': 'عرض Atlas', - 'oauth.scope.atlas:read.description': 'قراءة الدول والمناطق المزارة وقائمة الأمنيات', - 'oauth.scope.atlas:write.label': 'إدارة Atlas', - 'oauth.scope.atlas:write.description': 'تعليم الدول والمناطق كمزارة، وإدارة قائمة الأمنيات', - 'oauth.scope.packing:read.label': 'عرض قوائم الأمتعة', - 'oauth.scope.packing:read.description': 'قراءة عناصر الأمتعة والحقائب ومُسنَدي الفئات', - 'oauth.scope.packing:write.label': 'إدارة قوائم الأمتعة', - 'oauth.scope.packing:write.description': 'إضافة وتحديث وحذف وتبديل وإعادة ترتيب عناصر الأمتعة والحقائب', - 'oauth.scope.todos:read.label': 'عرض قوائم المهام', - 'oauth.scope.todos:read.description': 'قراءة مهام الرحلة ومُسنَدي الفئات', - 'oauth.scope.todos:write.label': 'إدارة قوائم المهام', - 'oauth.scope.todos:write.description': 'إنشاء وتحديث وتبديل وحذف وإعادة ترتيب المهام', - 'oauth.scope.budget:read.label': 'عرض الميزانية', - 'oauth.scope.budget:read.description': 'قراءة بنود الميزانية وتفاصيل النفقات', - 'oauth.scope.budget:write.label': 'إدارة الميزانية', - 'oauth.scope.budget:write.description': 'إنشاء وتحديث وحذف بنود الميزانية', - 'oauth.scope.reservations:read.label': 'عرض الحجوزات', - 'oauth.scope.reservations:read.description': 'قراءة الحجوزات وتفاصيل الإقامة', - 'oauth.scope.reservations:write.label': 'إدارة الحجوزات', - 'oauth.scope.reservations:write.description': 'إنشاء وتحديث وحذف وإعادة ترتيب الحجوزات', - 'oauth.scope.collab:read.label': 'عرض التعاون', - 'oauth.scope.collab:read.description': 'قراءة ملاحظات التعاون والاستطلاعات والرسائل', - 'oauth.scope.collab:write.label': 'إدارة التعاون', - 'oauth.scope.collab:write.description': 'إنشاء وتحديث وحذف الملاحظات والاستطلاعات والرسائل التعاونية', - 'oauth.scope.notifications:read.label': 'عرض الإشعارات', - 'oauth.scope.notifications:read.description': 'قراءة إشعارات التطبيق وأعداد غير المقروءة', - 'oauth.scope.notifications:write.label': 'إدارة الإشعارات', - 'oauth.scope.notifications:write.description': 'تعليم الإشعارات كمقروءة والرد عليها', - 'oauth.scope.vacay:read.label': 'عرض خطط الإجازة', - 'oauth.scope.vacay:read.description': 'قراءة بيانات تخطيط الإجازة والإدخالات والإحصاءات', - 'oauth.scope.vacay:write.label': 'إدارة خطط الإجازة', - 'oauth.scope.vacay:write.description': 'إنشاء وإدارة إدخالات الإجازة والعطلات وخطط الفريق', - 'oauth.scope.geo:read.label': 'الخرائط والترميز الجغرافي', - 'oauth.scope.geo:read.description': 'البحث عن مواقع وحل عناوين الخرائط والترميز الجغرافي العكسي للإحداثيات', - 'oauth.scope.weather:read.label': 'توقعات الطقس', - 'oauth.scope.weather:read.description': 'جلب توقعات الطقس لمواقع الرحلة وتواريخها', - 'oauth.scope.journey:read.label': 'عرض مذكرات السفر', - 'oauth.scope.journey:read.description': 'قراءة مذكرات السفر والمدخلات وقائمة المساهمين', - 'oauth.scope.journey:write.label': 'إدارة مذكرات السفر', - 'oauth.scope.journey:write.description': 'إنشاء مذكرات السفر وتحديثها وحذفها وإدخالاتها', - 'oauth.scope.journey:share.label': 'إدارة روابط مذكرات السفر', - 'oauth.scope.journey:share.description': 'إنشاء روابط مشاركة عامة لمذكرات السفر وتحديثها وإلغاؤها', - - // System notices - 'system_notice.welcome_v1.title': 'مرحبًا بك في TREK', - 'system_notice.welcome_v1.body': 'مخطط رحلاتك الشامل. أنشئ جداول السفر، وشارك رحلاتك مع الأصدقاء، وابقَ منظمًا — سواء كنت متصلاً بالإنترنت أم لا.', - 'system_notice.welcome_v1.cta_label': 'خطط لرحلة', - 'system_notice.welcome_v1.hero_alt': 'وجهة سفر خلابة مع واجهة تطبيق TREK', - 'system_notice.welcome_v1.highlight_plan': 'جداول رحلات يومية لكل سفرة', - 'system_notice.welcome_v1.highlight_share': 'تعاون مع شركاء السفر', - 'system_notice.welcome_v1.highlight_offline': 'يعمل بلا إنترنت على الهاتف', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'الإشعار السابق', - 'system_notice.pager.next': 'الإشعار التالي', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'الانتقال إلى الإشعار {n}', - 'system_notice.pager.position': 'الإشعار {current} من {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'تم نقل الصور في الإصدار 3.0', - 'system_notice.v3_photos.body': 'تمت إزالة تبويب ​**الصور**​ من مخطط الرحلة. صورك آمنة — لم يعدّل TREK مكتبتك على Immich أو Synology قطّ.\n\nتعيش الصور الآن في إضافة **Journey**. Journey اختيارية — إن لم تكن متاحة بعد، اطلب من المسؤول تفعيلها عبر Admin ← الإضافات.', - 'system_notice.v3_journey.title': 'تعرّف على Journey — مذكرة سفر', - 'system_notice.v3_journey.body': 'وثّق رحلاتك كقصص غنية بخطوط زمنية ومعارض صور وخرائط تفاعلية.', - 'system_notice.v3_journey.cta_label': 'فتح Journey', - 'system_notice.v3_journey.highlight_timeline': 'جدول زمني يومي ومعرض', - 'system_notice.v3_journey.highlight_photos': 'استيراد من Immich أو Synology', - 'system_notice.v3_journey.highlight_share': 'مشاركة علنية — دون تسجيل دخول', - 'system_notice.v3_journey.highlight_export': 'تصدير كألبوم صور PDF', - 'system_notice.v3_features.title': 'مزيد من مميزات 3.0', - 'system_notice.v3_features.body': 'بعض الجديد الآخر الجدير بالمعرفة في هذا الإصدار.', - 'system_notice.v3_features.highlight_dashboard': 'إعادة تصميم لوحة التحكم mobile-first', - 'system_notice.v3_features.highlight_offline': 'وضع لا اتصال كامل كتطبيق PWA', - 'system_notice.v3_features.highlight_search': 'إكمال تلقائي في الوقت الفعلي', - 'system_notice.v3_features.highlight_import': 'استيراد أماكن من ملفات KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: ترقية OAuth 2.1', - 'system_notice.v3_mcp.body': 'تمت إعادة تصميم تكامل MCP بالكامل. OAuth 2.1 هو الآن طريقة المصادقة الموصى بها. الرموز الثابتة (trek_…) مهملة وستُزال في إصدار مستقبلي.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 موصى به (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 نطاق أذونات دقيق', - 'system_notice.v3_mcp.highlight_deprecated': 'الرموز الثابتة trek_ مهملة', - 'system_notice.v3_mcp.highlight_tools': 'مجموعة أدوات وإرشادات موسعة', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'كلمة شخصية مني', - 'system_notice.v3_thankyou.body': 'قبل أن تمضي — أريد أن أتوقف لحظة.\n\nبدأ TREK كمشروع جانبي بنيته لرحلاتي الخاصة. لم أتخيل يومًا أنه سيكبر ليصبح شيئًا يعتمد عليه 4,000 منكم لتخطيط مغامراتهم. كل نجمة، كل مشكلة، كل طلب ميزة — أقرأها جميعًا، وهي ما يبقيني مستمرًا في الليالي المتأخرة بين عمل بدوام كامل والجامعة.\n\nأريدكم أن تعرفوا: TREK سيبقى دائمًا مفتوح المصدر، دائمًا مستضافًا ذاتيًا، دائمًا ملككم. لا تتبع، لا اشتراكات، لا شروط خفية. مجرد أداة بناها شخص يحب السفر بقدر ما تحبونه.\n\nشكر خاص لـ [jubnl](https://github.com/jubnl) — لقد أصبحت متعاونًا رائعًا. الكثير مما يجعل الإصدار 3.0 عظيمًا يحمل بصماتك. شكرًا لإيمانك بهذا المشروع عندما كان لا يزال في بداياته.\n\nولكل واحد منكم ممن أبلغ عن خطأ، أو ترجم نصًا، أو شارك TREK مع صديق، أو ببساطة استخدمه لتخطيط رحلة — **شكرًا لكم**. أنتم السبب في وجود هذا.\n\nإلى المزيد من المغامرات معًا.\n\n— Maurice\n\n---\n\n[انضم إلى المجتمع على Discord](https://discord.gg/7Q6M6jDwzf)\n\nإذا جعل TREK رحلاتك أفضل، [فنجان قهوة صغير](https://ko-fi.com/mauriceboe) يبقي الأضواء مشتعلة.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'إجراء مطلوب: تعارض في حسابات المستخدمين', - 'system_notice.v3014_whitespace_collision.body': 'اكتشف ترقية 3.0.14 تعارضًا في أسماء مستخدمين أو بريد إلكتروني ناتجًا عن مسافات بيضاء في بداية أو نهاية القيم المخزنة. تمت إعادة تسمية الحسابات المتأثرة تلقائيًا. تحقق من سجلات الخادم بحثًا عن أسطر تبدأ بـ **[migration] WHITESPACE COLLISION** لتحديد الحسابات التي تحتاج إلى مراجعة.', - 'transport.addTransport': 'إضافة وسيلة نقل', - 'transport.modalTitle.create': 'إضافة وسيلة نقل', - 'transport.modalTitle.edit': 'تعديل وسيلة النقل', - 'transport.title': 'المواصلات', - 'transport.addManual': 'نقل يدوي', -} - -export default ar - diff --git a/client/src/i18n/translations/br.ts b/client/src/i18n/translations/br.ts deleted file mode 100644 index 637285d5..00000000 --- a/client/src/i18n/translations/br.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const br: Record = { - // Common - 'common.save': 'Salvar', - 'common.showMore': 'Mostrar mais', - 'common.showLess': 'Mostrar menos', - 'common.cancel': 'Cancelar', - 'common.clear': 'Limpar', - 'common.delete': 'Excluir', - 'common.edit': 'Editar', - 'common.add': 'Adicionar', - 'common.loading': 'Carregando...', - 'common.import': 'Importar', - 'common.select': 'Selecionar', - 'common.selectAll': 'Selecionar tudo', - 'common.deselectAll': 'Desmarcar tudo', - 'common.error': 'Erro', - 'common.unknownError': 'Erro desconhecido', - 'common.tooManyAttempts': 'Muitas tentativas. Tente novamente mais tarde.', - 'common.back': 'Voltar', - 'common.all': 'Todos', - 'common.close': 'Fechar', - 'common.open': 'Abrir', - 'common.upload': 'Enviar', - 'common.search': 'Buscar', - 'common.confirm': 'Confirmar', - 'common.ok': 'OK', - 'common.yes': 'Sim', - 'common.no': 'Não', - 'common.or': 'ou', - 'common.none': 'Nenhum', - 'common.date': 'Data', - 'common.rename': 'Renomear', - 'common.discardChanges': 'Descartar alterações', - 'common.discard': 'Descartar', - 'common.name': 'Nome', - 'common.email': 'E-mail', - 'common.password': 'Senha', - 'common.saving': 'Salvando...', - 'common.saved': 'Salvo', - 'common.expand': 'Expandir', - 'common.collapse': 'Recolher', - 'trips.reminder': 'Lembrete', - 'trips.reminderNone': 'Nenhum', - 'trips.reminderDay': 'dia', - 'trips.reminderDays': 'dias', - 'trips.reminderCustom': 'Personalizado', - 'trips.memberRemoved': '{username} removido', - 'trips.memberRemoveError': 'Falha ao remover', - 'trips.memberAdded': '{username} adicionado', - 'trips.memberAddError': 'Falha ao adicionar', - 'trips.reminderDaysBefore': 'dias antes da partida', - 'trips.reminderDisabledHint': 'Os lembretes de viagem estão desativados. Ative-os em Admin > Configurações > Notificações.', - 'common.update': 'Atualizar', - 'common.change': 'Alterar', - 'common.uploading': 'Enviando…', - 'common.backToPlanning': 'Voltar ao planejamento', - 'common.reset': 'Redefinir', - - // Navbar - 'nav.trip': 'Viagem', - 'nav.share': 'Compartilhar', - 'nav.settings': 'Configurações', - 'nav.admin': 'Admin', - 'nav.logout': 'Sair', - 'nav.lightMode': 'Modo claro', - 'nav.darkMode': 'Modo escuro', - 'nav.autoMode': 'Automático', - 'nav.administrator': 'Administrador', - - // Dashboard - 'dashboard.title': 'Minhas viagens', - 'dashboard.subtitle.loading': 'Carregando viagens...', - 'dashboard.subtitle.trips': '{count} viagens ({archived} arquivadas)', - 'dashboard.subtitle.empty': 'Comece sua primeira viagem', - 'dashboard.subtitle.activeOne': '{count} viagem ativa', - 'dashboard.subtitle.activeMany': '{count} viagens ativas', - 'dashboard.subtitle.archivedSuffix': ' · {count} arquivadas', - 'dashboard.newTrip': 'Nova viagem', - 'dashboard.gridView': 'Grade', - 'dashboard.listView': 'Lista', - 'dashboard.currency': 'Moeda', - 'dashboard.timezone': 'Fusos horários', - 'dashboard.localTime': 'Local', - 'dashboard.timezoneCustomTitle': 'Fuso personalizado', - 'dashboard.timezoneCustomLabelPlaceholder': 'Rótulo (opcional)', - 'dashboard.timezoneCustomTzPlaceholder': 'ex.: America/Sao_Paulo', - 'dashboard.timezoneCustomAdd': 'Adicionar', - 'dashboard.timezoneCustomErrorEmpty': 'Informe um identificador de fuso', - 'dashboard.timezoneCustomErrorInvalid': 'Fuso inválido. Use o formato Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Já adicionado', - 'dashboard.emptyTitle': 'Nenhuma viagem ainda', - 'dashboard.emptyText': 'Crie sua primeira viagem e comece a planejar!', - 'dashboard.emptyButton': 'Criar primeira viagem', - 'dashboard.nextTrip': 'Próxima viagem', - 'dashboard.shared': 'Compartilhada', - 'dashboard.sharedBy': 'Compartilhada por {name}', - 'dashboard.days': 'Dias', - 'dashboard.places': 'Lugares', - 'dashboard.members': 'Parceiros de viagem', - 'dashboard.archive': 'Arquivar', - 'dashboard.copyTrip': 'Copiar', - 'dashboard.copySuffix': 'cópia', - 'dashboard.restore': 'Restaurar', - 'dashboard.archived': 'Arquivada', - 'dashboard.status.ongoing': 'Em andamento', - 'dashboard.status.today': 'Hoje', - 'dashboard.status.tomorrow': 'Amanhã', - 'dashboard.status.past': 'Passada', - 'dashboard.status.daysLeft': 'Faltam {count} dias', - 'dashboard.toast.loadError': 'Não foi possível carregar as viagens', - 'dashboard.toast.created': 'Viagem criada com sucesso!', - 'dashboard.toast.createError': 'Não foi possível criar a viagem', - 'dashboard.toast.updated': 'Viagem atualizada!', - 'dashboard.toast.updateError': 'Não foi possível atualizar a viagem', - 'dashboard.toast.deleted': 'Viagem excluída', - 'dashboard.toast.deleteError': 'Não foi possível excluir a viagem', - 'dashboard.toast.archived': 'Viagem arquivada', - 'dashboard.toast.archiveError': 'Não foi possível arquivar', - 'dashboard.toast.restored': 'Viagem restaurada', - 'dashboard.toast.restoreError': 'Não foi possível restaurar', - 'dashboard.toast.copied': 'Viagem copiada!', - 'dashboard.toast.copyError': 'Não foi possível copiar a viagem', - 'dashboard.confirm.delete': 'Excluir a viagem "{title}"? Todos os lugares e planos serão excluídos permanentemente.', - 'dashboard.editTrip': 'Editar viagem', - 'dashboard.createTrip': 'Criar nova viagem', - 'dashboard.tripTitle': 'Título', - 'dashboard.tripTitlePlaceholder': 'ex.: Verão no Japão', - 'dashboard.tripDescription': 'Descrição', - 'dashboard.tripDescriptionPlaceholder': 'Sobre o que é esta viagem?', - 'dashboard.startDate': 'Data de início', - 'dashboard.endDate': 'Data de término', - 'dashboard.dayCount': 'Número de dias', - 'dashboard.dayCountHint': 'Quantos dias planejar quando nenhuma data de viagem for definida.', - 'dashboard.noDateHint': 'Sem datas — serão criados 7 dias padrão. Você pode alterar depois.', - 'dashboard.coverImage': 'Imagem de capa', - 'dashboard.addCoverImage': 'Adicionar capa (ou arrastar e soltar)', - 'dashboard.addMembers': 'Companheiros de viagem', - 'dashboard.addMember': 'Adicionar membro', - 'dashboard.coverSaved': 'Capa salva', - 'dashboard.coverUploadError': 'Falha no envio', - 'dashboard.coverRemoveError': 'Falha ao remover', - 'dashboard.titleRequired': 'O título é obrigatório', - 'dashboard.endDateError': 'A data final deve ser depois da inicial', - - // Settings - 'settings.title': 'Configurações', - 'settings.subtitle': 'Ajuste suas preferências pessoais', - 'settings.tabs.display': 'Exibição', - 'settings.tabs.map': 'Mapa', - 'settings.tabs.notifications': 'Notificações', - 'settings.tabs.integrations': 'Integrações', - 'settings.tabs.account': 'Conta', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Sobre', - 'settings.map': 'Mapa', - 'settings.mapTemplate': 'Modelo de mapa', - 'settings.mapTemplatePlaceholder.select': 'Selecione o modelo...', - 'settings.mapDefaultHint': 'Deixe vazio para OpenStreetMap (padrão)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL do modelo de blocos do mapa', - 'settings.mapProvider': 'Provedor de mapa', - 'settings.mapProviderHint': 'Afeta os mapas do Planejador de Viagem e Diário. Atlas sempre usa Leaflet.', - 'settings.mapLeafletSubtitle': 'Clássico 2D, quaisquer blocos raster', - 'settings.mapMapboxSubtitle': 'Blocos vetoriais, prédios 3D & terreno', - 'settings.mapExperimental': 'Experimental', - 'settings.mapMapboxToken': 'Token de acesso Mapbox', - 'settings.mapMapboxTokenHint': 'Token público (pk.*) de', - 'settings.mapMapboxTokenLink': 'mapbox.com → Tokens de acesso', - 'settings.mapStyle': 'Estilo do mapa', - 'settings.mapStylePlaceholder': 'Selecionar um estilo Mapbox', - 'settings.mapStyleHint': 'Preset ou sua própria URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': 'Prédios 3D & terreno', - 'settings.map3dHint': 'Inclinação + extrusões 3D reais de prédios — funciona em todo estilo, incluindo satélite.', - 'settings.mapHighQuality': 'Modo alta qualidade', - 'settings.mapHighQualityHint': 'Antialiasing + projeção global para bordas mais nítidas e uma visão realista do mundo.', - 'settings.mapHighQualityWarning': 'Pode afetar o desempenho em dispositivos menos potentes.', - 'settings.mapTipLabel': 'Dica:', - 'settings.mapTip': 'Clique direito e arraste para girar/inclinar o mapa. Clique do meio para adicionar um local (o clique direito é reservado para rotação).', - 'settings.latitude': 'Latitude', - 'settings.longitude': 'Longitude', - 'settings.saveMap': 'Salvar mapa', - 'settings.apiKeys': 'Chaves de API', - 'settings.mapsKey': 'Chave da API Google Maps', - 'settings.mapsKeyHint': 'Para busca de lugares. Requer Places API (New). Obtenha em console.cloud.google.com', - 'settings.weatherKey': 'Chave OpenWeatherMap', - 'settings.weatherKeyHint': 'Para dados meteorológicos. Grátis em openweathermap.org/api', - 'settings.keyPlaceholder': 'Digite a chave...', - 'settings.configured': 'Configurada', - 'settings.saveKeys': 'Salvar chaves', - 'settings.display': 'Exibição', - 'settings.colorMode': 'Tema de cores', - 'settings.light': 'Claro', - 'settings.dark': 'Escuro', - 'settings.auto': 'Automático', - 'settings.language': 'Idioma', - 'settings.temperature': 'Unidade de temperatura', - 'settings.timeFormat': 'Formato de hora', - 'settings.blurBookingCodes': 'Ocultar códigos de reserva', - 'settings.notifications': 'Notificações', - 'settings.notifyTripInvite': 'Convites de viagem', - 'settings.notifyBookingChange': 'Alterações de reserva', - 'settings.notifyTripReminder': 'Lembretes de viagem', - 'settings.notifyTodoDue': 'Tarefa com vencimento', - 'settings.notifyVacayInvite': 'Convites de fusão Vacay', - 'settings.notifyPhotosShared': 'Fotos compartilhadas (Immich)', - 'settings.notifyCollabMessage': 'Mensagens de chat (Colab)', - 'settings.notifyPackingTagged': 'Lista de mala: atribuições', - 'settings.notifyWebhook': 'Notificações webhook', - 'settings.notificationsDisabled': 'As notificações não estão configuradas. Peça a um administrador para ativar notificações por e-mail ou webhook.', - 'settings.notificationsActive': 'Canal ativo', - 'settings.notificationsManagedByAdmin': 'Os eventos de notificação são configurados pelo administrador.', - 'admin.notifications.title': 'Notificações', - 'admin.notifications.hint': 'Escolha um canal de notificação. Apenas um pode estar ativo por vez.', - 'admin.notifications.none': 'Desativado', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Salvar configurações de notificação', - 'admin.notifications.saved': 'Configurações de notificação salvas', - 'admin.notifications.testWebhook': 'Enviar webhook de teste', - 'admin.notifications.testWebhookSuccess': 'Webhook de teste enviado com sucesso', - 'admin.notifications.testWebhookFailed': 'Falha ao enviar webhook de teste', - 'admin.smtp.title': 'E-mail e notificações', - 'admin.smtp.hint': 'Configuração SMTP para envio de notificações por e-mail.', - 'admin.smtp.testButton': 'Enviar e-mail de teste', - 'admin.webhook.hint': 'Enviar notificações para um webhook externo (Discord, Slack, etc.).', - 'admin.smtp.testSuccess': 'E-mail de teste enviado com sucesso', - 'admin.smtp.testFailed': 'Falha ao enviar e-mail de teste', - 'dayplan.icsTooltip': 'Exportar calendário (ICS)', - 'share.linkTitle': 'Link público', - 'share.linkHint': 'Crie um link que qualquer pessoa pode usar para ver esta viagem sem fazer login. Somente leitura — sem edição possível.', - 'share.createLink': 'Criar link', - 'share.deleteLink': 'Excluir link', - 'share.createError': 'Não foi possível criar o link', - 'common.copy': 'Copiar', - 'common.copied': 'Copiado', - 'share.permMap': 'Mapa e plano', - 'share.permBookings': 'Reservas', - 'share.permPacking': 'Mala', - 'shared.expired': 'Link expirado ou inválido', - 'shared.expiredHint': 'Este link de viagem compartilhado não está mais ativo.', - 'shared.readOnly': 'Visualização somente leitura', - 'shared.tabPlan': 'Plano', - 'shared.tabBookings': 'Reservas', - 'shared.tabPacking': 'Bagagem', - 'shared.tabBudget': 'Orçamento', - 'shared.tabChat': 'Chat', - 'shared.days': 'dias', - 'shared.places': 'lugares', - 'shared.other': 'Outros', - 'shared.totalBudget': 'Orçamento total', - 'shared.messages': 'mensagens', - 'shared.sharedVia': 'Compartilhado via', - 'shared.confirmed': 'Confirmado', - 'shared.pending': 'Pendente', - 'share.permBudget': 'Orçamento', - 'share.permCollab': 'Chat', - 'settings.on': 'Ligado', - 'settings.off': 'Desligado', - 'settings.account': 'Conta', - 'settings.about': 'Sobre', - 'settings.about.reportBug': 'Reportar um bug', - 'settings.about.reportBugHint': 'Encontrou um problema? Nos avise', - 'settings.about.featureRequest': 'Solicitar recurso', - 'settings.about.featureRequestHint': 'Sugira um novo recurso', - 'settings.about.wikiHint': 'Documentação e guias', - 'settings.about.supporters.badge': 'Apoiadores Mensais', - 'settings.about.supporters.title': 'Companheiros de viagem do TREK', - 'settings.about.supporters.subtitle': 'Enquanto você planeja sua próxima rota, essas pessoas planejam junto o futuro do TREK. A contribuição mensal delas vai direto para o desenvolvimento e horas reais investidas — para o TREK continuar Open Source.', - 'settings.about.supporters.since': 'apoiador desde {date}', - 'settings.about.supporters.tierEmpty': 'Seja o primeiro', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK é um planejador de viagens auto-hospedado que ajuda você a organizar suas viagens da primeira ideia à última lembrança. Planejamento diário, orçamento, listas de bagagem, fotos e muito mais — tudo em um só lugar, no seu próprio servidor.', - 'settings.about.madeWith': 'Feito com', - 'settings.about.madeBy': 'por Maurice e uma crescente comunidade open-source.', - 'settings.username': 'Nome de usuário', - 'settings.email': 'E-mail', - 'settings.role': 'Função', - 'settings.roleAdmin': 'Administrador', - 'settings.oidcLinked': 'Vinculado a', - 'settings.changePassword': 'Alterar senha', - 'settings.currentPassword': 'Senha atual', - 'settings.currentPasswordRequired': 'A senha atual é obrigatória', - 'settings.newPassword': 'Nova senha', - 'settings.confirmPassword': 'Confirmar nova senha', - 'settings.updatePassword': 'Atualizar senha', - 'settings.passwordRequired': 'Informe a senha atual e a nova', - 'settings.passwordTooShort': 'A senha deve ter pelo menos 8 caracteres', - 'settings.passwordMismatch': 'As senhas não coincidem', - 'settings.passwordWeak': 'A senha deve ter maiúscula, minúscula, número e um caractere especial', - 'settings.passwordChanged': 'Senha alterada com sucesso', - 'settings.deleteAccount': 'Excluir conta', - 'settings.deleteAccountTitle': 'Excluir sua conta?', - 'settings.deleteAccountWarning': 'Sua conta e todas as viagens, lugares e arquivos serão excluídos permanentemente. Esta ação não pode ser desfeita.', - 'settings.deleteAccountConfirm': 'Excluir permanentemente', - 'settings.deleteBlockedTitle': 'Exclusão não permitida', - 'settings.deleteBlockedMessage': 'Você é o único administrador. Promova outro usuário a administrador antes de excluir sua conta.', - 'settings.roleUser': 'Usuário', - 'settings.saveProfile': 'Salvar perfil', - 'settings.toast.mapSaved': 'Configurações do mapa salvas', - 'settings.toast.keysSaved': 'Chaves de API salvas', - 'settings.toast.displaySaved': 'Configurações de exibição salvas', - 'settings.toast.profileSaved': 'Perfil salvo', - 'settings.uploadAvatar': 'Enviar foto do perfil', - 'settings.removeAvatar': 'Remover foto do perfil', - 'settings.avatarUploaded': 'Foto do perfil atualizada', - 'settings.avatarRemoved': 'Foto do perfil removida', - 'settings.avatarError': 'Falha no envio', - 'settings.mfa.title': 'Autenticação em duas etapas (2FA)', - 'settings.mfa.description': 'Adiciona uma segunda etapa ao entrar com e-mail e senha. Use um app autenticador (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'O administrador exige autenticação em dois fatores. Configure um app autenticador abaixo antes de continuar.', - 'settings.mfa.backupTitle': 'Códigos de backup', - 'settings.mfa.backupDescription': 'Use estes códigos únicos se perder acesso ao app autenticador.', - 'settings.mfa.backupWarning': 'Salve estes códigos agora. Cada código pode ser usado apenas uma vez.', - 'settings.mfa.backupCopy': 'Copiar códigos', - 'settings.mfa.backupDownload': 'Baixar TXT', - 'settings.mfa.backupPrint': 'Imprimir / PDF', - 'settings.mfa.backupCopied': 'Códigos de backup copiados', - 'settings.mfa.enabled': 'O 2FA está ativado na sua conta.', - 'settings.mfa.disabled': 'O 2FA não está ativado.', - 'settings.mfa.setup': 'Configurar autenticador', - 'settings.mfa.scanQr': 'Leia este QR code no app ou digite o segredo manualmente.', - 'settings.mfa.secretLabel': 'Chave secreta (entrada manual)', - 'settings.mfa.codePlaceholder': 'Código de 6 dígitos', - 'settings.mfa.enable': 'Ativar 2FA', - 'settings.mfa.cancelSetup': 'Cancelar', - 'settings.mfa.disableTitle': 'Desativar 2FA', - 'settings.mfa.disableHint': 'Digite sua senha e um código atual do autenticador.', - 'settings.mfa.disable': 'Desativar 2FA', - 'settings.mfa.toastEnabled': 'Autenticação em duas etapas ativada', - 'settings.mfa.toastDisabled': 'Autenticação em duas etapas desativada', - 'settings.mfa.demoBlocked': 'Indisponível no modo demonstração', - 'settings.mcp.title': 'Configuração MCP', - 'settings.mcp.endpoint': 'Endpoint MCP', - 'settings.mcp.clientConfig': 'Configuração do cliente', - 'settings.mcp.clientConfigHint': 'Substitua por um token de API da lista abaixo. O caminho para o npx pode precisar ser ajustado para o seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Substitua e pelas credenciais exibidas no cliente OAuth 2.1 criado acima. O mcp-remote abrirá seu navegador para concluir a autorização na primeira conexão. O caminho para o npx pode precisar ser ajustado para seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).', - 'settings.mcp.copy': 'Copiar', - 'settings.mcp.copied': 'Copiado!', - 'settings.mcp.apiTokens': 'Tokens de API', - 'settings.mcp.createToken': 'Criar novo token', - 'settings.mcp.noTokens': 'Nenhum token ainda. Crie um para conectar clientes MCP.', - 'settings.mcp.tokenCreatedAt': 'Criado em', - 'settings.mcp.tokenUsedAt': 'Usado em', - 'settings.mcp.deleteTokenTitle': 'Excluir token', - 'settings.mcp.deleteTokenMessage': 'Este token deixará de funcionar imediatamente. Qualquer cliente MCP que o utilize perderá o acesso.', - 'settings.mcp.modal.createTitle': 'Criar token de API', - 'settings.mcp.modal.tokenName': 'Nome do token', - 'settings.mcp.modal.tokenNamePlaceholder': 'ex.: Claude Desktop, Notebook do trabalho', - 'settings.mcp.modal.creating': 'Criando…', - 'settings.mcp.modal.create': 'Criar token', - 'settings.mcp.modal.createdTitle': 'Token criado', - 'settings.mcp.modal.createdWarning': 'Este token será exibido apenas uma vez. Copie e guarde agora — não poderá ser recuperado.', - 'settings.mcp.modal.done': 'Concluído', - 'settings.mcp.toast.created': 'Token criado', - 'settings.mcp.toast.createError': 'Falha ao criar token', - 'settings.mcp.toast.deleted': 'Token excluído', - 'settings.mcp.toast.deleteError': 'Falha ao excluir token', - 'settings.mcp.apiTokensDeprecated': 'Os tokens de API estão obsoletos e serão removidos em uma versão futura. Por favor, use Clientes OAuth 2.1.', - 'settings.oauth.clients': 'Clientes OAuth 2.1', - 'settings.oauth.clientsHint': 'Registre clientes OAuth 2.1 para permitir que aplicações MCP de terceiros (Claude Web, Cursor, etc.) se conectem sem tokens estáticos.', - 'settings.oauth.createClient': 'Novo cliente', - 'settings.oauth.noClients': 'Nenhum cliente OAuth registrado.', - 'settings.oauth.clientId': 'ID do cliente', - 'settings.oauth.clientSecret': 'Segredo do cliente', - 'settings.oauth.deleteClient': 'Excluir cliente', - 'settings.oauth.deleteClientMessage': 'Este cliente e todas as sessões ativas serão removidos permanentemente. Qualquer aplicação que o utilize perderá o acesso imediatamente.', - 'settings.oauth.rotateSecret': 'Renovar segredo', - 'settings.oauth.rotateSecretMessage': 'Um novo segredo de cliente será gerado e todas as sessões existentes serão invalidadas imediatamente. Atualize sua aplicação antes de fechar esta janela.', - 'settings.oauth.rotateSecretConfirm': 'Renovar', - 'settings.oauth.rotateSecretConfirming': 'Renovando…', - 'settings.oauth.rotateSecretDoneTitle': 'Novo segredo gerado', - 'settings.oauth.rotateSecretDoneWarning': 'Este segredo é exibido apenas uma vez. Copie-o agora e atualize sua aplicação — todas as sessões anteriores foram invalidadas.', - 'settings.oauth.activeSessions': 'Sessões OAuth ativas', - 'settings.oauth.sessionScopes': 'Escopos', - 'settings.oauth.sessionExpires': 'Expira', - 'settings.oauth.revoke': 'Revogar', - 'settings.oauth.revokeSession': 'Revogar sessão', - 'settings.oauth.revokeSessionMessage': 'Isso revogará imediatamente o acesso desta sessão OAuth.', - 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth', - 'settings.oauth.modal.presets': 'Configurações rápidas', - 'settings.oauth.modal.clientName': 'Nome da aplicação', - 'settings.oauth.modal.clientNamePlaceholder': 'ex.: Claude Web, Meu app MCP', - 'settings.oauth.modal.redirectUris': 'URIs de redirecionamento', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Uma URI por linha. HTTPS obrigatório (localhost isento). Correspondência exata.', - 'settings.oauth.modal.scopes': 'Escopos permitidos', - 'settings.oauth.modal.scopesHint': 'list_trips e get_trip_summary estão sempre disponíveis — sem escopo necessário. Permitem à IA descobrir IDs de viagem.', - 'settings.oauth.modal.selectAll': 'Selecionar tudo', - 'settings.oauth.modal.deselectAll': 'Desmarcar tudo', - 'settings.oauth.modal.creating': 'Registrando…', - 'settings.oauth.modal.create': 'Registrar cliente', - 'settings.oauth.modal.createdTitle': 'Cliente registrado', - 'settings.oauth.modal.createdWarning': 'O segredo do cliente é exibido apenas uma vez. Copie-o agora — não pode ser recuperado.', - 'settings.oauth.toast.createError': 'Falha ao registrar cliente OAuth', - 'settings.oauth.toast.deleted': 'Cliente OAuth excluído', - 'settings.oauth.toast.deleteError': 'Falha ao excluir cliente OAuth', - 'settings.oauth.toast.revoked': 'Sessão revogada', - 'settings.oauth.toast.revokeError': 'Falha ao revogar sessão', - 'settings.oauth.toast.rotateError': 'Falha ao renovar segredo do cliente', - 'settings.oauth.modal.machineClient': 'Cliente de máquina (sem login no navegador)', - 'settings.oauth.modal.machineClientHint': 'Usa o grant client_credentials — sem URIs de redirecionamento. O token é emitido diretamente via client_id + client_secret e age como você dentro dos escopos selecionados.', - 'settings.oauth.modal.machineClientUsage': 'Obter token: POST /oauth/token com grant_type=client_credentials, client_id e client_secret. Sem navegador, sem refresh token.', - 'settings.oauth.badge.machine': 'máquina', - 'settings.mustChangePassword': 'Você deve alterar sua senha antes de continuar. Defina uma nova senha abaixo.', - - // Login - 'login.error': 'Falha no login. Verifique suas credenciais.', - 'login.tagline': 'Suas viagens.\nSeu plano.', - 'login.description': 'Planeje viagens em equipe com mapas interativos, orçamento e sincronização em tempo real.', - 'login.features.maps': 'Mapas interativos', - 'login.features.mapsDesc': 'Google Places, rotas e agrupamento', - 'login.features.realtime': 'Sincronização em tempo real', - 'login.features.realtimeDesc': 'Planejem juntos via WebSocket', - 'login.features.budget': 'Controle de orçamento', - 'login.features.budgetDesc': 'Categorias, gráficos e custo por pessoa', - 'login.features.collab': 'Colaboração', - 'login.features.collabDesc': 'Vários usuários com viagens compartilhadas', - 'login.features.packing': 'Listas de malas', - 'login.features.packingDesc': 'Categorias, progresso e sugestões', - 'login.features.bookings': 'Reservas', - 'login.features.bookingsDesc': 'Voos, hotéis, restaurantes e mais', - 'login.features.files': 'Documentos', - 'login.features.filesDesc': 'Envie e gerencie documentos', - 'login.features.routes': 'Rotas inteligentes', - 'login.features.routesDesc': 'Otimize e exporte para o Google Maps', - 'login.selfHosted': 'Auto-hospedado \u00B7 Código aberto \u00B7 Seus dados são seus', - 'login.title': 'Entrar', - 'login.subtitle': 'Bem-vindo de volta', - 'login.signingIn': 'Entrando…', - 'login.signIn': 'Entrar', - 'login.createAdmin': 'Criar conta de administrador', - 'login.createAdminHint': 'Configure a primeira conta de administrador do TREK.', - 'login.setNewPassword': 'Definir nova senha', - 'login.setNewPasswordHint': 'Você deve alterar sua senha antes de continuar.', - 'login.createAccount': 'Criar conta', - 'login.createAccountHint': 'Cadastre uma nova conta.', - 'login.creating': 'Criando…', - 'login.noAccount': 'Não tem conta?', - 'login.hasAccount': 'Já tem conta?', - 'login.register': 'Cadastrar', - 'login.emailPlaceholder': 'seu@email.com', - 'login.username': 'Nome de usuário', - 'login.oidc.registrationDisabled': 'Cadastro desativado. Fale com o administrador.', - 'login.oidc.noEmail': 'Nenhum e-mail recebido do provedor.', - 'login.oidc.tokenFailed': 'Falha na autenticação.', - 'login.oidc.invalidState': 'Sessão inválida. Tente novamente.', - 'login.demoFailed': 'Falha no login de demonstração', - 'login.oidcSignIn': 'Entrar com {name}', - 'login.oidcOnly': 'Login por senha desativado. Use o provedor SSO.', - 'login.oidcLoggedOut': 'Você foi desconectado. Entre novamente usando o provedor SSO.', - 'login.demoHint': 'Experimente a demonstração — sem cadastro', - 'login.mfaTitle': 'Autenticação em duas etapas', - 'login.mfaSubtitle': 'Digite o código de 6 dígitos do seu app autenticador.', - 'login.mfaCodeLabel': 'Código de verificação', - 'login.mfaCodeRequired': 'Digite o código do app autenticador.', - 'login.mfaHint': 'Abra o Google Authenticator, Authy ou outro app TOTP.', - 'login.mfaBack': '← Voltar ao login', - 'login.mfaVerify': 'Verificar', - 'login.invalidInviteLink': 'Link de convite inválido ou expirado', - 'login.oidcFailed': 'Falha no login OIDC', - 'login.usernameRequired': 'Nome de usuário é obrigatório', - 'login.passwordMinLength': 'A senha deve ter pelo menos 8 caracteres', - 'login.forgotPassword': 'Esqueceu a senha?', - 'login.forgotPasswordTitle': 'Redefinir sua senha', - 'login.forgotPasswordBody': 'Digite o e-mail cadastrado. Se houver uma conta, enviaremos um link de redefinição.', - 'login.forgotPasswordSubmit': 'Enviar link', - 'login.forgotPasswordSentTitle': 'Verifique seu e-mail', - 'login.forgotPasswordSentBody': 'Se houver uma conta para esse e-mail, o link está a caminho. Ele expira em 60 minutos.', - 'login.forgotPasswordSmtpHintOff': 'Observação: seu administrador não configurou SMTP, então o link de redefinição será gravado no console do servidor em vez de ser enviado por e-mail.', - 'login.backToLogin': 'Voltar ao login', - 'login.newPassword': 'Nova senha', - 'login.confirmPassword': 'Confirmar nova senha', - 'login.passwordsDontMatch': 'As senhas não coincidem', - 'login.mfaCode': 'Código 2FA', - 'login.resetPasswordTitle': 'Definir uma nova senha', - 'login.resetPasswordBody': 'Escolha uma senha forte que você ainda não tenha usado aqui. Mínimo de 8 caracteres.', - 'login.resetPasswordMfaBody': 'Digite seu código 2FA ou um código de backup para concluir a redefinição.', - 'login.resetPasswordSubmit': 'Redefinir senha', - 'login.resetPasswordVerify': 'Verificar e redefinir', - 'login.resetPasswordSuccessTitle': 'Senha atualizada', - 'login.resetPasswordSuccessBody': 'Agora você pode entrar com sua nova senha.', - 'login.resetPasswordInvalidLink': 'Link de redefinição inválido', - 'login.resetPasswordInvalidLinkBody': 'Este link está ausente ou corrompido. Solicite um novo para continuar.', - 'login.resetPasswordFailed': 'Falha na redefinição. O link pode ter expirado.', - - // Register - 'register.passwordMismatch': 'As senhas não coincidem', - 'register.passwordTooShort': 'A senha deve ter pelo menos 8 caracteres', - 'register.failed': 'Falha no cadastro', - 'register.getStarted': 'Começar', - 'register.subtitle': 'Crie uma conta e comece a planejar suas viagens.', - 'register.feature1': 'Viagens ilimitadas', - 'register.feature2': 'Mapa interativo', - 'register.feature3': 'Gerencie lugares e categorias', - 'register.feature4': 'Acompanhe reservas', - 'register.feature5': 'Listas de malas', - 'register.feature6': 'Fotos e arquivos', - 'register.createAccount': 'Criar conta', - 'register.startPlanning': 'Comece a planejar', - 'register.minChars': 'Mín. 6 caracteres', - 'register.confirmPassword': 'Confirmar senha', - 'register.repeatPassword': 'Repita a senha', - 'register.registering': 'Cadastrando...', - 'register.register': 'Cadastrar', - 'register.hasAccount': 'Já tem conta?', - 'register.signIn': 'Entrar', - - // Admin - 'admin.title': 'Administração', - 'admin.subtitle': 'Gestão de usuários e configurações do sistema', - 'admin.tabs.users': 'Usuários', - 'admin.tabs.categories': 'Categorias', - 'admin.tabs.backup': 'Backup', - 'admin.stats.users': 'Usuários', - 'admin.stats.trips': 'Viagens', - 'admin.stats.places': 'Lugares', - 'admin.stats.photos': 'Fotos', - 'admin.stats.files': 'Arquivos', - 'admin.table.user': 'Usuário', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Função', - 'admin.table.created': 'Criado', - 'admin.table.lastLogin': 'Último acesso', - 'admin.table.actions': 'Ações', - 'admin.you': '(Você)', - 'admin.editUser': 'Editar usuário', - 'admin.newPassword': 'Nova senha', - 'admin.newPasswordHint': 'Deixe em branco para manter a senha atual', - 'admin.deleteUser': 'Excluir o usuário "{name}"? Todas as viagens serão excluídas permanentemente.', - 'admin.deleteUserTitle': 'Excluir usuário', - 'admin.newPasswordPlaceholder': 'Digite a nova senha…', - 'admin.toast.loadError': 'Falha ao carregar dados do admin', - 'admin.toast.userUpdated': 'Usuário atualizado', - 'admin.toast.updateError': 'Falha ao atualizar', - 'admin.toast.userDeleted': 'Usuário excluído', - 'admin.toast.deleteError': 'Falha ao excluir', - 'admin.toast.cannotDeleteSelf': 'Não é possível excluir a própria conta', - 'admin.toast.userCreated': 'Usuário criado', - 'admin.toast.createError': 'Falha ao criar usuário', - 'admin.toast.fieldsRequired': 'Nome de usuário, e-mail e senha são obrigatórios', - 'admin.createUser': 'Criar usuário', - 'admin.invite.title': 'Links de convite', - 'admin.invite.subtitle': 'Crie links de cadastro de uso único', - 'admin.invite.create': 'Criar link', - 'admin.invite.createAndCopy': 'Criar e copiar', - 'admin.invite.empty': 'Nenhum link de convite criado ainda', - 'admin.invite.maxUses': 'Máx. usos', - 'admin.invite.expiry': 'Expira após', - 'admin.invite.uses': 'usado(s)', - 'admin.invite.expiresAt': 'expira', - 'admin.invite.createdBy': 'por', - 'admin.invite.active': 'Ativo', - 'admin.invite.expired': 'Expirado', - 'admin.invite.usedUp': 'Esgotado', - 'admin.invite.copied': 'Link de convite copiado para a área de transferência', - 'admin.invite.copyLink': 'Copiar link', - 'admin.invite.deleted': 'Link de convite excluído', - 'admin.invite.createError': 'Falha ao criar link de convite', - 'admin.invite.deleteError': 'Falha ao excluir link de convite', - 'admin.tabs.settings': 'Configurações', - 'admin.allowRegistration': 'Permitir cadastro', - 'admin.allowRegistrationHint': 'Novos usuários podem se cadastrar sozinhos', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Exigir autenticação em dois fatores (2FA)', - 'admin.requireMfaHint': 'Usuários sem 2FA precisam concluir a configuração em Configurações antes de usar o app.', - 'admin.apiKeys': 'Chaves de API', - 'admin.apiKeysHint': 'Opcional. Habilita dados estendidos de lugares, como fotos e clima.', - 'admin.mapsKey': 'Chave da API Google Maps', - 'admin.mapsKeyHint': 'Necessária para busca de lugares. Obtenha em console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Sem chave de API, o OpenStreetMap é usado na busca. Com uma chave Google, também podem ser carregadas fotos, avaliações e horários. Obtenha em console.cloud.google.com.', - 'admin.recommended': 'Recomendado', - 'admin.weatherKey': 'Chave OpenWeatherMap', - 'admin.weatherKeyHint': 'Para dados meteorológicos. Grátis em openweathermap.org', - 'admin.validateKey': 'Testar', - 'admin.keyValid': 'Conectado', - 'admin.keyInvalid': 'Inválida', - 'admin.keySaved': 'Chaves de API salvas', - 'admin.oidcTitle': 'Login Único (OIDC)', - 'admin.oidcSubtitle': 'Permitir login via provedores externos como Google, Apple, Authentik ou Keycloak.', - 'admin.oidcDisplayName': 'Nome exibido', - 'admin.oidcIssuer': 'URL do emissor', - 'admin.oidcIssuerHint': 'URL do emissor OpenID Connect do provedor, ex.: https://accounts.google.com', - 'admin.oidcSaved': 'Configuração OIDC salva', - 'admin.oidcOnlyMode': 'Desativar login por senha', - 'admin.oidcOnlyModeHint': 'Quando ativado, só é permitido login SSO. Login e cadastro por senha ficam bloqueados.', - - // File Types - 'admin.fileTypes': 'Tipos de arquivo permitidos', - 'admin.fileTypesHint': 'Configure quais tipos de arquivo os usuários podem enviar.', - 'admin.fileTypesFormat': 'Extensões separadas por vírgula (ex.: jpg,png,pdf,doc). Use * para permitir todos.', - 'admin.fileTypesSaved': 'Configurações de tipos de arquivo salvas', - - // Packing Templates & Bag Tracking - 'admin.placesPhotos.title': 'Fotos de Locais', - 'admin.placesPhotos.subtitle': 'Busca fotos da Google Places API. Desative para economizar cota da API. Fotos do Wikimedia não são afetadas.', - 'admin.placesAutocomplete.title': 'Autocompletar de Locais', - 'admin.placesAutocomplete.subtitle': 'Usa a Google Places API para sugestões de pesquisa. Desative para economizar cota da API.', - 'admin.placesDetails.title': 'Detalhes do Local', - 'admin.placesDetails.subtitle': 'Busca informações detalhadas do local (horários, avaliação, site) da Google Places API. Desative para economizar cota da API.', - 'admin.bagTracking.title': 'Rastreamento de malas', - 'admin.bagTracking.subtitle': 'Ativar peso e atribuição de mala para itens da lista', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Mensagens em tempo real para colaboração', - 'admin.collab.notes.title': 'Notas', - 'admin.collab.notes.subtitle': 'Notas e documentos compartilhados', - 'admin.collab.polls.title': 'Enquetes', - 'admin.collab.polls.subtitle': 'Enquetes e votações em grupo', - 'admin.collab.whatsnext.title': 'Próximos passos', - 'admin.collab.whatsnext.subtitle': 'Sugestões de atividades e próximos passos', - 'admin.tabs.config': 'Personalização', - 'admin.tabs.defaults': 'Padrões do usuário', - 'admin.defaultSettings.title': 'Configurações padrão do usuário', - 'admin.defaultSettings.description': 'Defina padrões para toda a instância. Usuários que não alteraram uma configuração verão esses valores. As próprias alterações deles sempre têm prioridade.', - 'admin.defaultSettings.saved': 'Padrão salvo', - 'admin.defaultSettings.reset': 'Redefinir para o padrão integrado', - 'admin.defaultSettings.resetToBuiltIn': 'redefinir', - 'admin.tabs.templates': 'Modelos de mala', - 'admin.packingTemplates.title': 'Modelos de mala', - 'admin.packingTemplates.subtitle': 'Crie listas de mala reutilizáveis para suas viagens', - 'admin.packingTemplates.create': 'Novo modelo', - 'admin.packingTemplates.namePlaceholder': 'Nome do modelo (ex.: Praia)', - 'admin.packingTemplates.empty': 'Nenhum modelo criado ainda', - 'admin.packingTemplates.items': 'itens', - 'admin.packingTemplates.categories': 'categorias', - 'admin.packingTemplates.itemName': 'Nome do item', - 'admin.packingTemplates.itemCategory': 'Categoria', - 'admin.packingTemplates.categoryName': 'Nome da categoria (ex.: Roupas)', - 'admin.packingTemplates.addCategory': 'Adicionar categoria', - 'admin.packingTemplates.created': 'Modelo criado', - 'admin.packingTemplates.deleted': 'Modelo excluído', - 'admin.packingTemplates.loadError': 'Falha ao carregar modelos', - 'admin.packingTemplates.createError': 'Falha ao criar modelo', - 'admin.packingTemplates.deleteError': 'Falha ao excluir modelo', - 'admin.packingTemplates.saveError': 'Falha ao salvar', - - // Addons - 'admin.tabs.addons': 'Complementos', - 'admin.addons.title': 'Complementos', - 'admin.addons.subtitle': 'Ative ou desative recursos para personalizar sua experiência no TREK.', - 'admin.addons.catalog.memories.name': 'Memórias', - 'admin.addons.catalog.memories.description': 'Álbuns de fotos compartilhados em cada viagem', - 'admin.addons.catalog.packing.name': 'Listas', - 'admin.addons.catalog.packing.description': 'Listas de bagagem e tarefas a fazer para suas viagens', - 'admin.addons.catalog.budget.name': 'Orçamento', - 'admin.addons.catalog.budget.description': 'Acompanhe despesas e planeje o orçamento da viagem', - 'admin.addons.catalog.documents.name': 'Documentos', - 'admin.addons.catalog.documents.description': 'Armazene e gerencie documentos de viagem', - 'admin.addons.catalog.vacay.name': 'Férias', - 'admin.addons.catalog.vacay.description': 'Planejador de férias pessoal com visão em calendário', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Mapa mundial com países visitados e estatísticas', - 'admin.addons.catalog.collab.name': 'Colab', - 'admin.addons.catalog.collab.description': 'Notas, enquetes e chat em tempo real para planejar a viagem', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol para integração com assistentes de IA', - 'admin.addons.subtitleBefore': 'Ative ou desative recursos para personalizar sua ', - 'admin.addons.subtitleAfter': ' experiência.', - 'admin.addons.enabled': 'Ativado', - 'admin.addons.disabled': 'Desativado', - 'admin.addons.type.trip': 'Viagem', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integração', - 'admin.addons.tripHint': 'Disponível como aba em cada viagem', - 'admin.addons.globalHint': 'Disponível como seção própria na navegação principal', - 'admin.addons.toast.updated': 'Complemento atualizado', - 'admin.addons.toast.error': 'Falha ao atualizar complemento', - 'admin.addons.noAddons': 'Nenhum complemento disponível', - 'admin.addons.integrationHint': 'Serviços de backend e integrações de API sem página dedicada', - // Weather info - 'admin.weather.title': 'Dados meteorológicos', - 'admin.weather.badge': 'Desde 24 de março de 2026', - 'admin.weather.description': 'O TREK usa Open-Meteo como fonte de clima. Open-Meteo é um serviço gratuito e de código aberto — sem chave de API.', - 'admin.weather.forecast': 'Previsão de 16 dias', - 'admin.weather.forecastDesc': 'Antes eram 5 dias (OpenWeatherMap)', - 'admin.weather.climate': 'Dados climáticos históricos', - 'admin.weather.climateDesc': 'Médias dos últimos 85 anos para dias além da previsão de 16 dias', - 'admin.weather.requests': '10.000 requisições / dia', - 'admin.weather.requestsDesc': 'Grátis, sem chave de API', - 'admin.weather.locationHint': 'O clima usa o primeiro lugar com coordenadas de cada dia. Se nenhum lugar estiver atribuído ao dia, qualquer lugar da lista serve como referência.', - - 'admin.tabs.audit': 'Auditoria', - - 'admin.audit.subtitle': 'Eventos sensíveis de segurança e administração (backups, usuários, 2FA, configurações).', - 'admin.audit.empty': 'Nenhum registro de auditoria.', - 'admin.audit.refresh': 'Atualizar', - 'admin.audit.loadMore': 'Carregar mais', - 'admin.audit.showing': '{count} carregados · {total} no total', - 'admin.audit.col.time': 'Hora', - 'admin.audit.col.user': 'Usuário', - 'admin.audit.col.action': 'Ação', - 'admin.audit.col.resource': 'Recurso', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Detalhes', - - // GitHub - 'admin.tabs.github': 'GitHub', - 'admin.github.title': 'Histórico de versões', - 'admin.github.subtitle': 'Últimas atualizações de {repo}', - 'admin.github.latest': 'Mais recente', - 'admin.github.prerelease': 'Pré-lançamento', - 'admin.github.showDetails': 'Mostrar detalhes', - 'admin.github.hideDetails': 'Ocultar detalhes', - 'admin.github.loadMore': 'Carregar mais', - 'admin.github.loading': 'Carregando...', - 'admin.github.error': 'Falha ao carregar versões', - 'admin.github.by': 'por', - 'admin.github.support': 'Ajuda a continuar desenvolvendo o TREK', - - 'admin.update.available': 'Atualização disponível', - 'admin.update.text': 'O TREK {version} está disponível. Você está na {current}.', - 'admin.update.button': 'Ver no GitHub', - 'admin.update.install': 'Instalar atualização', - 'admin.update.confirmTitle': 'Instalar atualização?', - 'admin.update.confirmText': 'O TREK será atualizado de {current} para {version}. O servidor reiniciará automaticamente em seguida.', - 'admin.update.dataInfo': 'Todos os seus dados (viagens, usuários, chaves de API, envios, Vacay, Atlas, orçamentos) serão preservados.', - 'admin.update.warning': 'O app ficará brevemente indisponível durante o reinício.', - 'admin.update.confirm': 'Atualizar agora', - 'admin.update.installing': 'Atualizando…', - 'admin.update.success': 'Atualização instalada! O servidor está reiniciando…', - 'admin.update.failed': 'Falha na atualização', - 'admin.update.backupHint': 'Recomendamos criar um backup antes de atualizar.', - 'admin.update.backupLink': 'Ir para Backup', - 'admin.update.howTo': 'Como atualizar', - 'admin.update.dockerText': 'Sua instância TREK roda no Docker. Para atualizar para {version}, execute no servidor:', - 'admin.update.reloadHint': 'Recarregue a página em alguns segundos.', - - // Vacay addon - 'vacay.subtitle': 'Planeje e gerencie dias de férias', - 'vacay.settings': 'Configurações', - 'vacay.year': 'Ano', - 'vacay.addYear': 'Adicionar próximo ano', - 'vacay.addPrevYear': 'Adicionar ano anterior', - 'vacay.removeYear': 'Remover ano', - 'vacay.removeYearConfirm': 'Remover {year}?', - 'vacay.removeYearHint': 'Todas as entradas de férias e feriados da empresa deste ano serão excluídas permanentemente.', - 'vacay.remove': 'Remover', - 'vacay.persons': 'Pessoas', - 'vacay.noPersons': 'Nenhuma pessoa adicionada', - 'vacay.addPerson': 'Adicionar pessoa', - 'vacay.editPerson': 'Editar pessoa', - 'vacay.removePerson': 'Remover pessoa', - 'vacay.removePersonConfirm': 'Remover {name}?', - 'vacay.removePersonHint': 'Todas as entradas de férias desta pessoa serão excluídas permanentemente.', - 'vacay.personName': 'Nome', - 'vacay.personNamePlaceholder': 'Digite o nome', - 'vacay.color': 'Cor', - 'vacay.add': 'Adicionar', - 'vacay.legend': 'Legenda', - 'vacay.publicHoliday': 'Feriado nacional', - 'vacay.companyHoliday': 'Feriado da empresa', - 'vacay.weekend': 'Fim de semana', - 'vacay.modeVacation': 'Férias', - 'vacay.modeCompany': 'Feriado da empresa', - 'vacay.entitlement': 'Direito', - 'vacay.entitlementDays': 'Dias', - 'vacay.used': 'Usados', - 'vacay.remaining': 'Restantes', - 'vacay.carriedOver': 'de {year}', - 'vacay.blockWeekends': 'Bloquear fins de semana', - 'vacay.weekendDays': 'Dias de fim de semana', - 'vacay.mon': 'Seg', - 'vacay.tue': 'Ter', - 'vacay.wed': 'Qua', - 'vacay.thu': 'Qui', - 'vacay.fri': 'Sex', - 'vacay.sat': 'Sáb', - 'vacay.sun': 'Dom', - 'vacay.blockWeekendsHint': 'Impedir entradas de férias aos sábados e domingos', - 'vacay.publicHolidays': 'Feriados nacionais', - 'vacay.publicHolidaysHint': 'Marcar feriados nacionais no calendário', - 'vacay.selectCountry': 'Selecione o país', - 'vacay.selectRegion': 'Selecione a região (opcional)', - 'vacay.addCalendar': 'Adicionar calendário', - 'vacay.calendarLabel': 'Rótulo (opcional)', - 'vacay.calendarColor': 'Cor', - 'vacay.noCalendars': 'Nenhum calendário de feriados adicionado ainda', - 'vacay.companyHolidays': 'Feriados da empresa', - 'vacay.companyHolidaysHint': 'Permitir marcar dias de feriado em toda a empresa', - 'vacay.companyHolidaysNoDeduct': 'Feriados da empresa não contam como dias de férias.', - 'vacay.weekStart': 'Semana começa em', - 'vacay.weekStartHint': 'Escolha se a semana começa na segunda-feira ou no domingo', - 'vacay.carryOver': 'Acúmulo', - 'vacay.carryOverHint': 'Levar automaticamente os dias de férias restantes para o ano seguinte', - 'vacay.sharing': 'Compartilhamento', - 'vacay.sharingHint': 'Compartilhe seu plano de férias com outros usuários do TREK', - 'vacay.owner': 'Proprietário', - 'vacay.shareEmailPlaceholder': 'E-mail do usuário TREK', - 'vacay.shareSuccess': 'Plano compartilhado com sucesso', - 'vacay.shareError': 'Não foi possível compartilhar o plano', - 'vacay.dissolve': 'Encerrar fusão', - 'vacay.dissolveHint': 'Separar os calendários novamente. Suas entradas serão mantidas.', - 'vacay.dissolveAction': 'Encerrar', - 'vacay.dissolved': 'Calendário separado', - 'vacay.fusedWith': 'Fundido com', - 'vacay.you': 'você', - 'vacay.noData': 'Sem dados', - 'vacay.changeColor': 'Alterar cor', - 'vacay.inviteUser': 'Convidar usuário', - 'vacay.inviteHint': 'Convide outro usuário TREK para compartilhar um calendário de férias combinado.', - 'vacay.selectUser': 'Selecionar usuário', - 'vacay.sendInvite': 'Enviar convite', - 'vacay.inviteSent': 'Convite enviado', - 'vacay.inviteError': 'Não foi possível enviar o convite', - 'vacay.pending': 'pendente', - 'vacay.noUsersAvailable': 'Nenhum usuário disponível', - 'vacay.accept': 'Aceitar', - 'vacay.decline': 'Recusar', - 'vacay.acceptFusion': 'Aceitar e fundir', - 'vacay.inviteTitle': 'Pedido de fusão', - 'vacay.inviteWantsToFuse': 'quer compartilhar um calendário de férias com você.', - 'vacay.fuseInfo1': 'Ambos verão todas as entradas de férias em um calendário compartilhado.', - 'vacay.fuseInfo2': 'Ambos podem criar e editar entradas um do outro.', - 'vacay.fuseInfo3': 'Ambos podem excluir entradas e alterar direitos de férias.', - 'vacay.fuseInfo4': 'Configurações como feriados nacionais e da empresa são compartilhadas.', - 'vacay.fuseInfo5': 'A fusão pode ser encerrada a qualquer momento por qualquer parte. Suas entradas serão preservadas.', - 'nav.myTrips': 'Minhas viagens', - - // Atlas addon - 'atlas.subtitle': 'Sua pegada de viagens pelo mundo', - 'atlas.countries': 'Países', - 'atlas.trips': 'Viagens', - 'atlas.places': 'Lugares', - 'atlas.unmark': 'Remover', - 'atlas.confirmMark': 'Marcar este país como visitado?', - 'atlas.confirmUnmark': 'Remover este país da lista de visitados?', - 'atlas.confirmUnmarkRegion': 'Remover esta região da lista de visitados?', - 'atlas.markVisited': 'Marcar como visitado', - 'atlas.markVisitedHint': 'Adicionar este país à lista de visitados', - 'atlas.markRegionVisitedHint': 'Adicionar esta região à lista de visitados', - 'atlas.addToBucket': 'Adicionar à lista de desejos', - 'atlas.addPoi': 'Adicionar lugar', - 'atlas.searchCountry': 'Buscar um país...', - 'atlas.bucketNamePlaceholder': 'Nome (país, cidade, lugar…)', - 'atlas.month': 'Mês', - 'atlas.year': 'Ano', - 'atlas.addToBucketHint': 'Salvar como lugar que você quer visitar', - 'atlas.bucketWhen': 'Quando pretende visitar?', - 'atlas.statsTab': 'Estatísticas', - 'atlas.bucketTab': 'Lista de desejos', - 'atlas.addBucket': 'Adicionar à lista de desejos', - 'atlas.bucketNotesPlaceholder': 'Notas (opcional)', - 'atlas.bucketEmpty': 'Sua lista de desejos está vazia', - 'atlas.bucketEmptyHint': 'Adicione lugares que sonha em visitar', - 'atlas.days': 'Dias', - 'atlas.visitedCountries': 'Países visitados', - 'atlas.cities': 'Cidades', - 'atlas.noData': 'Ainda sem dados de viagem', - 'atlas.noDataHint': 'Crie uma viagem e adicione lugares para ver o mapa mundial', - 'atlas.lastTrip': 'Última viagem', - 'atlas.nextTrip': 'Próxima viagem', - 'atlas.daysLeft': 'dias restantes', - 'atlas.streak': 'Sequência', - 'atlas.years': 'anos', - 'atlas.yearInRow': 'ano seguido', - 'atlas.yearsInRow': 'anos seguidos', - 'atlas.tripIn': 'viagem em', - 'atlas.tripsIn': 'viagens em', - 'atlas.since': 'desde', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Ásia', - 'atlas.northAmerica': 'América do Norte', - 'atlas.southAmerica': 'América do Sul', - 'atlas.africa': 'África', - 'atlas.oceania': 'Oceania', - 'atlas.other': 'Outro', - 'atlas.firstVisit': 'Primeira viagem', - 'atlas.lastVisitLabel': 'Última viagem', - 'atlas.tripSingular': 'Viagem', - 'atlas.tripPlural': 'Viagens', - 'atlas.placeVisited': 'Lugar visitado', - 'atlas.placesVisited': 'Lugares visitados', - - // Trip Planner - 'trip.tabs.plan': 'Plano', - 'trip.tabs.transports': 'Transportes', - 'trip.tabs.reservations': 'Reservas', - 'trip.tabs.reservationsShort': 'Reservas', - 'trip.tabs.packing': 'Lista de mala', - 'trip.tabs.packingShort': 'Mala', - 'trip.tabs.lists': 'Listas', - 'trip.tabs.listsShort': 'Listas', - 'trip.tabs.budget': 'Orçamento', - 'trip.tabs.files': 'Arquivos', - 'trip.loading': 'Carregando viagem...', - 'trip.mobilePlan': 'Plano', - 'trip.mobilePlaces': 'Lugares', - 'trip.toast.placeUpdated': 'Lugar atualizado', - 'trip.toast.placeAdded': 'Lugar adicionado', - 'trip.toast.placeDeleted': 'Lugar excluído', - 'trip.toast.selectDay': 'Selecione um dia primeiro', - 'trip.toast.assignedToDay': 'Lugar atribuído ao dia', - 'trip.toast.reorderError': 'Falha ao reordenar', - 'trip.toast.reservationUpdated': 'Reserva atualizada', - 'trip.toast.reservationAdded': 'Reserva adicionada', - 'trip.toast.deleted': 'Excluído', - 'trip.confirm.deletePlace': 'Tem certeza de que deseja excluir este lugar?', - 'trip.confirm.deletePlaces': 'Excluir {count} lugares?', - 'trip.toast.placesDeleted': '{count} lugares excluídos', - 'trip.loadingPhotos': 'Carregando fotos dos lugares...', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Nenhum lugar planejado para este dia', - 'dayplan.addNote': 'Adicionar nota', - 'dayplan.editNote': 'Editar nota', - 'dayplan.noteAdd': 'Adicionar nota', - 'dayplan.noteEdit': 'Editar nota', - 'dayplan.noteTitle': 'Nota', - 'dayplan.noteSubtitle': 'Nota do dia', - 'dayplan.totalCost': 'Custo total', - 'dayplan.days': 'Dias', - 'dayplan.dayN': 'Dia {n}', - 'dayplan.calculating': 'Calculando...', - 'dayplan.route': 'Rota', - 'dayplan.optimize': 'Otimizar', - 'dayplan.optimized': 'Rota otimizada', - 'dayplan.routeError': 'Falha ao calcular a rota', - 'dayplan.toast.needTwoPlaces': 'São necessários pelo menos dois lugares para otimizar a rota', - 'dayplan.toast.routeOptimized': 'Rota otimizada', - 'dayplan.toast.noGeoPlaces': 'Nenhum lugar com coordenadas para calcular a rota', - 'dayplan.confirmed': 'Confirmada', - 'dayplan.pendingRes': 'Pendente', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Exportar plano do dia em PDF', - 'dayplan.pdfError': 'Falha ao exportar PDF', - 'dayplan.cannotReorderTransport': 'Reservas com horário fixo não podem ser reordenadas', - 'dayplan.confirmRemoveTimeTitle': 'Remover horário?', - 'dayplan.confirmRemoveTimeBody': 'Este lugar tem um horário fixo ({time}). Movê-lo removerá o horário e permitirá ordenação livre.', - 'dayplan.confirmRemoveTimeAction': 'Remover horário e mover', - 'dayplan.cannotDropOnTimed': 'Itens não podem ser colocados entre entradas com horário fixo', - 'dayplan.cannotBreakChronology': 'Isso quebraria a ordem cronológica dos itens e reservas agendados', - - // Places Sidebar - 'places.addPlace': 'Adicionar lugar/atividade', - 'places.importFile': 'Importar arquivo', - 'places.sidebarDrop': 'Solte para importar', - 'places.importFileHint': 'Importe arquivos .gpx, .kml ou .kmz de ferramentas como Google My Maps, Google Earth ou um rastreador GPS.', - 'places.importFileDropHere': 'Clique para selecionar um arquivo ou arraste e solte aqui', - 'places.importFileDropActive': 'Solte o arquivo para selecionar', - 'places.importFileUnsupported': 'Tipo de arquivo não suportado. Use .gpx, .kml ou .kmz.', - 'places.importFileTooLarge': 'O arquivo é muito grande. O tamanho máximo de upload é {maxMb} MB.', - 'places.importFileError': 'Importação falhou', - 'places.importAllSkipped': 'Todos os lugares já estavam na viagem.', - 'places.gpxImported': '{count} lugares importados do GPX', - 'places.gpxImportTypes': 'O que deseja importar?', - 'places.gpxImportWaypoints': 'Pontos de caminho', - 'places.gpxImportRoutes': 'Rotas', - 'places.gpxImportTracks': 'Trilhas (com geometria de percurso)', - 'places.gpxImportNoneSelected': 'Selecione pelo menos um tipo para importar.', - 'places.kmlImportTypes': 'O que deseja importar?', - 'places.kmlImportPoints': 'Pontos (Placemarks)', - 'places.kmlImportPaths': 'Caminhos (LineStrings)', - 'places.kmlImportNoneSelected': 'Selecione pelo menos um tipo.', - 'places.selectionCount': '{count} selecionado(s)', - 'places.deleteSelected': 'Excluir seleção', - 'places.kmlKmzImported': '{count} lugares importados de KMZ/KML', - 'places.urlResolved': 'Lugar importado da URL', - 'places.importList': 'Importar lista', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importados: {created} • Ignorados: {skipped}', - 'places.importGoogleList': 'Lista Google', - 'places.importNaverList': 'Lista Naver', - 'places.googleListHint': 'Cole um link compartilhado de uma lista do Google Maps para importar todos os lugares.', - 'places.googleListImported': '{count} lugares importados de "{list}"', - 'places.googleListError': 'Falha ao importar lista do Google Maps', - 'places.naverListHint': 'Cole um link compartilhado de uma lista do Naver Maps para importar todos os lugares.', - 'places.naverListImported': '{count} lugares importados de "{list}"', - 'places.naverListError': 'Falha ao importar lista do Naver Maps', - 'places.viewDetails': 'Ver detalhes', - 'places.assignToDay': 'Adicionar a qual dia?', - 'places.all': 'Todos', - 'places.unplanned': 'Não planejados', - 'places.filterTracks': 'Trilhas', - 'places.search': 'Buscar lugares...', - 'places.allCategories': 'Todas as categorias', - 'places.categoriesSelected': 'categorias', - 'places.clearFilter': 'Limpar filtro', - 'places.count': '{count} lugares', - 'places.countSingular': '1 lugar', - 'places.allPlanned': 'Todos os lugares estão planejados', - 'places.noneFound': 'Nenhum lugar encontrado', - 'places.editPlace': 'Editar lugar', - 'places.formName': 'Nome', - 'places.formNamePlaceholder': 'ex.: Torre Eiffel', - 'places.formDescription': 'Descrição', - 'places.formDescriptionPlaceholder': 'Breve descrição...', - 'places.formAddress': 'Endereço', - 'places.formAddressPlaceholder': 'Rua, cidade, país', - 'places.formLat': 'Latitude (ex.: -23.5505)', - 'places.formLng': 'Longitude (ex.: -46.6333)', - 'places.formCategory': 'Categoria', - 'places.noCategory': 'Sem categoria', - 'places.categoryNamePlaceholder': 'Nome da categoria', - 'places.formTime': 'Horário', - 'places.startTime': 'Início', - 'places.endTime': 'Fim', - 'places.endTimeBeforeStart': 'O horário de fim é antes do início', - 'places.timeCollision': 'Sobreposição de horário com:', - 'places.formWebsite': 'Site', - 'places.formNotes': 'Notas', - 'places.formNotesPlaceholder': 'Notas pessoais...', - 'places.formReservation': 'Reserva', - 'places.reservationNotesPlaceholder': 'Notas da reserva, código de confirmação...', - 'places.mapsSearchPlaceholder': 'Buscar lugares...', - 'places.mapsSearchError': 'Falha na busca de lugares.', - 'places.loadingDetails': 'Carregando detalhes do lugar…', - 'places.osmHint': 'Busca via OpenStreetMap (sem fotos, horários ou avaliações). Adicione uma chave Google nas configurações para detalhes completos.', - 'places.osmActive': 'Busca via OpenStreetMap (sem fotos, avaliações ou horário de funcionamento). Adicione uma chave Google em Configurações para mais dados.', - 'places.categoryCreateError': 'Falha ao criar categoria', - 'places.nameRequired': 'Digite um nome', - 'places.saveError': 'Falha ao salvar', - // Place Inspector - 'inspector.opened': 'Aberto', - 'inspector.closed': 'Fechado', - 'inspector.openingHours': 'Horário de funcionamento', - 'inspector.showHours': 'Mostrar horário de funcionamento', - 'inspector.files': 'Arquivos', - 'inspector.filesCount': '{count} arquivos', - 'inspector.removeFromDay': 'Remover do dia', - 'inspector.remove': 'Remover', - 'inspector.addToDay': 'Adicionar ao dia', - 'inspector.confirmedRes': 'Reserva confirmada', - 'inspector.pendingRes': 'Reserva pendente', - 'inspector.google': 'Abrir no Google Maps', - 'inspector.website': 'Abrir site', - 'inspector.addRes': 'Reserva', - 'inspector.editRes': 'Editar reserva', - 'inspector.participants': 'Participantes', - 'inspector.trackStats': 'Dados da trilha', - - // Reservations - 'reservations.title': 'Reservas', - 'reservations.empty': 'Nenhuma reserva ainda', - 'reservations.emptyHint': 'Adicione reservas de voos, hotéis e mais', - 'reservations.add': 'Adicionar reserva', - 'reservations.addManual': 'Reserva manual', - 'reservations.placeHint': 'Dica: o ideal é criar reservas a partir de um lugar para vinculá-las ao plano do dia.', - 'reservations.confirmed': 'Confirmada', - 'reservations.pending': 'Pendente', - 'reservations.summary': '{confirmed} confirmada(s), {pending} pendente(s)', - 'reservations.fromPlan': 'Do plano', - 'reservations.showFiles': 'Mostrar arquivos', - 'reservations.editTitle': 'Editar reserva', - 'reservations.status': 'Status', - 'reservations.datetime': 'Data e hora', - 'reservations.startTime': 'Horário de início', - 'reservations.endTime': 'Horário de término', - 'reservations.date': 'Data', - 'reservations.time': 'Hora', - 'reservations.timeAlt': 'Hora (alternativa, ex.: 19:30)', - 'reservations.notes': 'Notas', - 'reservations.notesPlaceholder': 'Notas adicionais...', - 'reservations.meta.airline': 'Companhia aérea', - 'reservations.meta.flightNumber': 'Nº do voo', - 'reservations.meta.from': 'De', - 'reservations.meta.to': 'Para', - 'reservations.needsReview': 'Verificar', - 'reservations.needsReviewHint': 'Aeroporto não pôde ser identificado automaticamente — confirme o local.', - 'reservations.searchLocation': 'Buscar estação, porto, endereço...', - 'airport.searchPlaceholder': 'Código ou cidade do aeroporto (ex. FRA)', - 'map.connections': 'Conexões', - 'map.showConnections': 'Mostrar rotas de reservas', - 'map.hideConnections': 'Ocultar rotas de reservas', - 'settings.bookingLabels': 'Rótulos das rotas de reservas', - 'settings.bookingLabelsHint': 'Mostra nomes de estações / aeroportos no mapa. Desativado, apenas o ícone aparece.', - 'reservations.meta.trainNumber': 'Nº do trem', - 'reservations.meta.platform': 'Plataforma', - 'reservations.meta.seat': 'Assento', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in até', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Hospedagem', - 'reservations.meta.pickAccommodation': 'Vincular à hospedagem', - 'reservations.meta.noAccommodation': 'Nenhuma', - 'reservations.meta.hotelPlace': 'Hospedagem', - 'reservations.meta.pickHotel': 'Selecionar hospedagem', - 'reservations.meta.fromDay': 'De', - 'reservations.meta.toDay': 'Até', - 'reservations.meta.selectDay': 'Selecionar dia', - 'reservations.type.flight': 'Voo', - 'reservations.type.hotel': 'Hospedagem', - 'reservations.type.restaurant': 'Restaurante', - 'reservations.type.train': 'Trem', - 'reservations.type.car': 'Carro', - 'reservations.type.cruise': 'Cruzeiro', - 'reservations.type.event': 'Evento', - 'reservations.type.tour': 'Passeio', - 'reservations.type.other': 'Outro', - 'reservations.confirm.delete': 'Tem certeza de que deseja excluir a reserva "{name}"?', - 'reservations.confirm.deleteTitle': 'Excluir reserva?', - 'reservations.confirm.deleteBody': '"{name}" será excluído permanentemente.', - 'reservations.toast.updated': 'Reserva atualizada', - 'reservations.toast.removed': 'Reserva excluída', - 'reservations.toast.fileUploaded': 'Arquivo enviado', - 'reservations.toast.uploadError': 'Falha no envio', - 'reservations.newTitle': 'Nova reserva', - 'reservations.bookingType': 'Tipo de reserva', - 'reservations.titleLabel': 'Título', - 'reservations.titlePlaceholder': 'ex.: LATAM LA800, Hotel Copacabana...', - 'reservations.locationAddress': 'Local / endereço', - 'reservations.locationPlaceholder': 'Endereço, aeroporto, hotel...', - 'reservations.confirmationCode': 'Código da reserva', - 'reservations.confirmationPlaceholder': 'ex.: ABC12345', - 'reservations.day': 'Dia', - 'reservations.noDay': 'Sem dia', - 'reservations.place': 'Lugar', - 'reservations.noPlace': 'Sem lugar', - 'reservations.pendingSave': 'será salvo…', - 'reservations.uploading': 'Enviando...', - 'reservations.attachFile': 'Anexar arquivo', - 'reservations.linkExisting': 'Vincular arquivo existente', - 'reservations.toast.saveError': 'Falha ao salvar', - 'reservations.toast.updateError': 'Falha ao atualizar', - 'reservations.toast.deleteError': 'Falha ao excluir', - 'reservations.confirm.remove': 'Remover a reserva "{name}"?', - 'reservations.linkAssignment': 'Vincular à atribuição do dia', - 'reservations.pickAssignment': 'Selecione uma atribuição do seu plano...', - 'reservations.noAssignment': 'Sem vínculo (avulsa)', - 'reservations.price': 'Preço', - 'reservations.budgetCategory': 'Categoria de orçamento', - 'reservations.budgetCategoryPlaceholder': 'ex. Transporte, Acomodação', - 'reservations.budgetCategoryAuto': 'Automático (pelo tipo de reserva)', - 'reservations.budgetHint': 'Uma entrada de orçamento será criada automaticamente ao salvar.', - 'reservations.departureDate': 'Partida', - 'reservations.arrivalDate': 'Chegada', - 'reservations.departureTime': 'Hora partida', - 'reservations.arrivalTime': 'Hora chegada', - 'reservations.pickupDate': 'Retirada', - 'reservations.returnDate': 'Devolução', - 'reservations.pickupTime': 'Hora retirada', - 'reservations.returnTime': 'Hora devolução', - 'reservations.endDate': 'Data final', - 'reservations.meta.departureTimezone': 'TZ partida', - 'reservations.meta.arrivalTimezone': 'TZ chegada', - 'reservations.span.departure': 'Partida', - 'reservations.span.arrival': 'Chegada', - 'reservations.span.inTransit': 'Em trânsito', - 'reservations.span.pickup': 'Retirada', - 'reservations.span.return': 'Devolução', - 'reservations.span.active': 'Ativo', - 'reservations.span.start': 'Início', - 'reservations.span.end': 'Fim', - 'reservations.span.ongoing': 'Em andamento', - 'reservations.validation.endBeforeStart': 'A data/hora final deve ser posterior à data/hora inicial', - 'reservations.addBooking': 'Adicionar reserva', - - // Budget - 'budget.title': 'Orçamento', - 'budget.exportCsv': 'Exportar CSV', - 'budget.emptyTitle': 'Nenhum orçamento criado ainda', - 'budget.emptyText': 'Crie categorias e lançamentos para planejar o orçamento da viagem', - 'budget.emptyPlaceholder': 'Nome da categoria...', - 'budget.createCategory': 'Criar categoria', - 'budget.category': 'Categoria', - 'budget.categoryName': 'Nome da categoria', - 'budget.table.name': 'Nome', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Pessoas', - 'budget.table.days': 'Dias', - 'budget.table.perPerson': 'Por pessoa', - 'budget.table.perDay': 'Por dia', - 'budget.table.perPersonDay': 'P. p. / dia', - 'budget.table.note': 'Obs.', - 'budget.table.date': 'Data', - 'budget.newEntry': 'Novo lançamento', - 'budget.defaultEntry': 'Novo lançamento', - 'budget.defaultCategory': 'Nova categoria', - 'budget.total': 'Total', - 'budget.totalBudget': 'Orçamento total', - 'budget.byCategory': 'Por categoria', - 'budget.editTooltip': 'Clique para editar', - 'budget.linkedToReservation': 'Vinculado a uma reserva — edite o nome por lá', - 'budget.confirm.deleteCategory': 'Excluir a categoria "{name}" com {count} lançamento(s)?', - 'budget.deleteCategory': 'Excluir categoria', - 'budget.perPerson': 'Por pessoa', - 'budget.paid': 'Pago', - 'budget.open': 'Em aberto', - 'budget.noMembers': 'Nenhum membro atribuído', - 'budget.settlement': 'Acerto', - 'budget.settlementInfo': 'Clique no avatar de um membro em um item do orçamento para marcá-lo em verde — significa que ele pagou. O acerto mostra quem deve quanto a quem.', - 'budget.netBalances': 'Saldos líquidos', - - // Files - 'files.title': 'Arquivos', - 'files.pageTitle': 'Arquivos e documentos', - 'files.subtitle': '{count} arquivos para {trip}', - 'files.download': 'Baixar', - 'files.openError': 'Não foi possível abrir o arquivo', - 'files.downloadPdf': 'Baixar PDF', - 'files.count': '{count} arquivos', - 'files.countSingular': '1 arquivo', - 'files.uploaded': '{count} enviado(s)', - 'files.uploadError': 'Falha no envio', - 'files.dropzone': 'Solte os arquivos aqui', - 'files.dropzoneHint': 'ou clique para escolher', - 'files.allowedTypes': 'Imagens, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Máx. 50 MB', - 'files.uploading': 'Enviando...', - 'files.filterAll': 'Todos', - 'files.filterPdf': 'PDFs', - 'files.filterImages': 'Imagens', - 'files.filterDocs': 'Documentos', - 'files.filterCollab': 'Notas Colab', - 'files.sourceCollab': 'Das notas Colab', - 'files.empty': 'Nenhum arquivo ainda', - 'files.emptyHint': 'Envie arquivos para anexá-los à viagem', - 'files.openTab': 'Abrir em nova aba', - 'files.confirm.delete': 'Excluir este arquivo?', - 'files.toast.deleted': 'Arquivo excluído', - 'files.toast.deleteError': 'Falha ao excluir arquivo', - 'files.sourcePlan': 'Plano do dia', - 'files.sourceBooking': 'Reserva', - 'files.sourceTransport': 'Transporte', - 'files.attach': 'Anexar', - 'files.pasteHint': 'Você também pode colar imagens da área de transferência (Ctrl+V)', - 'files.trash': 'Lixeira', - 'files.trashEmpty': 'A lixeira está vazia', - 'files.emptyTrash': 'Esvaziar lixeira', - 'files.restore': 'Restaurar', - 'files.star': 'Favoritar', - 'files.unstar': 'Remover favorito', - 'files.assign': 'Atribuir', - 'files.assignTitle': 'Atribuir arquivo', - 'files.assignPlace': 'Lugar', - 'files.assignBooking': 'Reserva', - 'files.assignTransport': 'Transporte', - 'files.unassigned': 'Não atribuído', - 'files.unlink': 'Remover vínculo', - 'files.toast.trashed': 'Movido para a lixeira', - 'files.toast.restored': 'Arquivo restaurado', - 'files.toast.trashEmptied': 'Lixeira esvaziada', - 'files.toast.assigned': 'Arquivo atribuído', - 'files.toast.assignError': 'Falha na atribuição', - 'files.toast.restoreError': 'Falha ao restaurar', - 'files.confirm.permanentDelete': 'Excluir permanentemente este arquivo? Não é possível desfazer.', - 'files.confirm.emptyTrash': 'Excluir permanentemente todos os arquivos na lixeira? Não é possível desfazer.', - 'files.noteLabel': 'Nota', - 'files.notePlaceholder': 'Adicione uma nota...', - - // Packing - 'packing.title': 'Lista de mala', - 'packing.empty': 'A lista de mala está vazia', - 'packing.import': 'Importar', - 'packing.importTitle': 'Importar lista de bagagem', - 'packing.importHint': 'Um item por linha. Formato: Categoria, Nome, Peso (g), Bolsa, checked/unchecked (opcional)', - 'packing.importPlaceholder': 'Higiene, Escova de dentes\nRoupas, Camisetas, 200\nDocumentos, Passaporte, , Mala de mão\nEletrônicos, Carregador, 50, Mala, checked', - 'packing.importCsv': 'Carregar CSV/TXT', - 'packing.importAction': 'Importar {count}', - 'packing.importSuccess': '{count} itens importados', - 'packing.importError': 'Falha na importação', - 'packing.importEmpty': 'Nenhum item para importar', - 'packing.progress': '{packed} de {total} na mala ({percent}%)', - 'packing.clearChecked': 'Remover {count} marcado(s)', - 'packing.clearCheckedShort': 'Remover {count}', - 'packing.suggestions': 'Sugestões', - 'packing.suggestionsTitle': 'Adicionar sugestões', - 'packing.allSuggested': 'Todas as sugestões adicionadas', - 'packing.allPacked': 'Tudo na mala!', - 'packing.addPlaceholder': 'Adicionar item...', - 'packing.categoryPlaceholder': 'Categoria...', - 'packing.saveAsTemplate': 'Salvar como modelo', - 'packing.templateName': 'Nome do modelo', - 'packing.templateSaved': 'Lista de bagagem salva como modelo', - 'packing.filterAll': 'Todos', - 'packing.filterOpen': 'Abertos', - 'packing.filterDone': 'Prontos', - 'packing.emptyTitle': 'A lista de mala está vazia', - 'packing.emptyHint': 'Adicione itens ou use as sugestões', - 'packing.emptyFiltered': 'Nenhum item corresponde ao filtro', - 'packing.menuRename': 'Renomear', - 'packing.menuCheckAll': 'Marcar todos', - 'packing.menuUncheckAll': 'Desmarcar todos', - 'packing.menuDeleteCat': 'Excluir categoria', - 'packing.noMembers': 'Nenhum membro na viagem', - 'packing.addItem': 'Adicionar item', - 'packing.addItemPlaceholder': 'Nome do item...', - 'packing.addCategory': 'Adicionar categoria', - 'packing.newCategoryPlaceholder': 'Nome da categoria (ex.: Roupas)', - 'packing.applyTemplate': 'Aplicar modelo', - 'packing.template': 'Modelo', - 'packing.templateApplied': '{count} itens adicionados do modelo', - 'packing.templateError': 'Falha ao aplicar modelo', - 'packing.bags': 'Malas', - 'packing.noBag': 'Sem mala', - 'packing.totalWeight': 'Peso total', - 'packing.bagName': 'Nome da mala...', - 'packing.addBag': 'Adicionar mala', - 'packing.changeCategory': 'Alterar categoria', - 'packing.confirm.clearChecked': 'Remover {count} item(ns) marcado(s)?', - 'packing.confirm.deleteCat': 'Excluir a categoria "{name}" com {count} item(ns)?', - 'packing.defaultCategory': 'Outros', - 'packing.toast.saveError': 'Falha ao salvar', - 'packing.toast.deleteError': 'Falha ao excluir', - 'packing.toast.renameError': 'Falha ao renomear', - 'packing.toast.addError': 'Falha ao adicionar', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Passaporte', category: 'Documentos' }, - { name: 'Documento de identidade', category: 'Documentos' }, - { name: 'Seguro viagem', category: 'Documentos' }, - { name: 'Passagens aéreas', category: 'Documentos' }, - { name: 'Cartão de crédito', category: 'Finanças' }, - { name: 'Dinheiro', category: 'Finanças' }, - { name: 'Visto', category: 'Documentos' }, - { name: 'Camisetas', category: 'Roupas' }, - { name: 'Calças', category: 'Roupas' }, - { name: 'Roupa íntima', category: 'Roupas' }, - { name: 'Meias', category: 'Roupas' }, - { name: 'Jaqueta', category: 'Roupas' }, - { name: 'Pijama', category: 'Roupas' }, - { name: 'Traje de banho', category: 'Roupas' }, - { name: 'Capa de chuva', category: 'Roupas' }, - { name: 'Sapatos confortáveis', category: 'Roupas' }, - { name: 'Escova de dentes', category: 'Higiene' }, - { name: 'Creme dental', category: 'Higiene' }, - { name: 'Shampoo', category: 'Higiene' }, - { name: 'Desodorante', category: 'Higiene' }, - { name: 'Protetor solar', category: 'Higiene' }, - { name: 'Aparelho de barbear', category: 'Higiene' }, - { name: 'Carregador', category: 'Eletrônicos' }, - { name: 'Power bank', category: 'Eletrônicos' }, - { name: 'Fones de ouvido', category: 'Eletrônicos' }, - { name: 'Adaptador de viagem', category: 'Eletrônicos' }, - { name: 'Câmera', category: 'Eletrônicos' }, - { name: 'Medicamento para dor', category: 'Saúde' }, - { name: 'Curativos', category: 'Saúde' }, - { name: 'Desinfetante', category: 'Saúde' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Compartilhar viagem', - 'members.inviteUser': 'Convidar usuário', - 'members.selectUser': 'Selecionar usuário…', - 'members.invite': 'Convidar', - 'members.allHaveAccess': 'Todos os usuários já têm acesso.', - 'members.access': 'Acesso', - 'members.person': 'pessoa', - 'members.persons': 'pessoas', - 'members.you': 'você', - 'members.owner': 'Proprietário', - 'members.leaveTrip': 'Sair da viagem', - 'members.removeAccess': 'Remover acesso', - 'members.confirmLeave': 'Sair da viagem? Você perderá o acesso.', - 'members.confirmRemove': 'Remover o acesso deste usuário?', - 'members.loadError': 'Falha ao carregar membros', - 'members.added': 'adicionado', - 'members.addError': 'Falha ao adicionar', - 'members.removed': 'Membro removido', - 'members.removeError': 'Falha ao remover', - - // Categories (Admin) - 'categories.title': 'Categorias', - 'categories.subtitle': 'Gerenciar categorias de lugares', - 'categories.new': 'Nova categoria', - 'categories.empty': 'Nenhuma categoria ainda', - 'categories.namePlaceholder': 'Nome da categoria', - 'categories.icon': 'Ícone', - 'categories.color': 'Cor', - 'categories.customColor': 'Escolher cor personalizada', - 'categories.preview': 'Pré-visualização', - 'categories.defaultName': 'Categoria', - 'categories.update': 'Atualizar', - 'categories.create': 'Criar', - 'categories.confirm.delete': 'Excluir categoria? Os lugares desta categoria não serão excluídos.', - 'categories.toast.loadError': 'Falha ao carregar categorias', - 'categories.toast.nameRequired': 'Digite um nome', - 'categories.toast.updated': 'Categoria atualizada', - 'categories.toast.created': 'Categoria criada', - 'categories.toast.saveError': 'Falha ao salvar', - 'categories.toast.deleted': 'Categoria excluída', - 'categories.toast.deleteError': 'Falha ao excluir', - - // Backup (Admin) - 'backup.title': 'Backup de dados', - 'backup.subtitle': 'Banco de dados e todos os arquivos enviados', - 'backup.refresh': 'Atualizar', - 'backup.upload': 'Enviar backup', - 'backup.uploading': 'Enviando…', - 'backup.create': 'Criar backup', - 'backup.creating': 'Criando…', - 'backup.empty': 'Nenhum backup ainda', - 'backup.createFirst': 'Criar primeiro backup', - 'backup.download': 'Baixar', - 'backup.restore': 'Restaurar', - 'backup.confirm.restore': 'Restaurar o backup "{name}"?\n\nTodos os dados atuais serão substituídos pelo backup.', - 'backup.confirm.uploadRestore': 'Enviar e restaurar o arquivo "{name}"?\n\nTodos os dados atuais serão sobrescritos.', - 'backup.confirm.delete': 'Excluir o backup "{name}"?', - 'backup.toast.loadError': 'Falha ao carregar backups', - 'backup.toast.created': 'Backup criado com sucesso', - 'backup.toast.createError': 'Falha ao criar backup', - 'backup.toast.restored': 'Backup restaurado. A página será recarregada…', - 'backup.toast.restoreError': 'Falha ao restaurar', - 'backup.toast.uploadError': 'Falha no envio', - 'backup.toast.deleted': 'Backup excluído', - 'backup.toast.deleteError': 'Falha ao excluir', - 'backup.toast.downloadError': 'Falha no download', - 'backup.toast.settingsSaved': 'Configurações de backup automático salvas', - 'backup.toast.settingsError': 'Falha ao salvar configurações', - 'backup.auto.title': 'Backup automático', - 'backup.auto.subtitle': 'Backup automático em agenda', - 'backup.auto.enable': 'Ativar backup automático', - 'backup.auto.enableHint': 'Backups serão criados automaticamente conforme a agenda escolhida', - 'backup.auto.interval': 'Intervalo', - 'backup.auto.hour': 'Executar no horário', - 'backup.auto.hourHint': 'Horário local do servidor (formato {format})', - 'backup.auto.dayOfWeek': 'Dia da semana', - 'backup.auto.dayOfMonth': 'Dia do mês', - 'backup.auto.dayOfMonthHint': 'Limitado a 1–28 para compatibilidade com todos os meses', - 'backup.auto.scheduleSummary': 'Agenda', - 'backup.auto.summaryDaily': 'Todos os dias às {hour}:00', - 'backup.auto.summaryWeekly': 'Toda {day} às {hour}:00', - 'backup.auto.summaryMonthly': 'Dia {day} de cada mês às {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'O backup automático é configurado via variáveis de ambiente Docker. Para alterar essas configurações, atualize o docker-compose.yml e reinicie o contêiner.', - 'backup.auto.copyEnv': 'Copiar variáveis de ambiente Docker', - 'backup.auto.envCopied': 'Variáveis de ambiente Docker copiadas para a área de transferência', - 'backup.auto.keepLabel': 'Excluir backups antigos após', - 'backup.dow.sunday': 'Dom', - 'backup.dow.monday': 'Seg', - 'backup.dow.tuesday': 'Ter', - 'backup.dow.wednesday': 'Qua', - 'backup.dow.thursday': 'Qui', - 'backup.dow.friday': 'Sex', - 'backup.dow.saturday': 'Sáb', - 'backup.interval.hourly': 'A cada hora', - 'backup.interval.daily': 'Diário', - 'backup.interval.weekly': 'Semanal', - 'backup.interval.monthly': 'Mensal', - 'backup.keep.1day': '1 dia', - 'backup.keep.3days': '3 dias', - 'backup.keep.7days': '7 dias', - 'backup.keep.14days': '14 dias', - 'backup.keep.30days': '30 dias', - 'backup.keep.forever': 'Manter para sempre', - - // Photos - 'photos.title': 'Fotos', - 'photos.subtitle': '{count} fotos para {trip}', - 'photos.dropHere': 'Arraste fotos aqui...', - 'photos.dropHereActive': 'Arraste fotos aqui', - 'photos.captionForAll': 'Legenda (para todos)', - 'photos.captionPlaceholder': 'Legenda opcional...', - 'photos.addCaption': 'Adicionar legenda...', - 'photos.allDays': 'Todos os dias', - 'photos.noPhotos': 'Nenhuma foto ainda', - 'photos.uploadHint': 'Envie suas fotos de viagem', - 'photos.clickToSelect': 'ou clique para selecionar', - 'photos.linkPlace': 'Vincular lugar', - 'photos.noPlace': 'Sem lugar', - 'photos.uploadN': 'Enviar {n} foto(s)', - 'photos.linkDay': 'Vincular dia', - 'photos.noDay': 'Nenhum dia', - 'photos.dayLabel': 'Dia {number}', - 'photos.photoSelected': 'Foto selecionada', - 'photos.photosSelected': 'Fotos selecionadas', - 'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · até 30 fotos', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Restaurar backup?', - 'backup.restoreWarning': 'Todos os dados atuais (viagens, lugares, usuários, envios) serão permanentemente substituídos pelo backup. Esta ação não pode ser desfeita.', - 'backup.restoreTip': 'Dica: crie um backup do estado atual antes de restaurar.', - 'backup.restoreConfirm': 'Sim, restaurar', - - // PDF - 'pdf.travelPlan': 'Plano de viagem', - 'pdf.planned': 'Planejado', - 'pdf.costLabel': 'Custo (EUR)', - 'pdf.preview': 'Pré-visualização do PDF', - 'pdf.saveAsPdf': 'Salvar como PDF', - - // Planner - 'planner.places': 'Lugares', - 'planner.bookings': 'Reservas', - 'planner.packingList': 'Lista de mala', - 'planner.documents': 'Documentos', - 'planner.dayPlan': 'Plano do dia', - 'planner.reservations': 'Reservas', - 'planner.minTwoPlaces': 'São necessários pelo menos 2 lugares com coordenadas', - 'planner.noGeoPlaces': 'Nenhum lugar com coordenadas disponível', - 'planner.routeCalculated': 'Rota calculada', - 'planner.routeCalcFailed': 'Não foi possível calcular a rota', - 'planner.routeError': 'Erro ao calcular a rota', - 'planner.icsExportFailed': 'Falha ao exportar ICS', - 'planner.routeOptimized': 'Rota otimizada', - 'planner.reservationUpdated': 'Reserva atualizada', - 'planner.reservationAdded': 'Reserva adicionada', - 'planner.confirmDeleteReservation': 'Excluir reserva?', - 'planner.reservationDeleted': 'Reserva excluída', - 'planner.days': 'Dias', - 'planner.allPlaces': 'Todos os lugares', - 'planner.totalPlaces': '{n} lugares no total', - 'planner.noDaysPlanned': 'Nenhum dia planejado ainda', - 'planner.editTrip': 'Editar viagem \u2192', - 'planner.placeOne': '1 lugar', - 'planner.placeN': '{n} lugares', - 'planner.addNote': 'Adicionar nota', - 'planner.noEntries': 'Nenhuma entrada neste dia', - 'planner.addPlace': 'Adicionar lugar/atividade', - 'planner.addPlaceShort': '+ Adicionar lugar/atividade', - 'planner.resPending': 'Reserva pendente · ', - 'planner.resConfirmed': 'Reserva confirmada · ', - 'planner.notePlaceholder': 'Nota\u2026', - 'planner.noteTimePlaceholder': 'Horário (opcional)', - 'planner.noteExamplePlaceholder': 'ex.: metrô às 14:30 da estação central, barco do cais 7, pausa para almoço\u2026', - 'planner.totalCost': 'Custo total', - 'planner.searchPlaces': 'Buscar lugares\u2026', - 'planner.allCategories': 'Todas as categorias', - 'planner.noPlacesFound': 'Nenhum lugar encontrado', - 'planner.addFirstPlace': 'Adicionar primeiro lugar', - 'planner.noReservations': 'Nenhuma reserva', - 'planner.addFirstReservation': 'Adicionar primeira reserva', - 'planner.new': 'Novo', - 'planner.addToDay': '+ Dia', - 'planner.calculating': 'Calculando\u2026', - 'planner.route': 'Rota', - 'planner.optimize': 'Otimizar', - 'planner.openGoogleMaps': 'Abrir no Google Maps', - 'planner.selectDayHint': 'Selecione um dia na lista à esquerda para ver o plano do dia', - 'planner.noPlacesForDay': 'Nenhum lugar neste dia ainda', - 'planner.addPlacesLink': 'Adicionar lugares \u2192', - 'planner.minTotal': 'mín. total', - 'planner.noReservation': 'Sem reserva', - 'planner.removeFromDay': 'Remover do dia', - 'planner.addToThisDay': 'Adicionar ao dia', - 'planner.overview': 'Visão geral', - 'planner.noDays': 'Nenhum dia ainda', - 'planner.editTripToAddDays': 'Edite a viagem para adicionar dias', - 'planner.dayCount': '{n} dias', - 'planner.clickToUnlock': 'Clique para desbloquear', - 'planner.keepPosition': 'Manter posição durante a otimização da rota', - 'planner.dayDetails': 'Detalhes do dia', - 'planner.dayN': 'Dia {n}', - - // Dashboard Stats - 'stats.countries': 'Países', - 'stats.cities': 'Cidades', - 'stats.trips': 'Viagens', - 'stats.places': 'Lugares', - 'stats.worldProgress': 'Progresso no mundo', - 'stats.visited': 'visitados', - 'stats.remaining': 'restantes', - 'stats.visitedCountries': 'Países visitados', - - // Day Detail Panel - 'day.precipProb': 'Probabilidade de chuva', - 'day.precipitation': 'Precipitação', - 'day.wind': 'Vento', - 'day.sunrise': 'Nascer do sol', - 'day.sunset': 'Pôr do sol', - 'day.hourlyForecast': 'Previsão por hora', - 'day.climateHint': 'Médias históricas — previsão real disponível até 16 dias desta data.', - 'day.noWeather': 'Sem dados meteorológicos. Adicione um lugar com coordenadas.', - 'day.overview': 'Resumo do dia', - 'day.accommodation': 'Hospedagem', - 'day.addAccommodation': 'Adicionar hospedagem', - 'day.hotelDayRange': 'Aplicar aos dias', - 'day.noPlacesForHotel': 'Adicione lugares à viagem primeiro', - 'day.allDays': 'Todos', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Até', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Confirmação', - 'day.editAccommodation': 'Editar hospedagem', - 'day.reservations': 'Reservas', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Notas', - 'collab.tabs.polls': 'Enquetes', - 'collab.whatsNext.title': 'Próximos passos', - 'collab.whatsNext.today': 'Hoje', - 'collab.whatsNext.tomorrow': 'Amanhã', - 'collab.whatsNext.empty': 'Nenhuma atividade próxima', - 'collab.whatsNext.until': 'até', - 'collab.whatsNext.emptyHint': 'Atividades com horário aparecerão aqui', - 'collab.chat.send': 'Enviar', - 'collab.chat.placeholder': 'Digite uma mensagem...', - 'collab.chat.empty': 'Inicie a conversa', - 'collab.chat.emptyHint': 'As mensagens são compartilhadas com todos os membros da viagem', - 'collab.chat.emptyDesc': 'Compartilhe ideias, planos e atualizações com o grupo', - 'collab.chat.today': 'Hoje', - 'collab.chat.yesterday': 'Ontem', - 'collab.chat.deletedMessage': 'apagou uma mensagem', - 'collab.chat.reply': 'Responder', - 'collab.chat.loadMore': 'Carregar mensagens antigas', - 'collab.chat.justNow': 'agora mesmo', - 'collab.chat.minutesAgo': 'há {n} min', - 'collab.chat.hoursAgo': 'há {n} h', - 'collab.notes.title': 'Notas', - 'collab.notes.new': 'Nova nota', - 'collab.notes.empty': 'Nenhuma nota ainda', - 'collab.notes.emptyHint': 'Comece a registrar ideias e planos', - 'collab.notes.all': 'Todas', - 'collab.notes.titlePlaceholder': 'Título da nota', - 'collab.notes.contentPlaceholder': 'Escreva algo...', - 'collab.notes.categoryPlaceholder': 'Categoria', - 'collab.notes.newCategory': 'Nova categoria...', - 'collab.notes.category': 'Categoria', - 'collab.notes.noCategory': 'Sem categoria', - 'collab.notes.color': 'Cor', - 'collab.notes.save': 'Salvar', - 'collab.notes.cancel': 'Cancelar', - 'collab.notes.edit': 'Editar', - 'collab.notes.delete': 'Excluir', - 'collab.notes.pin': 'Fixar', - 'collab.notes.unpin': 'Desafixar', - 'collab.notes.daysAgo': 'há {n} d', - 'collab.notes.categorySettings': 'Gerenciar categorias', - 'collab.notes.create': 'Criar', - 'collab.notes.website': 'Site', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Anexar arquivos', - 'collab.notes.noCategoriesYet': 'Nenhuma categoria ainda', - 'collab.notes.emptyDesc': 'Crie uma nota para começar', - 'collab.polls.title': 'Enquetes', - 'collab.polls.new': 'Nova enquete', - 'collab.polls.empty': 'Nenhuma enquete ainda', - 'collab.polls.emptyHint': 'Pergunte ao grupo e votem juntos', - 'collab.polls.question': 'Pergunta', - 'collab.polls.questionPlaceholder': 'O que vamos fazer?', - 'collab.polls.addOption': '+ Adicionar opção', - 'collab.polls.optionPlaceholder': 'Opção {n}', - 'collab.polls.create': 'Criar enquete', - 'collab.polls.close': 'Encerrar', - 'collab.polls.closed': 'Encerrada', - 'collab.polls.votes': '{n} votos', - 'collab.polls.vote': '{n} voto', - 'collab.polls.multipleChoice': 'Múltipla escolha', - 'collab.polls.multiChoice': 'Múltipla escolha', - 'collab.polls.deadline': 'Prazo', - 'collab.polls.option': 'Opção', - 'collab.polls.options': 'Opções', - 'collab.polls.delete': 'Excluir', - 'collab.polls.closedSection': 'Encerradas', - - // Memories (Immich Photos) - 'memories.title': 'Fotos', - 'memories.notConnected': 'Immich não conectado', - 'memories.notConnectedHint': 'Conecte sua instância Immich nas Configurações para ver suas fotos de viagem aqui.', - 'memories.notConnectedMultipleHint': 'Conecte um destes provedores de fotos: {provider_names} nas Configurações para poder adicionar fotos a esta viagem.', - 'memories.noDates': 'Adicione datas à sua viagem para carregar fotos.', - 'memories.noPhotos': 'Nenhuma foto encontrada', - 'memories.noPhotosHint': 'Nenhuma foto encontrada no Immich para o período desta viagem.', - 'memories.photosFound': 'fotos', - 'memories.fromOthers': 'de outros', - 'memories.sharePhotos': 'Compartilhar fotos', - 'memories.sharing': 'Compartilhando', - 'memories.reviewTitle': 'Revise suas fotos', - 'memories.reviewHint': 'Clique nas fotos para excluí-las do compartilhamento.', - 'memories.shareCount': 'Compartilhar {count} fotos', - 'memories.providerUrl': 'URL do servidor', - 'memories.providerApiKey': 'Chave de API', - 'memories.providerUsername': 'Nome de usuário', - 'memories.providerPassword': 'Senha', - 'memories.providerOTP': 'Código MFA (se habilitado)', - 'memories.skipSSLVerification': 'Pular verificação de certificado SSL', - 'memories.immichAutoUpload': 'Espelhar fotos da jornada no Immich ao enviar', - 'memories.providerUrlHintSynology': 'Inclua o caminho do aplicativo Photos na URL, ex. https://nas:5001/photo', - 'memories.testConnection': 'Testar conexão', - 'memories.testShort': 'Testar', - 'memories.testFirst': 'Teste a conexão primeiro', - 'memories.connected': 'Conectado', - 'memories.disconnected': 'Não conectado', - 'memories.connectionSuccess': 'Conectado ao Immich', - 'memories.connectionError': 'Não foi possível conectar ao Immich', - 'memories.saved': 'Configurações do {provider_name} salvas', - 'memories.providerDisconnectedBanner': 'Sua conexão com {provider_name} foi perdida. Reconecte nas Configurações para ver as fotos.', - 'memories.saveError': 'Não foi possível salvar as configurações de {provider_name}', - 'memories.addPhotos': 'Adicionar fotos', - 'memories.linkAlbum': 'Vincular álbum', - 'memories.selectAlbum': 'Selecionar álbum do Immich', - 'memories.selectAlbumMultiple': 'Selecionar álbum', - 'memories.noAlbums': 'Nenhum álbum encontrado', - 'memories.syncAlbum': 'Sincronizar álbum', - 'memories.unlinkAlbum': 'Desvincular', - 'memories.photos': 'fotos', - 'memories.selectPhotos': 'Selecionar fotos do Immich', - 'memories.selectPhotosMultiple': 'Selecionar fotos', - 'memories.selectHint': 'Toque nas fotos para selecioná-las.', - 'memories.selected': 'selecionadas', - 'memories.addSelected': 'Adicionar {count} fotos', - 'memories.alreadyAdded': 'Já adicionada', - 'memories.private': 'Privado', - 'memories.stopSharing': 'Parar de compartilhar', - 'memories.oldest': 'Mais antigas', - 'memories.newest': 'Mais recentes', - 'memories.allLocations': 'Todos os locais', - 'memories.tripDates': 'Datas da viagem', - 'memories.allPhotos': 'Todas as fotos', - 'memories.confirmShareTitle': 'Compartilhar com membros da viagem?', - 'memories.confirmShareHint': '{count} fotos serão visíveis para todos os membros desta viagem. Você pode tornar fotos individuais privadas depois.', - 'memories.confirmShareButton': 'Compartilhar fotos', - - // Permissions - 'admin.tabs.permissions': 'Permissões', - 'admin.tabs.mcpTokens': 'Acesso MCP', - 'admin.mcpTokens.title': 'Acesso MCP', - 'admin.mcpTokens.subtitle': 'Gerenciar sessões OAuth e tokens de API de todos os usuários', - 'admin.mcpTokens.sectionTitle': 'Tokens de API', - 'admin.mcpTokens.owner': 'Proprietário', - 'admin.mcpTokens.tokenName': 'Nome do Token', - 'admin.mcpTokens.created': 'Criado', - 'admin.mcpTokens.lastUsed': 'Último uso', - 'admin.mcpTokens.never': 'Nunca', - 'admin.mcpTokens.empty': 'Nenhum token MCP foi criado ainda', - 'admin.mcpTokens.deleteTitle': 'Excluir Token', - 'admin.mcpTokens.deleteMessage': 'Isso revogará o token imediatamente. O usuário perderá o acesso MCP por este token.', - 'admin.mcpTokens.deleteSuccess': 'Token excluído', - 'admin.mcpTokens.deleteError': 'Falha ao excluir token', - 'admin.mcpTokens.loadError': 'Falha ao carregar tokens', - 'admin.oauthSessions.sectionTitle': 'Sessões OAuth', - 'admin.oauthSessions.clientName': 'Cliente', - 'admin.oauthSessions.owner': 'Proprietário', - 'admin.oauthSessions.scopes': 'Permissões', - 'admin.oauthSessions.created': 'Criado', - 'admin.oauthSessions.empty': 'Nenhuma sessão OAuth ativa', - 'admin.oauthSessions.revokeTitle': 'Revogar sessão', - 'admin.oauthSessions.revokeMessage': 'Esta sessão OAuth será revogada imediatamente. O cliente perderá o acesso MCP.', - 'admin.oauthSessions.revokeSuccess': 'Sessão revogada', - 'admin.oauthSessions.revokeError': 'Falha ao revogar sessão', - 'admin.oauthSessions.loadError': 'Falha ao carregar sessões OAuth', - 'perm.title': 'Configurações de Permissões', - 'perm.subtitle': 'Controle quem pode realizar ações no aplicativo', - 'perm.saved': 'Configurações de permissões salvas', - 'perm.resetDefaults': 'Restaurar padrões', - 'perm.customized': 'personalizado', - 'perm.level.admin': 'Apenas administrador', - 'perm.level.tripOwner': 'Dono da viagem', - 'perm.level.tripMember': 'Membros da viagem', - 'perm.level.everybody': 'Todos', - 'perm.cat.trip': 'Gerenciamento de Viagens', - 'perm.cat.members': 'Gerenciamento de Membros', - 'perm.cat.files': 'Arquivos', - 'perm.cat.content': 'Conteúdo e Cronograma', - 'perm.cat.extras': 'Orçamento, Bagagem e Colaboração', - 'perm.action.trip_create': 'Criar viagens', - 'perm.action.trip_edit': 'Editar detalhes da viagem', - 'perm.action.trip_delete': 'Excluir viagens', - 'perm.action.trip_archive': 'Arquivar / desarquivar viagens', - 'perm.action.trip_cover_upload': 'Enviar imagem de capa', - 'perm.action.member_manage': 'Adicionar / remover membros', - 'perm.action.file_upload': 'Enviar arquivos', - 'perm.action.file_edit': 'Editar metadados do arquivo', - 'perm.action.file_delete': 'Excluir arquivos', - 'perm.action.place_edit': 'Adicionar / editar / excluir lugares', - 'perm.action.day_edit': 'Editar dias, notas e atribuições', - 'perm.action.reservation_edit': 'Gerenciar reservas', - 'perm.action.budget_edit': 'Gerenciar orçamento', - 'perm.action.packing_edit': 'Gerenciar listas de bagagem', - 'perm.action.collab_edit': 'Colaboração (notas, enquetes, chat)', - 'perm.action.share_manage': 'Gerenciar links de compartilhamento', - 'perm.actionHint.trip_create': 'Quem pode criar novas viagens', - 'perm.actionHint.trip_edit': 'Quem pode alterar nome, datas, descrição e moeda da viagem', - 'perm.actionHint.trip_delete': 'Quem pode excluir permanentemente uma viagem', - 'perm.actionHint.trip_archive': 'Quem pode arquivar ou desarquivar uma viagem', - 'perm.actionHint.trip_cover_upload': 'Quem pode enviar ou alterar a imagem de capa', - 'perm.actionHint.member_manage': 'Quem pode convidar ou remover membros da viagem', - 'perm.actionHint.file_upload': 'Quem pode enviar arquivos para uma viagem', - 'perm.actionHint.file_edit': 'Quem pode editar descrições e links dos arquivos', - 'perm.actionHint.file_delete': 'Quem pode mover arquivos para a lixeira ou excluí-los permanentemente', - 'perm.actionHint.place_edit': 'Quem pode adicionar, editar ou excluir lugares', - 'perm.actionHint.day_edit': 'Quem pode editar dias, notas dos dias e atribuições de lugares', - 'perm.actionHint.reservation_edit': 'Quem pode criar, editar ou excluir reservas', - 'perm.actionHint.budget_edit': 'Quem pode criar, editar ou excluir itens do orçamento', - 'perm.actionHint.packing_edit': 'Quem pode gerenciar itens de bagagem e malas', - 'perm.actionHint.collab_edit': 'Quem pode criar notas, enquetes e enviar mensagens', - 'perm.actionHint.share_manage': 'Quem pode criar ou excluir links de compartilhamento públicos', - // Undo - 'undo.button': 'Desfazer', - 'undo.tooltip': 'Desfazer: {action}', - 'undo.assignPlace': 'Local atribuído ao dia', - 'undo.removeAssignment': 'Local removido do dia', - 'undo.reorder': 'Locais reordenados', - 'undo.optimize': 'Rota otimizada', - 'undo.deletePlace': 'Local excluído', - 'undo.deletePlaces': 'Lugares excluídos', - 'undo.moveDay': 'Local movido para outro dia', - 'undo.lock': 'Bloqueio do local alternado', - 'undo.importGpx': 'Importação de GPX', - 'undo.importKeyholeMarkup': 'Importação de KMZ/KML', - 'undo.importGoogleList': 'Importação do Google Maps', - 'undo.importNaverList': 'Importação do Naver Maps', - - // Notifications - 'notifications.title': 'Notificações', - 'notifications.markAllRead': 'Marcar tudo como lido', - 'notifications.deleteAll': 'Excluir tudo', - 'notifications.showAll': 'Ver todas as notificações', - 'notifications.empty': 'Sem notificações', - 'notifications.emptyDescription': 'Você está em dia!', - 'notifications.all': 'Todas', - 'notifications.unreadOnly': 'Não lidas', - 'notifications.markRead': 'Marcar como lido', - 'notifications.markUnread': 'Marcar como não lido', - 'notifications.delete': 'Excluir', - 'notifications.system': 'Sistema', - 'notifications.synologySessionCleared.title': 'Synology Photos desconectado', - 'notifications.synologySessionCleared.text': 'Seu servidor ou conta foi alterado — vá para Configurações para testar sua conexão novamente.', - 'memories.error.loadAlbums': 'Falha ao carregar álbuns', - 'memories.error.linkAlbum': 'Falha ao vincular álbum', - 'memories.error.unlinkAlbum': 'Falha ao desvincular álbum', - 'memories.error.syncAlbum': 'Falha ao sincronizar álbum', - 'memories.error.loadPhotos': 'Falha ao carregar fotos', - 'memories.error.addPhotos': 'Falha ao adicionar fotos', - 'memories.error.removePhoto': 'Falha ao remover foto', - 'memories.error.toggleSharing': 'Falha ao atualizar compartilhamento', - 'undo.addPlace': 'Local adicionado', - 'undo.done': 'Desfeito: {action}', - 'notifications.test.title': 'Notificação de teste de {actor}', - 'notifications.test.text': 'Esta é uma notificação de teste simples.', - 'notifications.test.booleanTitle': '{actor} solicita sua aprovação', - 'notifications.test.booleanText': 'Notificação de teste booleana.', - 'notifications.test.accept': 'Aprovar', - 'notifications.test.decline': 'Recusar', - 'notifications.test.navigateTitle': 'Confira algo', - 'notifications.test.navigateText': 'Notificação de teste de navegação.', - 'notifications.test.goThere': 'Ir lá', - 'notifications.test.adminTitle': 'Transmissão do admin', - 'notifications.test.adminText': '{actor} enviou uma notificação de teste para todos os admins.', - 'notifications.test.tripTitle': '{actor} postou na sua viagem', - 'notifications.test.tripText': 'Notificação de teste para a viagem "{trip}".', - - // Todo - 'todo.subtab.packing': 'Lista de bagagem', - 'todo.subtab.todo': 'A fazer', - 'todo.completed': 'concluído(s)', - 'todo.filter.all': 'Todos', - 'todo.filter.open': 'Aberto', - 'todo.filter.done': 'Concluído', - 'todo.uncategorized': 'Sem categoria', - 'todo.namePlaceholder': 'Nome da tarefa', - 'todo.descriptionPlaceholder': 'Descrição (opcional)', - 'todo.unassigned': 'Não atribuído', - 'todo.noCategory': 'Sem categoria', - 'todo.hasDescription': 'Com descrição', - 'todo.addItem': 'Nova tarefa', - 'todo.sidebar.sortBy': 'Ordenar por', - 'todo.priority': 'Prioridade', - 'todo.newCategoryLabel': 'nova', - 'budget.categoriesLabel': 'categorias', - 'todo.newCategory': 'Nome da categoria', - 'todo.addCategory': 'Adicionar categoria', - 'todo.newItem': 'Nova tarefa', - 'todo.empty': 'Nenhuma tarefa ainda. Adicione uma tarefa para começar!', - 'todo.filter.my': 'Minhas tarefas', - 'todo.filter.overdue': 'Atrasada', - 'todo.sidebar.tasks': 'Tarefas', - 'todo.sidebar.categories': 'Categorias', - 'todo.detail.title': 'Tarefa', - 'todo.detail.description': 'Descrição', - 'todo.detail.category': 'Categoria', - 'todo.detail.dueDate': 'Data de vencimento', - 'todo.detail.assignedTo': 'Atribuído a', - 'todo.detail.delete': 'Excluir', - 'todo.detail.save': 'Salvar alterações', - 'todo.detail.create': 'Criar tarefa', - 'todo.detail.priority': 'Prioridade', - 'todo.detail.noPriority': 'Nenhuma', - 'todo.sortByPrio': 'Prioridade', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nova versão disponível', - 'settings.notificationPreferences.noChannels': 'Nenhum canal de notificação configurado. Peça a um administrador para configurar notificações por e-mail ou webhook.', - 'settings.webhookUrl.label': 'URL do webhook', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Insira a URL do seu webhook do Discord, Slack ou personalizado para receber notificações.', - 'settings.webhookUrl.saved': 'URL do webhook salva', - 'settings.webhookUrl.test': 'Testar', - 'settings.webhookUrl.testSuccess': 'Webhook de teste enviado com sucesso', - 'settings.webhookUrl.testFailed': 'Falha no webhook de teste', - 'settings.ntfyUrl.topicLabel': 'Tópico Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL do servidor Ntfy (opcional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Insira seu tópico Ntfy para receber notificações push. Deixe o servidor em branco para usar o padrão configurado pelo seu administrador.', - 'settings.ntfyUrl.tokenLabel': 'Token de acesso (opcional)', - 'settings.ntfyUrl.tokenHint': 'Necessário para tópicos protegidos por senha.', - 'settings.ntfyUrl.saved': 'Configurações do Ntfy salvas', - 'settings.ntfyUrl.test': 'Testar', - 'settings.ntfyUrl.testSuccess': 'Notificação de teste do Ntfy enviada com sucesso', - 'settings.ntfyUrl.testFailed': 'Falha na notificação de teste do Ntfy', - 'settings.ntfyUrl.tokenCleared': 'Token de acesso removido', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'As notificações no aplicativo estão sempre ativas e não podem ser desativadas globalmente.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook de admin', - 'admin.notifications.adminWebhookPanel.hint': 'Este webhook é usado exclusivamente para notificações de admin (ex. alertas de versão). É independente dos webhooks de usuários e dispara automaticamente quando uma URL está configurada.', - 'admin.notifications.adminWebhookPanel.saved': 'URL do webhook de admin salva', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de teste enviado com sucesso', - 'admin.notifications.adminWebhookPanel.testFailed': 'Falha no webhook de teste', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'O webhook de admin dispara automaticamente quando uma URL está configurada', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Permite que os usuários configurem seus próprios tópicos ntfy para notificações push. Configure o servidor padrão abaixo para preencher as configurações do usuário.', - 'admin.notifications.testNtfy': 'Enviar Ntfy de teste', - 'admin.notifications.testNtfySuccess': 'Ntfy de teste enviado com sucesso', - 'admin.notifications.testNtfyFailed': 'Falha ao enviar Ntfy de teste', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', - 'admin.notifications.adminNtfyPanel.hint': 'Este tópico Ntfy é usado exclusivamente para notificações de admin (ex. alertas de versão). É independente dos tópicos por usuário e sempre dispara quando configurado.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL do servidor Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Também usado como servidor padrão para notificações ntfy dos usuários. Deixe em branco para usar ntfy.sh. Os usuários podem substituir isso em suas próprias configurações.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Tópico de admin', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acesso (opcional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Token de acesso admin removido', - 'admin.notifications.adminNtfyPanel.saved': 'Configurações de Ntfy de admin salvas', - 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de teste', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de teste enviado com sucesso', - 'admin.notifications.adminNtfyPanel.testFailed': 'Falha ao enviar Ntfy de teste', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'O Ntfy de admin sempre dispara quando um tópico está configurado', - 'admin.notifications.adminNotificationsHint': 'Configure quais canais entregam notificações de admin (ex. alertas de versão). O webhook dispara automaticamente se uma URL de webhook de admin estiver definida.', - 'admin.notifications.tripReminders.title': 'Lembretes de viagem', - 'admin.notifications.tripReminders.hint': 'Envia uma notificação de lembrete antes do início de uma viagem (requer dias de lembrete definidos na viagem).', - 'admin.notifications.tripReminders.enabled': 'Lembretes de viagem ativados', - 'admin.notifications.tripReminders.disabled': 'Lembretes de viagem desativados', - 'admin.tabs.notifications': 'Notificações', - 'notifications.versionAvailable.title': 'Atualização disponível', - 'notifications.versionAvailable.text': 'TREK {version} já está disponível.', - 'notifications.versionAvailable.button': 'Ver detalhes', - 'notif.test.title': '[Teste] Notificação', - 'notif.test.simple.text': 'Esta é uma notificação de teste simples.', - 'notif.test.boolean.text': 'Você aceita esta notificação de teste?', - 'notif.test.navigate.text': 'Clique abaixo para ir ao painel.', - - // Notifications - 'notif.trip_invite.title': 'Convite para viagem', - 'notif.trip_invite.text': '{actor} convidou você para {trip}', - 'notif.booking_change.title': 'Reserva atualizada', - 'notif.booking_change.text': '{actor} atualizou uma reserva em {trip}', - 'notif.trip_reminder.title': 'Lembrete de viagem', - 'notif.trip_reminder.text': 'Sua viagem {trip} está chegando!', - 'notif.todo_due.title': 'Tarefa com vencimento', - 'notif.todo_due.text': '{todo} em {trip} vence em {due}', - 'notif.vacay_invite.title': 'Convite Vacay Fusion', - 'notif.vacay_invite.text': '{actor} convidou você para fundir planos de férias', - 'notif.photos_shared.title': 'Fotos compartilhadas', - 'notif.photos_shared.text': '{actor} compartilhou {count} foto(s) em {trip}', - 'notif.collab_message.title': 'Nova mensagem', - 'notif.collab_message.text': '{actor} enviou uma mensagem em {trip}', - 'notif.packing_tagged.title': 'Atribuição de bagagem', - 'notif.packing_tagged.text': '{actor} atribuiu você a {category} em {trip}', - 'notif.version_available.title': 'Nova versão disponível', - 'notif.version_available.text': 'TREK {version} está disponível', - 'notif.action.view_trip': 'Ver viagem', - 'notif.action.view_collab': 'Ver mensagens', - 'notif.action.view_packing': 'Ver bagagem', - 'notif.action.view_photos': 'Ver fotos', - 'notif.action.view_vacay': 'Ver Vacay', - 'notif.action.view_admin': 'Ir para admin', - 'notif.action.view': 'Ver', - 'notif.action.accept': 'Aceitar', - 'notif.action.decline': 'Recusar', - 'notif.generic.title': 'Notificação', - 'notif.generic.text': 'Você tem uma nova notificação', - 'notif.dev.unknown_event.title': '[DEV] Evento desconhecido', - 'notif.dev.unknown_event.text': 'O tipo de evento "{event}" não está registrado em EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'agora mesmo', - 'common.hoursAgo': 'há {count}h', - 'common.daysAgo': 'há {count}d', - 'memories.saveRouteNotConfigured': 'A rota de salvamento não está configurada para este provedor', - 'memories.testRouteNotConfigured': 'A rota de teste não está configurada para este provedor', - 'memories.fillRequiredFields': 'Por favor preencha todos os campos obrigatórios', - 'journey.search.placeholder': 'Buscar jornadas…', - 'journey.search.noResults': 'Nenhuma jornada corresponde a "{query}"', - 'journey.title': 'Jornada', - 'journey.subtitle': 'Registre suas viagens em tempo real', - 'journey.new': 'Nova jornada', - 'journey.create': 'Criar', - 'journey.titlePlaceholder': 'Para onde você vai?', - 'journey.empty': 'Nenhuma jornada ainda', - 'journey.emptyHint': 'Comece a documentar sua próxima viagem', - 'journey.deleted': 'Jornada excluída', - 'journey.createError': 'Não foi possível criar a jornada', - 'journey.deleteError': 'Não foi possível excluir a jornada', - 'journey.deleteConfirmTitle': 'Excluir', - 'journey.deleteConfirmMessage': 'Excluir "{title}"? Isso não pode ser desfeito.', - 'journey.deleteConfirmGeneric': 'Tem certeza de que deseja excluir isso?', - 'journey.notFound': 'Jornada não encontrada', - 'journey.photos': 'Fotos', - 'journey.timelineEmpty': 'Nenhuma parada ainda', - 'journey.timelineEmptyHint': 'Adicione um check-in ou escreva uma entrada no diário para começar', - 'journey.status.draft': 'Rascunho', - 'journey.status.active': 'Ativa', - 'journey.status.completed': 'Concluída', - 'journey.status.upcoming': 'Próxima', - 'journey.status.archived': 'Arquivado', - 'journey.checkin.add': 'Fazer check-in', - 'journey.checkin.namePlaceholder': 'Nome do local', - 'journey.checkin.notesPlaceholder': 'Notas (opcional)', - 'journey.checkin.save': 'Salvar', - 'journey.checkin.error': 'Não foi possível salvar o check-in', - 'journey.entry.add': 'Diário', - 'journey.entry.edit': 'Editar entrada', - 'journey.entry.titlePlaceholder': 'Título (opcional)', - 'journey.entry.bodyPlaceholder': 'O que aconteceu hoje?', - 'journey.entry.save': 'Salvar', - 'journey.entry.error': 'Não foi possível salvar a entrada', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Falha no envio', - 'journey.share.share': 'Compartilhar', - 'journey.share.public': 'Público', - 'journey.share.linkCopied': 'Link público copiado', - 'journey.share.disabled': 'Compartilhamento público desativado', - 'journey.editor.titlePlaceholder': 'Dê um nome a este momento...', - 'journey.editor.bodyPlaceholder': 'Conte a história deste dia...', - 'journey.editor.placePlaceholder': 'Localização (opcional)', - 'journey.editor.tagsPlaceholder': 'Tags: joia escondida, melhor refeição, preciso voltar...', - 'journey.visibility.private': 'Privado', - 'journey.visibility.shared': 'Compartilhado', - 'journey.visibility.public': 'Público', - 'journey.emptyState.title': 'Sua história começa aqui', - 'journey.emptyState.subtitle': 'Faça check-in em um lugar ou escreva sua primeira entrada no diário', - 'journey.frontpage.subtitle': 'Transforme suas viagens em histórias que você nunca vai esquecer', - 'journey.frontpage.createJourney': 'Criar jornada', - 'journey.frontpage.activeJourney': 'Jornada ativa', - 'journey.frontpage.allJourneys': 'Todas as jornadas', - 'journey.frontpage.journeys': 'jornadas', - 'journey.frontpage.createNew': 'Criar uma nova jornada', - 'journey.frontpage.createNewSub': 'Escolha viagens, escreva histórias, compartilhe suas aventuras', - 'journey.frontpage.live': 'Ao vivo', - 'journey.frontpage.synced': 'Sincronizado', - 'journey.frontpage.continueWriting': 'Continuar escrevendo', - 'journey.frontpage.updated': 'Atualizado {time}', - 'journey.frontpage.suggestionLabel': 'A viagem acabou de terminar', - 'journey.frontpage.suggestionText': 'Transforme {title} em uma jornada', - 'journey.frontpage.dismiss': 'Dispensar', - 'journey.frontpage.journeyName': 'Nome da jornada', - 'journey.frontpage.namePlaceholder': 'ex. Sudeste Asiático 2026', - 'journey.frontpage.selectTrips': 'Selecionar viagens', - 'journey.frontpage.tripsSelected': 'viagens selecionadas', - 'journey.frontpage.trips': 'viagens', - 'journey.frontpage.placesImported': 'lugares serão importados', - 'journey.frontpage.places': 'lugares', - 'journey.detail.backToJourney': 'Voltar à jornada', - 'journey.detail.syncedWithTrips': 'Sincronizado com viagens', - 'journey.detail.addEntry': 'Adicionar entrada', - 'journey.detail.newEntry': 'Nova entrada', - 'journey.detail.editEntry': 'Editar entrada', - 'journey.detail.noEntries': 'Nenhuma entrada ainda', - 'journey.detail.noEntriesHint': 'Adicione uma viagem para começar com entradas preliminares', - 'journey.detail.noPhotos': 'Nenhuma foto ainda', - 'journey.detail.noPhotosHint': 'Envie fotos para as entradas ou explore sua biblioteca do Immich/Synology', - 'journey.detail.journeyStats': 'Estatísticas da jornada', - 'journey.detail.syncedTrips': 'Viagens sincronizadas', - 'journey.detail.noTripsLinked': 'Nenhuma viagem vinculada ainda', - 'journey.detail.contributors': 'Colaboradores', - 'journey.detail.readMore': 'Ler mais', - 'journey.detail.prosCons': 'Prós e contras', - 'journey.detail.photos': 'fotos', - 'journey.detail.day': 'Dia {number}', - 'journey.detail.places': 'lugares', - 'journey.stats.days': 'Dias', - 'journey.stats.cities': 'Cidades', - 'journey.stats.entries': 'Entradas', - 'journey.stats.photos': 'Fotos', - 'journey.stats.places': 'Lugares', - 'journey.skeletons.show': 'Mostrar sugestões', - 'journey.skeletons.hide': 'Ocultar sugestões', - 'journey.verdict.lovedIt': 'Adorei', - 'journey.verdict.couldBeBetter': 'Poderia ser melhor', - 'journey.synced.places': 'lugares', - 'journey.synced.synced': 'sincronizado', - 'journey.editor.discardChangesConfirm': 'Você tem alterações não salvas. Descartá-las?', - 'journey.editor.uploadFailed': 'Falha ao enviar fotos', - 'journey.editor.uploadPhotos': 'Enviar fotos', - 'journey.editor.uploading': 'Enviando...', - 'journey.editor.uploadingProgress': 'Enviando {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} de {total} fotos falharam — salve novamente para tentar', - 'journey.editor.fromGallery': 'Da galeria', - 'journey.editor.allPhotosAdded': 'Todas as fotos já foram adicionadas', - 'journey.editor.writeStory': 'Escreva sua história...', - 'journey.editor.prosCons': 'Prós e contras', - 'journey.editor.pros': 'Prós', - 'journey.editor.cons': 'Contras', - 'journey.editor.proPlaceholder': 'Algo ótimo...', - 'journey.editor.conPlaceholder': 'Não tão bom...', - 'journey.editor.addAnother': 'Adicionar outro', - 'journey.editor.date': 'Data', - 'journey.editor.location': 'Localização', - 'journey.editor.searchLocation': 'Buscar localização...', - 'journey.editor.mood': 'Humor', - 'journey.editor.weather': 'Clima', - 'journey.editor.photoFirst': '1º', - 'journey.editor.makeFirst': 'Tornar 1º', - 'journey.editor.searching': 'Pesquisando...', - 'journey.mood.amazing': 'Incrível', - 'journey.mood.good': 'Bom', - 'journey.mood.neutral': 'Neutro', - 'journey.mood.rough': 'Difícil', - 'journey.weather.sunny': 'Ensolarado', - 'journey.weather.partly': 'Parcialmente nublado', - 'journey.weather.cloudy': 'Nublado', - 'journey.weather.rainy': 'Chuvoso', - 'journey.weather.stormy': 'Tempestuoso', - 'journey.weather.cold': 'Nevando', - 'journey.trips.linkTrip': 'Vincular viagem', - 'journey.trips.searchTrip': 'Buscar viagem', - 'journey.trips.searchPlaceholder': 'Nome da viagem ou destino...', - 'journey.trips.noTripsAvailable': 'Nenhuma viagem disponível', - 'journey.trips.link': 'Vincular', - 'journey.trips.tripLinked': 'Viagem vinculada', - 'journey.trips.linkFailed': 'Não foi possível vincular a viagem', - 'journey.trips.addTrip': 'Adicionar viagem', - 'journey.trips.unlinkTrip': 'Desvincular viagem', - 'journey.trips.unlinkMessage': 'Desvincular "{title}"? Todas as entradas e fotos sincronizadas desta viagem serão excluídas permanentemente. Isso não pode ser desfeito.', - 'journey.trips.unlink': 'Desvincular', - 'journey.trips.tripUnlinked': 'Viagem desvinculada', - 'journey.trips.unlinkFailed': 'Não foi possível desvincular a viagem', - 'journey.trips.noTripsLinkedSettings': 'Nenhuma viagem vinculada', - 'journey.contributors.invite': 'Convidar colaborador', - 'journey.contributors.searchUser': 'Buscar usuário', - 'journey.contributors.searchPlaceholder': 'Nome de usuário ou e-mail...', - 'journey.contributors.noUsers': 'Nenhum usuário encontrado', - 'journey.contributors.role': 'Função', - 'journey.contributors.added': 'Colaborador adicionado', - 'journey.contributors.addFailed': 'Não foi possível adicionar o colaborador', - 'journey.share.publicShare': 'Compartilhamento público', - 'journey.share.createLink': 'Criar link de compartilhamento', - 'journey.share.linkCreated': 'Link de compartilhamento criado', - 'journey.share.createFailed': 'Não foi possível criar o link', - 'journey.share.copy': 'Copiar', - 'journey.share.copied': 'Copiado!', - 'journey.share.timeline': 'Linha do tempo', - 'journey.share.gallery': 'Galeria', - 'journey.share.map': 'Mapa', - 'journey.share.removeLink': 'Remover link de compartilhamento', - 'journey.share.linkDeleted': 'Link de compartilhamento removido', - 'journey.share.deleteFailed': 'Não foi possível excluir', - 'journey.share.updateFailed': 'Não foi possível atualizar', - - // Journey — Invite - 'journey.invite.role': 'Função', - 'journey.invite.viewer': 'Visualizador', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Convidar', - 'journey.invite.inviting': 'Convidando...', - 'journey.settings.title': 'Configurações da jornada', - 'journey.settings.coverImage': 'Imagem de capa', - 'journey.settings.changeCover': 'Alterar capa', - 'journey.settings.addCover': 'Adicionar imagem de capa', - 'journey.settings.name': 'Nome', - 'journey.settings.subtitle': 'Subtítulo', - 'journey.settings.subtitlePlaceholder': 'ex. Tailândia, Vietnã e Camboja', - 'journey.settings.endJourney': 'Arquivar Jornada', - 'journey.settings.reopenJourney': 'Restaurar Jornada', - 'journey.settings.archived': 'Jornada arquivada', - 'journey.settings.reopened': 'Jornada reaberta', - 'journey.settings.endDescription': 'Oculta o selo Ao Vivo. Você pode reabrir a qualquer momento.', - 'journey.settings.delete': 'Excluir', - 'journey.settings.deleteJourney': 'Excluir jornada', - 'journey.settings.deleteMessage': 'Excluir "{title}"? Todas as entradas e fotos serão perdidas.', - 'journey.settings.saved': 'Configurações salvas', - 'journey.settings.saveFailed': 'Não foi possível salvar', - 'journey.settings.coverUpdated': 'Capa atualizada', - 'journey.settings.coverFailed': 'Falha no envio', - 'journey.settings.failedToDelete': 'Falha ao excluir', - 'journey.entries.deleteTitle': 'Excluir entrada', - 'journey.photosUploaded': '{count} fotos enviadas', - 'journey.photosUploadFailed': 'Algumas fotos não foram enviadas', - 'journey.photosAdded': '{count} fotos adicionadas', - 'journey.public.notFound': 'Não encontrado', - 'journey.public.notFoundMessage': 'Esta jornada não existe ou o link expirou.', - 'journey.public.readOnly': 'Somente leitura · Jornada pública', - 'journey.public.tagline': 'Kit de recursos e exploração de viagens', - 'journey.public.sharedVia': 'Compartilhado via', - 'journey.public.madeWith': 'Feito com', - 'journey.pdf.journeyBook': 'Livro da jornada', - 'journey.pdf.madeWith': 'Feito com TREK', - 'journey.pdf.day': 'Dia', - 'journey.pdf.theEnd': 'Fim', - 'journey.pdf.saveAsPdf': 'Salvar como PDF', - 'journey.pdf.pages': 'páginas', - 'journey.picker.tripPeriod': 'Período da viagem', - 'journey.picker.dateRange': 'Período', - 'journey.picker.allPhotos': 'Todas as fotos', - 'journey.picker.albums': 'Álbuns', - 'journey.picker.selected': 'selecionados', - 'journey.picker.addTo': 'Adicionar a', - 'journey.picker.newGallery': 'Nova galeria', - 'journey.picker.selectAll': 'Selecionar tudo', - 'journey.picker.deselectAll': 'Desmarcar tudo', - 'journey.picker.noAlbums': 'Nenhum álbum encontrado', - 'journey.picker.selectDate': 'Selecionar data', - 'journey.picker.search': 'Pesquisar', - 'dashboard.greeting.morning': 'Bom dia,', - 'dashboard.greeting.afternoon': 'Boa tarde,', - 'dashboard.greeting.evening': 'Boa noite,', - 'dashboard.mobile.liveNow': 'Ao vivo agora', - 'dashboard.mobile.tripProgress': 'Progresso da viagem', - 'dashboard.mobile.daysLeft': '{count} dias restantes', - 'dashboard.mobile.places': 'Lugares', - 'dashboard.mobile.buddies': 'Companheiros', - 'dashboard.mobile.newTrip': 'Nova viagem', - 'dashboard.mobile.currency': 'Moeda', - 'dashboard.mobile.timezone': 'Fuso horário', - 'dashboard.mobile.upcomingTrips': 'Próximas viagens', - 'dashboard.mobile.yourTrips': 'Suas viagens', - 'dashboard.mobile.trips': 'viagens', - 'dashboard.mobile.starts': 'Começa', - 'dashboard.mobile.duration': 'Duração', - 'dashboard.mobile.day': 'dia', - 'dashboard.mobile.days': 'dias', - 'dashboard.mobile.ongoing': 'Em andamento', - 'dashboard.mobile.startsToday': 'Começa hoje', - 'dashboard.mobile.tomorrow': 'Amanhã', - 'dashboard.mobile.inDays': 'Em {count} dias', - 'dashboard.mobile.inMonths': 'Em {count} meses', - 'dashboard.mobile.completed': 'Concluído', - 'dashboard.mobile.currencyConverter': 'Conversor de moedas', - 'nav.profile': 'Perfil', - 'nav.bottomSettings': 'Configurações', - 'nav.bottomAdmin': 'Administração', - 'nav.bottomLogout': 'Sair', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Adicionar lugar', - 'dayplan.mobile.searchPlaces': 'Buscar lugares...', - 'dayplan.mobile.allAssigned': 'Todos os lugares atribuídos', - 'dayplan.mobile.noMatch': 'Sem correspondência', - 'dayplan.mobile.createNew': 'Criar novo lugar', - 'admin.addons.catalog.journey.name': 'Jornada', - 'admin.addons.catalog.journey.description': 'Rastreamento de viagens e diário de viajante com check-ins, fotos e histórias diárias', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Viagens', - 'oauth.scope.group.places': 'Locais', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Bagagem', - 'oauth.scope.group.todos': 'Tarefas', - 'oauth.scope.group.budget': 'Orçamento', - 'oauth.scope.group.reservations': 'Reservas', - 'oauth.scope.group.collab': 'Colaboração', - 'oauth.scope.group.notifications': 'Notificações', - 'oauth.scope.group.vacay': 'Férias', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Clima', - 'oauth.scope.group.journey': 'Jornada', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Ver viagens e itinerários', - 'oauth.scope.trips:read.description': 'Ler viagens, dias, notas e membros', - 'oauth.scope.trips:write.label': 'Editar viagens e itinerários', - 'oauth.scope.trips:write.description': 'Criar e atualizar viagens, dias, notas e gerenciar membros', - 'oauth.scope.trips:delete.label': 'Excluir viagens', - 'oauth.scope.trips:delete.description': 'Excluir viagens permanentemente — esta ação é irreversível', - 'oauth.scope.trips:share.label': 'Gerenciar links de compartilhamento', - 'oauth.scope.trips:share.description': 'Criar, atualizar e revogar links de compartilhamento públicos', - 'oauth.scope.places:read.label': 'Ver locais e dados do mapa', - 'oauth.scope.places:read.description': 'Ler locais, atribuições de dias, tags e categorias', - 'oauth.scope.places:write.label': 'Gerenciar locais', - 'oauth.scope.places:write.description': 'Criar, atualizar e excluir locais, atribuições e tags', - 'oauth.scope.atlas:read.label': 'Ver Atlas', - 'oauth.scope.atlas:read.description': 'Ler países visitados, regiões e lista de desejos', - 'oauth.scope.atlas:write.label': 'Gerenciar Atlas', - 'oauth.scope.atlas:write.description': 'Marcar países e regiões como visitados, gerenciar lista de desejos', - 'oauth.scope.packing:read.label': 'Ver listas de bagagem', - 'oauth.scope.packing:read.description': 'Ler itens, malas e responsáveis por categoria', - 'oauth.scope.packing:write.label': 'Gerenciar listas de bagagem', - 'oauth.scope.packing:write.description': 'Adicionar, atualizar, excluir, marcar e reordenar itens e malas', - 'oauth.scope.todos:read.label': 'Ver listas de tarefas', - 'oauth.scope.todos:read.description': 'Ler tarefas da viagem e responsáveis por categoria', - 'oauth.scope.todos:write.label': 'Gerenciar listas de tarefas', - 'oauth.scope.todos:write.description': 'Criar, atualizar, marcar, excluir e reordenar tarefas', - 'oauth.scope.budget:read.label': 'Ver orçamento', - 'oauth.scope.budget:read.description': 'Ler itens de orçamento e detalhamento de despesas', - 'oauth.scope.budget:write.label': 'Gerenciar orçamento', - 'oauth.scope.budget:write.description': 'Criar, atualizar e excluir itens de orçamento', - 'oauth.scope.reservations:read.label': 'Ver reservas', - 'oauth.scope.reservations:read.description': 'Ler reservas e detalhes de acomodação', - 'oauth.scope.reservations:write.label': 'Gerenciar reservas', - 'oauth.scope.reservations:write.description': 'Criar, atualizar, excluir e reordenar reservas', - 'oauth.scope.collab:read.label': 'Ver colaboração', - 'oauth.scope.collab:read.description': 'Ler notas colaborativas, enquetes e mensagens', - 'oauth.scope.collab:write.label': 'Gerenciar colaboração', - 'oauth.scope.collab:write.description': 'Criar, atualizar e excluir notas, enquetes e mensagens', - 'oauth.scope.notifications:read.label': 'Ver notificações', - 'oauth.scope.notifications:read.description': 'Ler notificações e contagens não lidas', - 'oauth.scope.notifications:write.label': 'Gerenciar notificações', - 'oauth.scope.notifications:write.description': 'Marcar notificações como lidas e respondê-las', - 'oauth.scope.vacay:read.label': 'Ver planos de férias', - 'oauth.scope.vacay:read.description': 'Ler dados de planejamento de férias, entradas e estatísticas', - 'oauth.scope.vacay:write.label': 'Gerenciar planos de férias', - 'oauth.scope.vacay:write.description': 'Criar e gerenciar entradas de férias, feriados e planos de equipe', - 'oauth.scope.geo:read.label': 'Mapas e geocodificação', - 'oauth.scope.geo:read.description': 'Pesquisar locais, resolver URLs de mapa e geocodificar coordenadas', - 'oauth.scope.weather:read.label': 'Previsão do tempo', - 'oauth.scope.weather:read.description': 'Obter previsão do tempo para locais e datas da viagem', - 'oauth.scope.journey:read.label': 'Ver jornadas', - 'oauth.scope.journey:read.description': 'Ler jornadas, entradas e lista de colaboradores', - 'oauth.scope.journey:write.label': 'Gerenciar jornadas', - 'oauth.scope.journey:write.description': 'Criar, atualizar e excluir jornadas e suas entradas', - 'oauth.scope.journey:share.label': 'Gerenciar links de jornadas', - 'oauth.scope.journey:share.description': 'Criar, atualizar e revogar links de compartilhamento públicos para jornadas', - - // System notices - 'system_notice.welcome_v1.title': 'Bem-vindo ao TREK', - 'system_notice.welcome_v1.body': 'Seu planejador de viagens tudo-em-um. Crie roteiros, compartilhe viagens com amigos e fique organizado — online ou offline.', - 'system_notice.welcome_v1.cta_label': 'Planejar uma viagem', - 'system_notice.welcome_v1.hero_alt': 'Destino de viagem pitoresco com a interface do TREK', - 'system_notice.welcome_v1.highlight_plan': 'Roteiros dia a dia para qualquer viagem', - 'system_notice.welcome_v1.highlight_share': 'Colabore com seus companheiros de viagem', - 'system_notice.welcome_v1.highlight_offline': 'Funciona offline no celular', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Aviso anterior', - 'system_notice.pager.next': 'Próximo aviso', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Ir para o aviso {n}', - 'system_notice.pager.position': 'Aviso {current} de {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Fotos foram movidas na versão 3.0', - 'system_notice.v3_photos.body': '**Fotos** no Planejador de Viagens foram removidas. Suas fotos estão seguras — o TREK nunca modificou sua biblioteca Immich ou Synology.\n\nAs fotos agora vivem no addon **Journey**. Journey é opcional — se ainda não estiver disponível, peça ao seu admin para ativá-lo em Admin → Addons.', - 'system_notice.v3_journey.title': 'Conheça o Journey — diário de viagem', - 'system_notice.v3_journey.body': 'Documente suas viagens como histórias ricas com cronologias, galerias de fotos e mapas interativos.', - 'system_notice.v3_journey.cta_label': 'Abrir Journey', - 'system_notice.v3_journey.highlight_timeline': 'Linha do tempo e galeria diária', - 'system_notice.v3_journey.highlight_photos': 'Importar do Immich ou Synology', - 'system_notice.v3_journey.highlight_share': 'Compartilhar publicamente — sem login', - 'system_notice.v3_journey.highlight_export': 'Exportar como álbum de fotos PDF', - 'system_notice.v3_features.title': 'Mais destaques na versão 3.0', - 'system_notice.v3_features.body': 'Algumas outras novidades que vale a pena conhecer nesta versão.', - 'system_notice.v3_features.highlight_dashboard': 'Redesign do painel mobile-first', - 'system_notice.v3_features.highlight_offline': 'Modo offline completo como PWA', - 'system_notice.v3_features.highlight_search': 'Autocompleção de lugares em tempo real', - 'system_notice.v3_features.highlight_import': 'Importar lugares de arquivos KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: atualização OAuth 2.1', - 'system_notice.v3_mcp.body': 'A integração MCP foi completamente reformulada. OAuth 2.1 agora é o método de autenticação recomendado. Tokens estáticos (trek_…) foram descontinuados e serão removidos em uma versão futura.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recomendado (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 escopos de permissão granulares', - 'system_notice.v3_mcp.highlight_deprecated': 'Tokens estáticos trek_ descontinuados', - 'system_notice.v3_mcp.highlight_tools': 'Conjunto de ferramentas e prompts expandido', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Uma nota pessoal minha', - 'system_notice.v3_thankyou.body': 'Antes de seguir em frente — quero fazer uma pausa.\n\nO TREK começou como um projeto paralelo que criei para minhas próprias viagens. Nunca imaginei que cresceria a ponto de 4.000 de vocês confiarem nele para planejar suas aventuras. Cada estrela, cada issue, cada pedido de recurso — eu leio todos, e eles me mantêm firme nas noites longas entre um trabalho em tempo integral e a universidade.\n\nQuero que saibam: o TREK sempre será open source, sempre self-hosted, sempre de vocês. Sem rastreamento, sem assinaturas, sem pegadinhas. Apenas uma ferramenta feita por alguém que ama viajar tanto quanto vocês.\n\nAgradecimento especial ao [jubnl](https://github.com/jubnl) — você se tornou um colaborador incrível. Muito do que torna a versão 3.0 especial tem a sua marca. Obrigado por acreditar neste projeto quando ele ainda era bem cru.\n\nE a cada um de vocês que reportou um bug, traduziu uma string, compartilhou o TREK com um amigo ou simplesmente o usou para planejar uma viagem — **obrigado**. Vocês são a razão de tudo isso existir.\n\nQue venham muitas mais aventuras juntos.\n\n— Maurice\n\n---\n\n[Junte-se à comunidade no Discord](https://discord.gg/7Q6M6jDwzf)\n\nSe o TREK torna suas viagens melhores, um [cafezinho](https://ko-fi.com/mauriceboe) sempre mantém as luzes acesas.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Ação necessária: conflito de conta de usuário', - 'system_notice.v3014_whitespace_collision.body': 'A atualização 3.0.14 detectou um ou mais conflitos de nome de usuário ou e-mail causados por espaços em branco no início ou fim dos valores armazenados. As contas afetadas foram renomeadas automaticamente. Verifique os logs do servidor por linhas começando com **[migration] WHITESPACE COLLISION** para identificar quais contas precisam de revisão.', - 'transport.addTransport': 'Adicionar transporte', - 'transport.modalTitle.create': 'Adicionar transporte', - 'transport.modalTitle.edit': 'Editar transporte', - 'transport.title': 'Transportes', - 'transport.addManual': 'Transporte Manual', -} - -export default br - diff --git a/client/src/i18n/translations/cs.ts b/client/src/i18n/translations/cs.ts deleted file mode 100644 index fbc035d6..00000000 --- a/client/src/i18n/translations/cs.ts +++ /dev/null @@ -1,2371 +0,0 @@ -const cs: Record = { - // Společné (Common) - 'common.save': 'Uložit', - 'common.showMore': 'Zobrazit více', - 'common.showLess': 'Zobrazit méně', - 'common.cancel': 'Zrušit', - 'common.clear': 'Vymazat', - 'common.delete': 'Smazat', - 'common.edit': 'Upravit', - 'common.add': 'Přidat', - 'common.loading': 'Načítání...', - 'common.import': 'Importovat', - 'common.select': 'Vybrat', - 'common.selectAll': 'Vybrat vše', - 'common.deselectAll': 'Zrušit výběr všeho', - 'common.error': 'Chyba', - 'common.unknownError': 'Neznámá chyba', - 'common.tooManyAttempts': 'Příliš mnoho pokusů. Zkuste to prosím znovu.', - 'common.back': 'Zpět', - 'common.all': 'Vše', - 'common.close': 'Zavřít', - 'common.open': 'Otevřít', - 'common.upload': 'Nahrát', - 'common.search': 'Hledat', - 'common.confirm': 'Potvrdit', - 'common.ok': 'OK', - 'common.yes': 'Ano', - 'common.no': 'Ne', - 'common.or': 'nebo', - 'common.none': 'Žádné', - 'common.date': 'Datum', - 'common.rename': 'Přejmenovat', - 'common.discardChanges': 'Zahodit změny', - 'common.discard': 'Zahodit', - 'common.name': 'Jméno', - 'common.email': 'E-mail', - 'common.password': 'Heslo', - 'common.saving': 'Ukládání...', - 'trips.memberRemoved': '{username} odebrán', - 'trips.memberRemoveError': 'Odebrání se nezdařilo', - 'trips.memberAdded': '{username} přidán', - 'trips.memberAddError': 'Přidání se nezdařilo', - 'common.expand': 'Rozbalit', - 'common.collapse': 'Sbalit', - 'common.saved': 'Uloženo', - 'trips.reminder': 'Připomínka', - 'trips.reminderNone': 'Žádná', - 'trips.reminderDay': 'den', - 'trips.reminderDays': 'dní', - 'trips.reminderCustom': 'Vlastní', - 'trips.reminderDaysBefore': 'dní před odjezdem', - 'trips.reminderDisabledHint': 'Připomínky výletů jsou zakázány. Povolte je v Správa > Nastavení > Oznámení.', - 'common.update': 'Aktualizovat', - 'common.change': 'Změnit', - 'common.uploading': 'Nahrávání…', - 'common.backToPlanning': 'Zpět k plánování', - 'common.reset': 'Resetovat', - - // Navigační lišta (Navbar) - 'nav.trip': 'Cesta', - 'nav.share': 'Sdílet', - 'nav.settings': 'Nastavení', - 'nav.admin': 'Administrace', - 'nav.logout': 'Odhlásit se', - 'nav.lightMode': 'Světlý režim', - 'nav.darkMode': 'Tmavý režim', - 'nav.autoMode': 'Automatický režim', - 'nav.administrator': 'Administrátor', - 'nav.myTrips': 'Moje cesty', - - // Přehled (Dashboard) - 'dashboard.title': 'Moje cesty', - 'dashboard.subtitle.loading': 'Načítání cest...', - 'dashboard.subtitle.trips': '{count} cest ({archived} archivováno)', - 'dashboard.subtitle.empty': 'Začněte svou první cestu', - 'dashboard.subtitle.activeOne': '{count} aktivní cesta', - 'dashboard.subtitle.activeMany': '{count} aktivních cest', - 'dashboard.subtitle.archivedSuffix': ' · {count} archivováno', - 'dashboard.newTrip': 'Nová cesta', - 'dashboard.gridView': 'Mřížka', - 'dashboard.listView': 'Seznam', - 'dashboard.currency': 'Měna', - 'dashboard.timezone': 'Časová pásma', - 'dashboard.localTime': 'Místní čas', - 'dashboard.timezoneCustomTitle': 'Vlastní pásmo', - 'dashboard.timezoneCustomLabelPlaceholder': 'Popisek (volitelné)', - 'dashboard.timezoneCustomTzPlaceholder': 'např. America/New_York', - 'dashboard.timezoneCustomAdd': 'Přidat', - 'dashboard.timezoneCustomErrorEmpty': 'Zadejte identifikátor pásma', - 'dashboard.timezoneCustomErrorInvalid': 'Neplatné pásmo. Použijte formát jako např. Europe/Prague', - 'dashboard.timezoneCustomErrorDuplicate': 'Již bylo přidáno', - 'dashboard.emptyTitle': 'Zatím žádné cesty', - 'dashboard.emptyText': 'Vytvořte svou první cestu a začněte plánovat!', - 'dashboard.emptyButton': 'Vytvořit první cestu', - 'dashboard.nextTrip': 'Další cesta', - 'dashboard.shared': 'Sdílené', - 'dashboard.sharedBy': 'Sdílí {name}', - 'dashboard.days': 'Dní', - 'dashboard.places': 'Míst', - 'dashboard.members': 'Cestovní parťáci', - 'dashboard.archive': 'Archivovat', - 'dashboard.copyTrip': 'Kopírovat', - 'dashboard.copySuffix': 'kopie', - 'dashboard.restore': 'Obnovit', - 'dashboard.archived': 'Archivováno', - 'dashboard.status.ongoing': 'Probíhající', - 'dashboard.status.today': 'Dnes', - 'dashboard.status.tomorrow': 'Zítra', - 'dashboard.status.past': 'Proběhlé', - 'dashboard.status.daysLeft': 'zbývá {count} dní', - 'dashboard.toast.loadError': 'Nepodařilo se načíst cesty', - 'dashboard.toast.created': 'Cesta byla úspěšně vytvořena!', - 'dashboard.toast.createError': 'Nepodařilo se vytvořit cestu', - 'dashboard.toast.updated': 'Cesta byla aktualizována!', - 'dashboard.toast.updateError': 'Nepodařilo se aktualizovat cestu', - 'dashboard.toast.deleted': 'Cesta byla smazána', - 'dashboard.toast.deleteError': 'Nepodařilo se smazat cestu', - 'dashboard.toast.archived': 'Cesta byla archivována', - 'dashboard.toast.archiveError': 'Nepodařilo se archivovat cestu', - 'dashboard.toast.restored': 'Cesta byla obnovena', - 'dashboard.toast.restoreError': 'Nepodařilo se obnovit cestu', - 'dashboard.toast.copied': 'Cesta byla zkopírována!', - 'dashboard.toast.copyError': 'Nepodařilo se zkopírovat cestu', - 'dashboard.confirm.delete': 'Smazat cestu „{title}”? Všechna místa a plány budou trvale smazány.', - 'dashboard.editTrip': 'Upravit cestu', - 'dashboard.createTrip': 'Vytvořit novou cestu', - 'dashboard.tripTitle': 'Název', - 'dashboard.tripTitlePlaceholder': 'např. Léto v Japonsku', - 'dashboard.tripDescription': 'Popis', - 'dashboard.tripDescriptionPlaceholder': 'O čem je tato cesta?', - 'dashboard.startDate': 'Datum začátku', - 'dashboard.endDate': 'Datum konce', - 'dashboard.dayCount': 'Počet dnů', - 'dashboard.dayCountHint': 'Kolik dnů naplánovat, když nejsou nastavena data cesty.', - 'dashboard.noDateHint': 'Datum nezadáno – výchozí délka nastavena na 7 dní. Toto lze kdykoli změnit.', - 'dashboard.coverImage': 'Úvodní obrázek', - 'dashboard.addCoverImage': 'Vybrat úvodní obrázek (nebo přetáhnout sem)', - 'dashboard.addMembers': 'Spolucestující', - 'dashboard.addMember': 'Přidat člena', - 'dashboard.coverSaved': 'Úvodní obrázek uložen', - 'dashboard.coverUploadError': 'Nahrávání se nezdařilo', - 'dashboard.coverRemoveError': 'Odstranění se nezdařilo', - 'dashboard.titleRequired': 'Název je povinný', - 'dashboard.endDateError': 'Datum konce musí být po datu začátku', - - // Nastavení (Settings) - 'settings.title': 'Nastavení', - 'settings.subtitle': 'Upravte své osobní nastavení', - 'settings.tabs.display': 'Zobrazení', - 'settings.tabs.map': 'Mapa', - 'settings.tabs.notifications': 'Oznámení', - 'settings.tabs.integrations': 'Integrace', - 'settings.tabs.account': 'Účet', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'O aplikaci', - 'settings.map': 'Mapy', - 'settings.mapTemplate': 'Šablona mapy', - 'settings.mapTemplatePlaceholder.select': 'Vyberte šablonu...', - 'settings.mapDefaultHint': 'Ponechte prázdné pro OpenStreetMap (výchozí)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL šablony pro mapové dlaždice', - 'settings.mapProvider': 'Poskytovatel mapy', - 'settings.mapProviderHint': 'Ovlivňuje mapy v Trip Planneru a Journey. Atlas vždy používá Leaflet.', - 'settings.mapLeafletSubtitle': 'Klasické 2D, libovolné rastrové dlaždice', - 'settings.mapMapboxSubtitle': 'Vektorové dlaždice, 3D budovy a terén', - 'settings.mapExperimental': 'Experimentální', - 'settings.mapMapboxToken': 'Mapbox přístupový token', - 'settings.mapMapboxTokenHint': 'Veřejný token (pk.*) z', - 'settings.mapMapboxTokenLink': 'mapbox.com → Přístupové tokeny', - 'settings.mapStyle': 'Styl mapy', - 'settings.mapStylePlaceholder': 'Vyberte styl Mapbox', - 'settings.mapStyleHint': 'Preset nebo vaše vlastní URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': '3D budovy a terén', - 'settings.map3dHint': 'Náklon + skutečné 3D vyvýšení budov — funguje s každým stylem, včetně satelitu.', - 'settings.mapHighQuality': 'Režim vysoké kvality', - 'settings.mapHighQualityHint': 'Antialiasing + zobrazení glóbu pro ostřejší hrany a realistický pohled na svět.', - 'settings.mapHighQualityWarning': 'Může ovlivnit výkon na slabších zařízeních.', - 'settings.mapTipLabel': 'Tip:', - 'settings.mapTip': 'Pravé tlačítko myši a táhněte pro rotaci/náklon mapy. Prostřední tlačítko pro přidání místa (pravé tlačítko je vyhrazeno pro rotaci).', - 'settings.latitude': 'Zeměpisná šířka', - 'settings.longitude': 'Zeměpisná délka', - 'settings.saveMap': 'Uložit nastavení mapy', - 'settings.apiKeys': 'API klíče', - 'settings.mapsKey': 'Google Maps API klíč', - 'settings.mapsKeyHint': 'Pro vyhledávání míst. Vyžaduje Places API (New). Získáte na console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API klíč', - 'settings.weatherKeyHint': 'Pro předpověď počasí. Zdarma na openweathermap.org/api', - 'settings.keyPlaceholder': 'Vložte klíč...', - 'settings.configured': 'Nastaveno', - 'settings.saveKeys': 'Uložit klíče', - 'settings.display': 'Zobrazení', - 'settings.colorMode': 'Barevné schéma', - 'settings.light': 'Světlé', - 'settings.dark': 'Tmavé', - 'settings.auto': 'Automatické', - 'settings.language': 'Jazyk', - 'settings.temperature': 'Jednotky teploty', - 'settings.timeFormat': 'Formát času', - 'settings.blurBookingCodes': 'Skrýt rezervační kódy', - 'settings.notifications': 'Oznámení', - 'settings.notifyTripInvite': 'Pozvánky na cesty', - 'settings.notifyBookingChange': 'Změny rezervací', - 'settings.notifyTripReminder': 'Připomínky cest', - 'settings.notifyTodoDue': 'Úkol se blíží', - 'settings.notifyVacayInvite': 'Pozvánky k propojení Vacay', - 'settings.notifyPhotosShared': 'Sdílené fotky (Immich)', - 'settings.notifyCollabMessage': 'Zprávy v chatu (Collab)', - 'settings.notifyPackingTagged': 'Seznam balení: přiřazení', - 'settings.notifyWebhook': 'Webhook oznámení', - 'settings.notificationsDisabled': 'Oznámení nejsou nakonfigurována. Požádejte správce o aktivaci e-mailových nebo webhookových oznámení.', - 'settings.notificationsActive': 'Aktivní kanál', - 'settings.notificationsManagedByAdmin': 'Události oznámení jsou konfigurovány administrátorem.', - 'settings.on': 'Zapnuto', - 'settings.off': 'Vypnuto', - 'settings.mcp.title': 'Konfigurace MCP', - 'settings.mcp.endpoint': 'MCP endpoint', - 'settings.mcp.clientConfig': 'Konfigurace klienta', - 'settings.mcp.clientConfigHint': 'Nahraďte API tokenem ze seznamu níže. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Nahraďte a přihlašovacími údaji ze klienta OAuth 2.1, který jste vytvořili výše. mcp-remote při prvním připojení otevře prohlížeč pro dokončení autorizace. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).', - 'settings.mcp.copy': 'Kopírovat', - 'settings.mcp.copied': 'Zkopírováno!', - 'settings.mcp.apiTokens': 'API tokeny', - 'settings.mcp.createToken': 'Vytvořit nový token', - 'settings.mcp.noTokens': 'Zatím žádné tokeny. Vytvořte jeden pro připojení MCP klientů.', - 'settings.mcp.tokenCreatedAt': 'Vytvořen', - 'settings.mcp.tokenUsedAt': 'Použit', - 'settings.mcp.deleteTokenTitle': 'Smazat token', - 'settings.mcp.deleteTokenMessage': 'Tento token přestane okamžitě fungovat. Všichni MCP klienti, kteří ho používají, ztratí přístup.', - 'settings.mcp.modal.createTitle': 'Vytvořit API token', - 'settings.mcp.modal.tokenName': 'Název tokenu', - 'settings.mcp.modal.tokenNamePlaceholder': 'např. Claude Desktop, Pracovní notebook', - 'settings.mcp.modal.creating': 'Vytváření…', - 'settings.mcp.modal.create': 'Vytvořit token', - 'settings.mcp.modal.createdTitle': 'Token vytvořen', - 'settings.mcp.modal.createdWarning': 'Tento token bude zobrazen pouze jednou. Zkopírujte a uložte ho nyní — nelze ho obnovit.', - 'settings.mcp.modal.done': 'Hotovo', - 'settings.mcp.toast.created': 'Token vytvořen', - 'settings.mcp.toast.createError': 'Nepodařilo se vytvořit token', - 'settings.mcp.toast.deleted': 'Token smazán', - 'settings.mcp.toast.deleteError': 'Nepodařilo se smazat token', - 'settings.mcp.apiTokensDeprecated': 'API tokeny jsou zastaralé a budou odstraněny v budoucí verzi. Místo toho použijte klienty OAuth 2.1.', - 'settings.oauth.clients': 'Klienti OAuth 2.1', - 'settings.oauth.clientsHint': 'Zaregistrujte klienty OAuth 2.1, aby se aplikace MCP třetích stran (Claude Web, Cursor atd.) mohly připojit bez statických tokenů.', - 'settings.oauth.createClient': 'Nový klient', - 'settings.oauth.noClients': 'Žádní klienti OAuth nejsou zaregistrováni.', - 'settings.oauth.clientId': 'ID klienta', - 'settings.oauth.clientSecret': 'Tajný klíč klienta', - 'settings.oauth.deleteClient': 'Smazat klienta', - 'settings.oauth.deleteClientMessage': 'Tento klient a všechny aktivní relace budou trvale odstraněny. Jakákoliv aplikace, která ho používá, okamžitě ztratí přístup.', - 'settings.oauth.rotateSecret': 'Obnovit tajný klíč', - 'settings.oauth.rotateSecretMessage': 'Bude vygenerován nový tajný klíč klienta a všechny stávající relace budou okamžitě zneplatněny. Aktualizujte aplikaci před zavřením tohoto dialogu.', - 'settings.oauth.rotateSecretConfirm': 'Obnovit', - 'settings.oauth.rotateSecretConfirming': 'Obnovování…', - 'settings.oauth.rotateSecretDoneTitle': 'Nový tajný klíč vygenerován', - 'settings.oauth.rotateSecretDoneWarning': 'Tento tajný klíč se zobrazí pouze jednou. Zkopírujte ho nyní a aktualizujte aplikaci — všechny předchozí relace byly zneplatněny.', - 'settings.oauth.activeSessions': 'Aktivní relace OAuth', - 'settings.oauth.sessionScopes': 'Oprávnění', - 'settings.oauth.sessionExpires': 'Vyprší', - 'settings.oauth.revoke': 'Odvolat', - 'settings.oauth.revokeSession': 'Odvolat relaci', - 'settings.oauth.revokeSessionMessage': 'Tím se okamžitě odvolá přístup pro tuto relaci OAuth.', - 'settings.oauth.modal.createTitle': 'Zaregistrovat klienta OAuth', - 'settings.oauth.modal.presets': 'Rychlá nastavení', - 'settings.oauth.modal.clientName': 'Název aplikace', - 'settings.oauth.modal.clientNamePlaceholder': 'např. Claude Web, Moje MCP aplikace', - 'settings.oauth.modal.redirectUris': 'Přesměrovací URI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Jedno URI na řádek. Vyžadováno HTTPS (localhost vyjmuto). Vyžadována přesná shoda.', - 'settings.oauth.modal.scopes': 'Povolená oprávnění', - 'settings.oauth.modal.scopesHint': 'list_trips a get_trip_summary jsou vždy dostupné — bez požadovaného oprávnění. Umožňují AI zjistit potřebná ID výletů.', - 'settings.oauth.modal.selectAll': 'Vybrat vše', - 'settings.oauth.modal.deselectAll': 'Zrušit výběr', - 'settings.oauth.modal.creating': 'Registrování…', - 'settings.oauth.modal.create': 'Zaregistrovat klienta', - 'settings.oauth.modal.createdTitle': 'Klient zaregistrován', - 'settings.oauth.modal.createdWarning': 'Tajný klíč klienta se zobrazí pouze jednou. Zkopírujte ho nyní — nelze ho obnovit.', - 'settings.oauth.toast.createError': 'Registrace klienta OAuth se nezdařila', - 'settings.oauth.toast.deleted': 'Klient OAuth smazán', - 'settings.oauth.toast.deleteError': 'Smazání klienta OAuth se nezdařilo', - 'settings.oauth.toast.revoked': 'Relace odvolána', - 'settings.oauth.toast.revokeError': 'Odvolání relace se nezdařilo', - 'settings.oauth.toast.rotateError': 'Obnovení tajného klíče klienta se nezdařilo', - 'settings.oauth.modal.machineClient': 'Strojový klient (bez přihlášení v prohlížeči)', - 'settings.oauth.modal.machineClientHint': 'Používá grant client_credentials — bez URI pro přesměrování. Token je vydán přímo přes client_id + client_secret a funguje jako vy v rámci vybraných oborů.', - 'settings.oauth.modal.machineClientUsage': 'Získat token: POST /oauth/token s grant_type=client_credentials, client_id a client_secret. Bez prohlížeče, bez obnovovacího tokenu.', - 'settings.oauth.badge.machine': 'strojový', - 'settings.account': 'Účet', - 'settings.about': 'O aplikaci', - 'settings.about.reportBug': 'Nahlásit chybu', - 'settings.about.reportBugHint': 'Našli jste problém? Dejte nám vědět', - 'settings.about.featureRequest': 'Navrhnout funkci', - 'settings.about.featureRequestHint': 'Navrhněte novou funkci', - 'settings.about.wikiHint': 'Dokumentace a návody', - 'settings.about.supporters.badge': 'Měsíční podporovatelé', - 'settings.about.supporters.title': 'Společníci na cestě s TREK', - 'settings.about.supporters.subtitle': 'Zatímco plánuješ další trasu, tihle lidé plánují společně se mnou budoucnost TREK. Jejich měsíční příspěvek jde přímo na vývoj a reálně strávené hodiny — aby TREK zůstal Open Source.', - 'settings.about.supporters.since': 'podporovatel od {date}', - 'settings.about.supporters.tierEmpty': 'Buď první', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK je samohostovaný plánovač cest, který vám pomůže organizovat výlety od prvního nápadu po poslední vzpomínku. Denní plánování, rozpočet, balicí seznamy, fotky a mnoho dalšího — vše na jednom místě, na vašem vlastním serveru.', - 'settings.about.madeWith': 'Vytvořeno s', - 'settings.about.madeBy': 'Mauricem a rostoucí open-source komunitou.', - 'settings.username': 'Uživatelské jméno', - 'settings.email': 'E-mail', - 'settings.role': 'Role', - 'settings.roleAdmin': 'Administrátor', - 'settings.oidcLinked': 'Propojeno přes', - 'settings.changePassword': 'Změnit heslo', - 'settings.currentPassword': 'Současné heslo', - 'settings.currentPasswordRequired': 'Současné heslo je vyžadováno', - 'settings.newPassword': 'Nové heslo', - 'settings.confirmPassword': 'Potvrdit nové heslo', - 'settings.updatePassword': 'Aktualizovat heslo', - 'settings.passwordRequired': 'Zadejte prosím současné i nové heslo', - 'settings.passwordTooShort': 'Heslo musí mít alespoň 8 znaků', - 'settings.passwordMismatch': 'Hesla se neshodují', - 'settings.passwordWeak': 'Heslo musí obsahovat velké a malé písmeno, číslici a speciální znak', - 'settings.passwordChanged': 'Heslo bylo úspěšně změněno', - 'settings.deleteAccount': 'Smazat účet', - 'settings.deleteAccountTitle': 'Smazat váš účet?', - 'settings.deleteAccountWarning': 'Váš účet a všechny vaše cesty, místa a soubory budou trvale smazány. Tuto akci nelze vrátit.', - 'settings.deleteAccountConfirm': 'Smazat natrvalo', - 'settings.deleteBlockedTitle': 'Účet nelze smazat', - 'settings.deleteBlockedMessage': 'Jste jediným administrátorem. Před smazáním svého účtu předejte roli administrátora jinému uživateli.', - 'settings.roleUser': 'Uživatel', - 'settings.saveProfile': 'Uložit profil', - 'settings.toast.mapSaved': 'Nastavení map uloženo', - 'settings.toast.keysSaved': 'API klíče uloženy', - 'settings.toast.displaySaved': 'Nastavení zobrazení uloženo', - 'settings.toast.profileSaved': 'Profil byl uložen', - 'settings.uploadAvatar': 'Nahrát profilový obrázek', - 'settings.removeAvatar': 'Odebrat profilový obrázek', - 'settings.avatarUploaded': 'Profilový obrázek byl aktualizován', - 'settings.avatarRemoved': 'Profilový obrázek byl odstraněn', - 'settings.avatarError': 'Nahrávání se nezdařilo', - 'settings.mfa.title': 'Dvoufaktorové ověření (2FA)', - 'settings.mfa.description': 'Přidá druhý stupeň zabezpečení při přihlašování e-mailem a heslem. Použijte aplikaci (Google Authenticator, Authy apod.).', - 'settings.mfa.requiredByPolicy': 'Správce vyžaduje dvoufázové ověření. Nejdřív níže nastavte aplikaci autentikátoru.', - 'settings.mfa.backupTitle': 'Záložní kódy', - 'settings.mfa.backupDescription': 'Použijte tyto jednorázové kódy, pokud ztratíte přístup k autentizační aplikaci.', - 'settings.mfa.backupWarning': 'Uložte si je hned. Každý kód lze použít pouze jednou.', - 'settings.mfa.backupCopy': 'Kopírovat kódy', - 'settings.mfa.backupDownload': 'Stáhnout TXT', - 'settings.mfa.backupPrint': 'Tisk / PDF', - 'settings.mfa.backupCopied': 'Záložní kódy zkopírovány', - 'settings.mfa.enabled': '2FA je pro váš účet aktivní.', - 'settings.mfa.disabled': '2FA není aktivní.', - 'settings.mfa.setup': 'Nastavit autentizační aplikaci', - 'settings.mfa.scanQr': 'Naskenujte tento QR kód ve vaší aplikaci nebo zadejte kód ručně.', - 'settings.mfa.secretLabel': 'Tajný klíč (pro ruční zadání)', - 'settings.mfa.codePlaceholder': '6místný kód', - 'settings.mfa.enable': 'Zapnout 2FA', - 'settings.mfa.cancelSetup': 'Zrušit', - 'settings.mfa.disableTitle': 'Vypnout 2FA', - 'settings.mfa.disableHint': 'Zadejte své heslo k účtu a aktuální kód z aplikace.', - 'settings.mfa.disable': 'Vypnout 2FA', - 'settings.mfa.toastEnabled': 'Dvoufaktorové ověření bylo zapnuto', - 'settings.mfa.toastDisabled': 'Dvoufaktorové ověření bylo vypnuto', - 'settings.mfa.demoBlocked': 'Není k dispozici v demo režimu', - 'admin.notifications.title': 'Oznámení', - 'admin.notifications.hint': 'Vyberte kanál oznámení. Současně může být aktivní pouze jeden.', - 'admin.notifications.none': 'Vypnuto', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Uložit nastavení oznámení', - 'admin.notifications.saved': 'Nastavení oznámení uloženo', - 'admin.notifications.testWebhook': 'Odeslat testovací webhook', - 'admin.notifications.testWebhookSuccess': 'Testovací webhook úspěšně odeslán', - 'admin.notifications.testWebhookFailed': 'Odeslání testovacího webhooku se nezdařilo', - 'admin.smtp.title': 'E-mail a oznámení', - 'admin.smtp.hint': 'Konfigurace SMTP pro odesílání e-mailových oznámení.', - 'admin.smtp.testButton': 'Odeslat testovací e-mail', - 'admin.webhook.hint': 'Odesílat oznámení na externí webhook (Discord, Slack atd.).', - 'admin.smtp.testSuccess': 'Testovací e-mail byl úspěšně odeslán', - 'admin.smtp.testFailed': 'Odeslání testovacího e-mailu se nezdařilo', - 'dayplan.icsTooltip': 'Exportovat kalendář (ICS)', - 'share.linkTitle': 'Veřejný odkaz', - 'share.linkHint': 'Vytvořte odkaz, kterým si může kdokoli prohlédnout tuto cestu bez přihlášení. Pouze pro čtení — úpravy nejsou možné.', - 'share.createLink': 'Vytvořit odkaz', - 'share.deleteLink': 'Smazat odkaz', - 'share.createError': 'Nepodařilo se vytvořit odkaz', - 'common.copy': 'Kopírovat', - 'common.copied': 'Zkopírováno', - 'share.permMap': 'Mapa a plán', - 'share.permBookings': 'Rezervace', - 'share.permPacking': 'Balení', - 'shared.expired': 'Odkaz vypršel nebo je neplatný', - 'shared.expiredHint': 'Tento sdílený odkaz na cestu již není aktivní.', - 'shared.readOnly': 'Sdílené zobrazení – pouze pro čtení', - 'shared.tabPlan': 'Plán', - 'shared.tabBookings': 'Rezervace', - 'shared.tabPacking': 'Balení', - 'shared.tabBudget': 'Rozpočet', - 'shared.tabChat': 'Chat', - 'shared.days': 'dní', - 'shared.places': 'míst', - 'shared.other': 'Ostatní', - 'shared.totalBudget': 'Celkový rozpočet', - 'shared.messages': 'zpráv', - 'shared.sharedVia': 'Sdíleno přes', - 'shared.confirmed': 'Potvrzeno', - 'shared.pending': 'Čeká na potvrzení', - 'share.permBudget': 'Rozpočet', - 'share.permCollab': 'Chat', - - // Přihlášení (Login) - 'login.error': 'Přihlášení se nezdařilo. Zkontrolujte prosím své údaje.', - 'login.tagline': 'Vaše cesty.\nVáš plán.', - 'login.description': 'Plánujte cesty společně s interaktivními mapami, rozpočty a synchronizací v reálném čase.', - 'login.features.maps': 'Interaktivní mapy', - 'login.features.mapsDesc': 'Google Places, trasy a shlukování bodů', - 'login.features.realtime': 'Synchronizace v reálném čase', - 'login.features.realtimeDesc': 'Plánujte společně přes WebSocket', - 'login.features.budget': 'Sledování rozpočtu', - 'login.features.budgetDesc': 'Kategorie, grafy a náklady na osobu', - 'login.features.collab': 'Spolupráce', - 'login.features.collabDesc': 'Více uživatelů se sdílenými cestami', - 'login.features.packing': 'Seznamy věcí', - 'login.features.packingDesc': 'Kategorie, pokrok v balení a návrhy', - 'login.features.bookings': 'Rezervace', - 'login.features.bookingsDesc': 'Lety, hotely, restaurace a další', - 'login.features.files': 'Dokumenty', - 'login.features.filesDesc': 'Nahrávejte a spravujte dokumenty', - 'login.features.routes': 'Chytré trasy', - 'login.features.routesDesc': 'Automatická optimalizace a export do Google Maps', - 'login.selfHosted': 'Self-hosted · Open Source · Vaše data zůstávají u vás', - 'login.title': 'Přihlásit se', - 'login.subtitle': 'Vítejte zpět', - 'login.signingIn': 'Přihlašování…', - 'login.signIn': 'Přihlásit se', - 'login.createAdmin': 'Vytvořit účet administrátora', - 'login.createAdminHint': 'Nastavte první administrátorský účet pro TREK.', - 'login.setNewPassword': 'Nastavit nové heslo', - 'login.setNewPasswordHint': 'Před pokračováním musíte změnit heslo.', - 'login.createAccount': 'Vytvořit účet', - 'login.createAccountHint': 'Zaregistrujte si nový účet.', - 'login.creating': 'Vytváření…', - 'login.noAccount': 'Nemáte účet?', - 'login.hasAccount': 'Již máte účet?', - 'login.register': 'Registrovat se', - 'login.emailPlaceholder': 'vas@email.cz', - 'login.username': 'Uživatelské jméno', - 'login.oidc.registrationDisabled': 'Registrace je zakázána. Kontaktujte svého administrátora.', - 'login.oidc.noEmail': 'Od poskytovatele nebyl přijat žádný e-mail.', - 'login.oidc.tokenFailed': 'Ověření se nezdařilo.', - 'login.oidc.invalidState': 'Neplatná relace. Zkuste to prosím znovu.', - 'login.demoFailed': 'Přihlášení do dema se nezdařilo', - 'login.oidcSignIn': 'Přihlásit se přes {name}', - 'login.oidcOnly': 'Ověřování heslem je zakázáno. Přihlaste se prosím přes SSO poskytovatele.', - 'login.oidcLoggedOut': 'Byl jste odhlášen. Přihlaste se znovu přes SSO poskytovatele.', - 'login.demoHint': 'Vyzkoušejte demo – registrace není nutná', - 'login.mfaTitle': 'Dvoufaktorové ověření', - 'login.mfaSubtitle': 'Zadejte 6místný kód z vaší autentizační aplikace.', - 'login.mfaCodeLabel': 'Ověřovací kód', - 'login.mfaCodeRequired': 'Zadejte kód z aplikace.', - 'login.mfaHint': 'Otevřete Google Authenticator, Authy nebo jinou TOTP aplikaci.', - 'login.mfaBack': '← Zpět k přihlášení', - 'login.mfaVerify': 'Ověřit', - 'login.invalidInviteLink': 'Neplatný nebo vypršelý odkaz s pozvánkou', - 'login.oidcFailed': 'Přihlášení přes OIDC se nezdařilo', - 'login.usernameRequired': 'Uživatelské jméno je povinné', - 'login.passwordMinLength': 'Heslo musí mít alespoň 8 znaků', - 'login.forgotPassword': 'Zapomenuté heslo?', - 'login.forgotPasswordTitle': 'Obnovení hesla', - 'login.forgotPasswordBody': 'Zadej e-mail použitý při registraci. Pokud účet existuje, pošleme odkaz pro obnovení.', - 'login.forgotPasswordSubmit': 'Odeslat odkaz', - 'login.forgotPasswordSentTitle': 'Zkontroluj e-mail', - 'login.forgotPasswordSentBody': 'Pokud k tomuto e-mailu existuje účet, odkaz je na cestě. Platnost vyprší za 60 minut.', - 'login.forgotPasswordSmtpHintOff': 'Upozornění: správce nemá nakonfigurovaný SMTP, takže se odkaz pro obnovení zapíše do konzole serveru místo odeslání e-mailem.', - 'login.backToLogin': 'Zpět na přihlášení', - 'login.newPassword': 'Nové heslo', - 'login.confirmPassword': 'Potvrď nové heslo', - 'login.passwordsDontMatch': 'Hesla se neshodují', - 'login.mfaCode': 'Kód 2FA', - 'login.resetPasswordTitle': 'Nastavit nové heslo', - 'login.resetPasswordBody': 'Vyber silné heslo, které jsi tu ještě nepoužil. Minimálně 8 znaků.', - 'login.resetPasswordMfaBody': 'Zadej 2FA kód nebo záložní kód pro dokončení obnovení.', - 'login.resetPasswordSubmit': 'Obnovit heslo', - 'login.resetPasswordVerify': 'Ověřit a obnovit', - 'login.resetPasswordSuccessTitle': 'Heslo aktualizováno', - 'login.resetPasswordSuccessBody': 'Nyní se můžeš přihlásit novým heslem.', - 'login.resetPasswordInvalidLink': 'Neplatný odkaz', - 'login.resetPasswordInvalidLinkBody': 'Odkaz chybí nebo je poškozený. Pro pokračování si vyžádej nový.', - 'login.resetPasswordFailed': 'Obnovení se nezdařilo. Odkaz mohl vypršet.', - - // Registrace (Register) - 'register.passwordMismatch': 'Hesla se neshodují', - 'register.passwordTooShort': 'Heslo musí mít alespoň 8 znaků', - 'register.failed': 'Registrace se nezdařila', - 'register.getStarted': 'Začínáme', - 'register.subtitle': 'Vytvořte si účet a začněte plánovat svou vysněnou cestu.', - 'register.feature1': 'Neomezené plány cest', - 'register.feature2': 'Zobrazení na interaktivní mapě', - 'register.feature3': 'Správa míst a kategorií', - 'register.feature4': 'Sledování rezervací', - 'register.feature5': 'Vytváření seznamů věcí', - 'register.feature6': 'Ukládání fotek a souborů', - 'register.createAccount': 'Vytvořit účet', - 'register.startPlanning': 'Začít plánovat', - 'register.minChars': 'Min. 6 znaků', - 'register.confirmPassword': 'Potvrdit heslo', - 'register.repeatPassword': 'Heslo znovu', - 'register.registering': 'Registrace...', - 'register.register': 'Registrovat se', - 'register.hasAccount': 'Již máte účet?', - 'register.signIn': 'Přihlásit se', - - // Administrace (Admin) - 'admin.title': 'Administrace', - 'admin.subtitle': 'Správa uživatelů a systémová nastavení', - 'admin.tabs.users': 'Uživatelé', - 'admin.tabs.categories': 'Kategorie', - 'admin.tabs.backup': 'Zálohování', - 'admin.stats.users': 'Uživatelé', - 'admin.stats.trips': 'Cesty', - 'admin.stats.places': 'Místa', - 'admin.stats.photos': 'Fotky', - 'admin.stats.files': 'Soubory', - 'admin.table.user': 'Uživatel', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Role', - 'admin.table.created': 'Vytvořeno', - 'admin.table.lastLogin': 'Poslední přihlášení', - 'admin.table.actions': 'Akce', - 'admin.you': '(Vy)', - 'admin.editUser': 'Upravit uživatele', - 'admin.newPassword': 'Nové heslo', - 'admin.newPasswordHint': 'Ponechte prázdné pro zachování současného hesla', - 'admin.deleteUser': 'Smazat uživatele „{name}“? Všechny jeho cesty budou trvale smazány.', - 'admin.deleteUserTitle': 'Smazat uživatele', - 'admin.newPasswordPlaceholder': 'Zadejte nové heslo…', - 'admin.toast.loadError': 'Nepodařilo se načíst data administrace', - 'admin.toast.userUpdated': 'Uživatel byl aktualizován', - 'admin.toast.updateError': 'Aktualizace se nezdařila', - 'admin.toast.userDeleted': 'Uživatel byl smazán', - 'admin.toast.deleteError': 'Smazání se nezdařilo', - 'admin.toast.cannotDeleteSelf': 'Nemůžete smazat svůj vlastní účet', - 'admin.toast.userCreated': 'Uživatel byl vytvořen', - 'admin.toast.createError': 'Nepodařilo se vytvořit uživatele', - 'admin.toast.fieldsRequired': 'Uživatelské jméno, e-mail a heslo jsou povinné', - 'admin.createUser': 'Vytvořit uživatele', - 'admin.invite.title': 'Pozvánky', - 'admin.invite.subtitle': 'Vytvářejte jednorázové registrační odkazy', - 'admin.invite.create': 'Vytvořit odkaz', - 'admin.invite.createAndCopy': 'Vytvořit a zkopírovat', - 'admin.invite.empty': 'Zatím nebyly vytvořeny žádné pozvánky', - 'admin.invite.maxUses': 'Max. použití', - 'admin.invite.expiry': 'Vyprší za', - 'admin.invite.uses': 'použito', - 'admin.invite.expiresAt': 'vyprší', - 'admin.invite.createdBy': 'vytvořil', - 'admin.invite.active': 'Aktivní', - 'admin.invite.expired': 'Expirované', - 'admin.invite.usedUp': 'Využito', - 'admin.invite.copied': 'Odkaz byl zkopírován do schránky', - 'admin.invite.copyLink': 'Kopírovat odkaz', - 'admin.invite.deleted': 'Pozvánka smazána', - 'admin.invite.createError': 'Nepodařilo se vytvořit pozvánku', - 'admin.invite.deleteError': 'Nepodařilo se smazat pozvánku', - 'admin.tabs.settings': 'Nastavení', - 'admin.allowRegistration': 'Povolit registraci', - 'admin.allowRegistrationHint': 'Noví uživatelé se mohou sami registrovat', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Vyžadovat dvoufázové ověření (2FA)', - 'admin.requireMfaHint': 'Uživatelé bez 2FA musí dokončit nastavení v Nastavení před použitím aplikace.', - 'admin.apiKeys': 'API klíče', - 'admin.apiKeysHint': 'Volitelné. Povoluje rozšířená data o místech (fotky, počasí).', - 'admin.mapsKey': 'Google Maps API klíč', - 'admin.mapsKeyHint': 'Povinné pro hledání míst. Získáte na console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Bez API klíče se pro hledání používá OpenStreetMap. S Google klíčem lze načítat fotky, hodnocení a otevírací dobu.', - 'admin.recommended': 'Doporučeno', - 'admin.weatherKey': 'OpenWeatherMap API klíč', - 'admin.weatherKeyHint': 'Pro data o počasí. Zdarma na openweathermap.org', - 'admin.validateKey': 'Testovat', - 'admin.keyValid': 'Připojeno', - 'admin.keyInvalid': 'Neplatný', - 'admin.keySaved': 'API klíče byly uloženy', - 'admin.oidcTitle': 'Jednotné přihlášení (OIDC)', - 'admin.oidcSubtitle': 'Povolit přihlášení přes externí poskytovatele (Google, Apple, Authentik, Keycloak).', - 'admin.oidcDisplayName': 'Zobrazované jméno', - 'admin.oidcIssuer': 'URL vydavatele (Issuer)', - 'admin.oidcIssuerHint': 'OpenID Connect Issuer URL, např. https://accounts.google.com', - 'admin.oidcSaved': 'Konfigurace OIDC uložena', - 'admin.oidcOnlyMode': 'Zakázat ověřování heslem', - 'admin.oidcOnlyModeHint': 'Pokud je zapnuto, je povolen pouze SSO login. Registrace i přihlášení heslem budou zablokovány.', - - // Typy souborů (File Types) - 'admin.fileTypes': 'Povolené typy souborů', - 'admin.fileTypesHint': 'Nastavte, které typy souborů mohou uživatelé nahrávat.', - 'admin.fileTypesFormat': 'Přípony oddělené čárkou (např. jpg,png,pdf,doc). Použijte * pro všechny typy.', - 'admin.fileTypesSaved': 'Nastavení souborů uloženo', - - // Šablony balení (Packing Templates) - 'admin.placesPhotos.title': 'Fotografie míst', - 'admin.placesPhotos.subtitle': 'Načítání fotografií z Google Places API. Zakázáním ušetříte kvótu API. Fotografie z Wikimedia nejsou ovlivněny.', - 'admin.placesAutocomplete.title': 'Automatické doplňování míst', - 'admin.placesAutocomplete.subtitle': 'Použití Google Places API pro návrhy vyhledávání. Zakázáním ušetříte kvótu API.', - 'admin.placesDetails.title': 'Podrobnosti o místě', - 'admin.placesDetails.subtitle': 'Načítání podrobných informací o místě (hodiny, hodnocení, web) z Google Places API. Zakázáním ušetříte kvótu API.', - 'admin.bagTracking.title': 'Sledování zavazadel', - 'admin.bagTracking.subtitle': 'Povolit váhu a přiřazení k zavazadlům u položek balení', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Zasílání zpráv v reálném čase', - 'admin.collab.notes.title': 'Poznámky', - 'admin.collab.notes.subtitle': 'Sdílené poznámky a dokumenty', - 'admin.collab.polls.title': 'Ankety', - 'admin.collab.polls.subtitle': 'Skupinové ankety a hlasování', - 'admin.collab.whatsnext.title': 'Co dál', - 'admin.collab.whatsnext.subtitle': 'Návrhy aktivit a další kroky', - 'admin.tabs.config': 'Personalizace', - 'admin.tabs.defaults': 'Výchozí nastavení uživatele', - 'admin.defaultSettings.title': 'Výchozí nastavení uživatele', - 'admin.defaultSettings.description': 'Nastavte výchozí hodnoty pro celou instanci. Uživatelé, kteří nezměnili nastavení, uvidí tyto hodnoty. Jejich vlastní změny mají vždy přednost.', - 'admin.defaultSettings.saved': 'Výchozí nastavení uloženo', - 'admin.defaultSettings.reset': 'Obnovit na vestavěnou výchozí hodnotu', - 'admin.defaultSettings.resetToBuiltIn': 'obnovit', - 'admin.tabs.templates': 'Šablony seznamů', - 'admin.packingTemplates.title': 'Šablony pro balení', - 'admin.packingTemplates.subtitle': 'Vytvářejte opakovaně použitelné seznamy pro své cesty', - 'admin.packingTemplates.create': 'Nová šablona', - 'admin.packingTemplates.namePlaceholder': 'Název šablony (např. Dovolená u moře)', - 'admin.packingTemplates.empty': 'Zatím nejsou vytvořeny žádné šablony', - 'admin.packingTemplates.items': 'položek', - 'admin.packingTemplates.categories': 'kategorií', - 'admin.packingTemplates.itemName': 'Název položky', - 'admin.packingTemplates.itemCategory': 'Kategorie', - 'admin.packingTemplates.categoryName': 'Název kategorie (např. Oblečení)', - 'admin.packingTemplates.addCategory': 'Přidat kategorii', - 'admin.packingTemplates.created': 'Šablona vytvořena', - 'admin.packingTemplates.deleted': 'Šablona smazána', - 'admin.packingTemplates.loadError': 'Nepodařilo se načíst šablony', - 'admin.packingTemplates.createError': 'Nepodařilo se vytvořit šablonu', - 'admin.packingTemplates.deleteError': 'Nepodařilo se smazat šablonu', - 'admin.packingTemplates.saveError': 'Uložení se nezdařilo', - - // Doplňky (Addons) - 'admin.tabs.addons': 'Doplňky', - 'admin.addons.title': 'Doplňky', - 'admin.addons.subtitle': 'Zapněte nebo vypněte funkce a přizpůsobte si TREK.', - 'admin.addons.catalog.memories.name': 'Fotky (Immich)', - 'admin.addons.catalog.memories.description': 'Sdílejte cestovní fotky přes vaši instanci Immich', - 'admin.addons.catalog.packing.name': 'Seznamy', - 'admin.addons.catalog.packing.description': 'Balicí seznamy a úkoly pro vaše výlety', - 'admin.addons.catalog.budget.name': 'Rozpočet', - 'admin.addons.catalog.budget.description': 'Sledování výdajů a plánování rozpočtu cesty', - 'admin.addons.catalog.documents.name': 'Dokumenty', - 'admin.addons.catalog.documents.description': 'Ukládání a správa cestovních dokladů', - 'admin.addons.catalog.vacay.name': 'Dovolená (Vacay)', - 'admin.addons.catalog.vacay.description': 'Osobní plánovač dovolené s kalendářem', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Mapa světa s navštívenými zeměmi a statistikami', - 'admin.addons.catalog.collab.name': 'Spolupráce', - 'admin.addons.catalog.collab.description': 'Poznámky v reálném čase, hlasování a chat pro plánování', - 'admin.addons.enabled': 'Povoleno', - 'admin.addons.disabled': 'Zakázáno', - 'admin.addons.type.trip': 'Cesta', - 'admin.addons.type.global': 'Globální', - 'admin.addons.type.integration': 'Integrace', - 'admin.addons.tripHint': 'Dostupné jako karta v rámci každé cesty', - 'admin.addons.globalHint': 'Dostupné jako samostatná sekce v hlavní navigaci', - 'admin.addons.integrationHint': 'Backendové služby a API integrace bez vlastní stránky', - 'admin.addons.toast.updated': 'Doplněk byl aktualizován', - 'admin.addons.toast.error': 'Aktualizace doplňku se nezdařila', - 'admin.addons.noAddons': 'Žádné doplňky nejsou k dispozici', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol pro integraci AI asistentů', - 'admin.addons.subtitleBefore': 'Zapněte nebo vypněte funkce a přizpůsobte si ', - 'admin.addons.subtitleAfter': '.', - - 'admin.tabs.audit': 'Audit', - - 'admin.audit.subtitle': 'Bezpečnostní a administrátorské události (zálohy, uživatelé, 2FA, nastavení).', - 'admin.audit.empty': 'Zatím žádné záznamy auditu.', - 'admin.audit.refresh': 'Obnovit', - 'admin.audit.loadMore': 'Načíst další', - 'admin.audit.showing': '{count} načteno · {total} celkem', - 'admin.audit.col.time': 'Čas', - 'admin.audit.col.user': 'Uživatel', - 'admin.audit.col.action': 'Akce', - 'admin.audit.col.resource': 'Zdroj', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Detaily', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP přístup', - 'admin.mcpTokens.title': 'MCP přístup', - 'admin.mcpTokens.subtitle': 'Správa OAuth relací a API tokenů všech uživatelů', - 'admin.mcpTokens.sectionTitle': 'API tokeny', - 'admin.mcpTokens.owner': 'Vlastník', - 'admin.mcpTokens.tokenName': 'Název tokenu', - 'admin.mcpTokens.created': 'Vytvořen', - 'admin.mcpTokens.lastUsed': 'Naposledy použit', - 'admin.mcpTokens.never': 'Nikdy', - 'admin.mcpTokens.empty': 'Zatím nebyly vytvořeny žádné MCP tokeny', - 'admin.mcpTokens.deleteTitle': 'Smazat token', - 'admin.mcpTokens.deleteMessage': 'Tento token bude okamžitě zneplatněn. Uživatel ztratí MCP přístup přes tento token.', - 'admin.mcpTokens.deleteSuccess': 'Token smazán', - 'admin.mcpTokens.deleteError': 'Nepodařilo se smazat token', - 'admin.mcpTokens.loadError': 'Nepodařilo se načíst tokeny', - 'admin.oauthSessions.sectionTitle': 'OAuth relace', - 'admin.oauthSessions.clientName': 'Klient', - 'admin.oauthSessions.owner': 'Vlastník', - 'admin.oauthSessions.scopes': 'Oprávnění', - 'admin.oauthSessions.created': 'Vytvořeno', - 'admin.oauthSessions.empty': 'Žádné aktivní OAuth relace', - 'admin.oauthSessions.revokeTitle': 'Zrušit relaci', - 'admin.oauthSessions.revokeMessage': 'Tato OAuth relace bude okamžitě zrušena. Klient ztratí přístup k MCP.', - 'admin.oauthSessions.revokeSuccess': 'Relace zrušena', - 'admin.oauthSessions.revokeError': 'Nepodařilo se zrušit relaci', - 'admin.oauthSessions.loadError': 'Nepodařilo se načíst OAuth relace', - - // GitHub - 'admin.tabs.github': 'GitHub', - 'admin.github.title': 'Historie verzí', - 'admin.github.subtitle': 'Nejnovější aktualizace z {repo}', - 'admin.github.latest': 'Nejnovější', - 'admin.github.prerelease': 'Předběžná verze', - 'admin.github.showDetails': 'Zobrazit podrobnosti', - 'admin.github.hideDetails': 'Skrýt podrobnosti', - 'admin.github.loadMore': 'Načíst další', - 'admin.github.loading': 'Načítání...', - 'admin.github.error': 'Nepodařilo se načíst verze', - 'admin.github.by': 'od', - 'admin.github.support': 'Pomáhá udržovat vývoj TREK', - - // Počasí (Weather) - 'admin.weather.title': 'Data o počasí', - 'admin.weather.badge': 'Od 24. března 2026', - 'admin.weather.description': 'TREK používá Open-Meteo jako zdroj dat. Je to bezplatná open-source služba – není vyžadován API klíč.', - 'admin.weather.forecast': 'Předpověď na 16 dní', - 'admin.weather.forecastDesc': 'Dříve 5 dní (OpenWeatherMap)', - 'admin.weather.climate': 'Historická klimatická data', - 'admin.weather.climateDesc': 'Průměry za posledních 85 let pro dny mimo 16denní předpověď', - 'admin.weather.requests': '10 000 požadavků denně', - 'admin.weather.requestsDesc': 'Zdarma, bez nutnosti klíče', - 'admin.weather.locationHint': 'Počasí se určuje podle prvního místa se souřadnicemi v daném dni.', - - // Aktualizace (Updates) - 'admin.update.available': 'Dostupná aktualizace', - 'admin.update.text': 'TREK {version} je k dispozici. Aktuálně používáte verzi {current}.', - 'admin.update.button': 'Zobrazit na GitHubu', - 'admin.update.install': 'Instalovat aktualizaci', - 'admin.update.confirmTitle': 'Instalovat aktualizaci?', - 'admin.update.confirmText': 'TREK bude aktualizován z verze {current} na {version}. Server se poté automaticky restartuje.', - 'admin.update.dataInfo': 'Všechna vaše data (cesty, uživatelé, API klíče, soubory) budou zachována.', - 'admin.update.warning': 'Aplikace bude během restartu krátce nedostupná.', - 'admin.update.confirm': 'Aktualizovat nyní', - 'admin.update.installing': 'Aktualizace probíhá…', - 'admin.update.success': 'Aktualizace byla nainstalována! Server se restartuje…', - 'admin.update.failed': 'Aktualizace se nezdařila', - 'admin.update.backupHint': 'Před aktualizací doporučujeme vytvořit zálohu.', - 'admin.update.backupLink': 'Přejít na zálohování', - 'admin.update.howTo': 'Jak aktualizovat', - 'admin.update.dockerText': 'Váš TREK běží v Dockeru. Pro aktualizaci na verzi {version} spusťte na svém serveru tyto příkazy:', - 'admin.update.reloadHint': 'Prosím obnovte stránku za několik sekund.', - - // Vacay doplněk - 'vacay.subtitle': 'Plánování a správa dovolené', - 'vacay.settings': 'Nastavení', - 'vacay.year': 'Rok', - 'vacay.addYear': 'Přidat následující rok', - 'vacay.addPrevYear': 'Přidat předchozí rok', - 'vacay.removeYear': 'Odebrat rok', - 'vacay.removeYearConfirm': 'Odebrat rok {year}?', - 'vacay.removeYearHint': 'Všechny záznamy o dovolené a firemní svátky pro tento rok budou trvale smazány.', - 'vacay.remove': 'Odebrat', - 'vacay.persons': 'Osoby', - 'vacay.noPersons': 'Žádné osoby nebyly přidány', - 'vacay.addPerson': 'Přidat osobu', - 'vacay.editPerson': 'Upravit osobu', - 'vacay.removePerson': 'Odebrat osobu', - 'vacay.removePersonConfirm': 'Odebrat osobu {name}?', - 'vacay.removePersonHint': 'Všechny záznamy dovolené pro tuto osobu budou trvale smazány.', - 'vacay.personName': 'Jméno', - 'vacay.personNamePlaceholder': 'Zadejte jméno', - 'vacay.color': 'Barva', - 'vacay.add': 'Přidat', - 'vacay.legend': 'Legenda', - 'vacay.publicHoliday': 'Státní svátek', - 'vacay.companyHoliday': 'Firemní volno', - 'vacay.weekend': 'Víkend', - 'vacay.modeVacation': 'Dovolená', - 'vacay.modeCompany': 'Firemní volno', - 'vacay.entitlement': 'Nárok', - 'vacay.entitlementDays': 'Dní', - 'vacay.used': 'Vyčerpáno', - 'vacay.remaining': 'Zbývá', - 'vacay.carriedOver': 'z roku {year}', - 'vacay.blockWeekends': 'Blokovat víkendy', - 'vacay.blockWeekendsHint': 'Zamezit zadávání dovolené na víkendové dny', - 'vacay.mon': 'Po', - 'vacay.tue': 'Út', - 'vacay.wed': 'St', - 'vacay.thu': 'Čt', - 'vacay.fri': 'Pá', - 'vacay.sat': 'So', - 'vacay.sun': 'Ne', - 'vacay.weekendDays': 'Víkendové dny', - 'vacay.publicHolidays': 'Státní svátky', - 'vacay.publicHolidaysHint': 'Zobrazit státní svátky v kalendáři', - 'vacay.selectCountry': 'Vyberte zemi', - 'vacay.selectRegion': 'Vyberte region (volitelné)', - 'vacay.addCalendar': 'Přidat kalendář', - 'vacay.calendarLabel': 'Popisek (volitelné)', - 'vacay.calendarColor': 'Barva', - 'vacay.noCalendars': 'Zatím nebyly přidány žádné svátkové kalendáře', - 'vacay.companyHolidays': 'Firemní volno', - 'vacay.companyHolidaysHint': 'Povolit označování dnů celofiremního volna', - 'vacay.companyHolidaysNoDeduct': 'Firemní volno se nezapočítává do nároku na dovolenou.', - 'vacay.weekStart': 'Týden začíná', - 'vacay.weekStartHint': 'Zvolte, zda týden začíná v pondělí nebo v neděli', - 'vacay.carryOver': 'Převod dovolené', - 'vacay.carryOverHint': 'Automaticky převádět zbývající dny do dalšího roku', - 'vacay.sharing': 'Sdílení', - 'vacay.sharingHint': 'Sdílejte svůj plán dovolené s ostatními uživateli TREK', - 'vacay.owner': 'Vlastník', - 'vacay.shareEmailPlaceholder': 'E-mail uživatele TREK', - 'vacay.shareSuccess': 'Plán byl úspěšně sdílen', - 'vacay.shareError': 'Nepodařilo se sdílet plán', - 'vacay.dissolve': 'Zrušit propojení', - 'vacay.dissolveHint': 'Znovu oddělit kalendáře. Vaše záznamy zůstanou zachovány.', - 'vacay.dissolveAction': 'Oddělit', - 'vacay.dissolved': 'Kalendáře byly odděleny', - 'vacay.fusedWith': 'Propojeno s', - 'vacay.you': 'vy', - 'vacay.noData': 'Žádná data', - 'vacay.changeColor': 'Změnit barvu', - 'vacay.inviteUser': 'Pozvat uživatele', - 'vacay.inviteHint': 'Pozvěte jiného uživatele TREK ke sdílení společného kalendáře dovolených.', - 'vacay.selectUser': 'Vyberte uživatele', - 'vacay.sendInvite': 'Odeslat pozvánku', - 'vacay.inviteSent': 'Pozvánka odeslána', - 'vacay.inviteError': 'Nepodařilo se odeslat pozvánku', - 'vacay.pending': 'čeká na vyřízení', - 'vacay.noUsersAvailable': 'Žádní uživatelé nejsou k dispozici', - 'vacay.accept': 'Přijmout', - 'vacay.decline': 'Odmítnout', - 'vacay.acceptFusion': 'Přijmout a propojit', - 'vacay.inviteTitle': 'Žádost o propojení', - 'vacay.inviteWantsToFuse': 'vás zve ke sdílení kalendáře dovolených.', - 'vacay.fuseInfo1': 'Oba uvidíte všechny záznamy v jednom sdíleném kalendáři.', - 'vacay.fuseInfo2': 'Obě strany mohou vytvářet a upravovat záznamy tomu druhému.', - 'vacay.fuseInfo3': 'Obě strany mohou měnit nároky na dovolenou.', - 'vacay.fuseInfo4': 'Nastavení (svátky, firemní volno) jsou sdílená.', - 'vacay.fuseInfo5': 'Propojení lze kdykoli zrušit bez ztráty dat.', - - // Atlas doplněk - 'atlas.subtitle': 'Vaše stopa ve světě', - 'atlas.countries': 'Země', - 'atlas.trips': 'Cesty', - 'atlas.places': 'Místa', - 'atlas.unmark': 'Odebrat', - 'atlas.confirmMark': 'Označit tuto zemi jako navštívenou?', - 'atlas.confirmUnmark': 'Odebrat tuto zemi ze seznamu navštívených?', - 'atlas.confirmUnmarkRegion': 'Odebrat tento region ze seznamu navštívených?', - 'atlas.markVisited': 'Označit jako navštívené', - 'atlas.markVisitedHint': 'Přidat tuto zemi do seznamu navštívených', - 'atlas.markRegionVisitedHint': 'Přidat tento region do seznamu navštívených', - 'atlas.addToBucket': 'Přidat do seznamu přání (Bucket list)', - 'atlas.addPoi': 'Přidat místo', - 'atlas.bucketNamePlaceholder': 'Název (země, město, místo...)', - 'atlas.month': 'Měsíc', - 'atlas.addToBucketHint': 'Uložit jako místo, které chcete navštívit', - 'atlas.bucketWhen': 'Kdy plánujete návštěvu?', - 'atlas.statsTab': 'Statistiky', - 'atlas.bucketTab': 'Bucket List', - 'atlas.addBucket': 'Přidat na Bucket List', - 'atlas.bucketNotesPlaceholder': 'Poznámky (volitelné)', - 'atlas.bucketEmpty': 'Váš seznam přání je prázdný', - 'atlas.bucketEmptyHint': 'Přidejte místa, která sníte navštívit', - 'atlas.days': 'Dní', - 'atlas.visitedCountries': 'Navštívené země', - 'atlas.cities': 'Města', - 'atlas.noData': 'Zatím žádná cestovatelská data', - 'atlas.noDataHint': 'Vytvořte cestu a přidejte místa, abyste viděli svou mapu světa', - 'atlas.lastTrip': 'Poslední cesta', - 'atlas.nextTrip': 'Další cesta', - 'atlas.daysLeft': 'dní zbývá', - 'atlas.streak': 'Série', - 'atlas.year': 'rok', - 'atlas.years': 'roky/let', - 'atlas.yearInRow': 'rok v řadě', - 'atlas.yearsInRow': 'let v řadě', - 'atlas.tripIn': 'cesta v roce', - 'atlas.tripsIn': 'cest v roce', - 'atlas.since': 'od', - 'atlas.europe': 'Evropa', - 'atlas.asia': 'Asie', - 'atlas.northAmerica': 'S. Amerika', - 'atlas.southAmerica': 'J. Amerika', - 'atlas.africa': 'Afrika', - 'atlas.oceania': 'Oceánie', - 'atlas.other': 'Ostatní', - 'atlas.firstVisit': 'První cesta', - 'atlas.lastVisitLabel': 'Poslední cesta', - 'atlas.tripSingular': 'Cesta', - 'atlas.tripPlural': 'Cesty', - 'atlas.placeVisited': 'Navštívené místo', - 'atlas.placesVisited': 'Navštívená místa', - - // Plánovač cesty (Trip Planner) - 'trip.tabs.plan': 'Plán', - 'trip.tabs.transports': 'Doprava', - 'trip.tabs.reservations': 'Rezervace', - 'trip.tabs.reservationsShort': 'Rez.', - 'trip.tabs.packing': 'Seznam věcí', - 'trip.tabs.packingShort': 'Balení', - 'trip.tabs.lists': 'Seznamy', - 'trip.tabs.listsShort': 'Seznamy', - 'trip.tabs.budget': 'Rozpočet', - 'trip.tabs.files': 'Soubory', - 'trip.loading': 'Načítání cesty...', - 'trip.loadingPhotos': 'Načítání fotek míst...', - 'trip.mobilePlan': 'Plán', - 'trip.mobilePlaces': 'Místa', - 'trip.toast.placeUpdated': 'Místo bylo aktualizováno', - 'trip.toast.placeAdded': 'Místo bylo přidáno', - 'trip.toast.placeDeleted': 'Místo bylo smazáno', - 'trip.toast.selectDay': 'Prosím nejdříve vyberte den', - 'trip.toast.assignedToDay': 'Místo bylo přiřazeno ke dni', - 'trip.toast.reorderError': 'Nepodařilo se změnit pořadí', - 'trip.toast.reservationUpdated': 'Rezervace aktualizována', - 'trip.toast.reservationAdded': 'Rezervace přidána', - 'trip.toast.deleted': 'Smazáno', - 'trip.confirm.deletePlace': 'Opravdu chcete toto místo smazat?', - 'trip.confirm.deletePlaces': 'Smazat {count} míst?', - 'trip.toast.placesDeleted': '{count} míst smazáno', - - // Denní plán (Day Plan) - 'dayplan.emptyDay': 'Na tento den nejsou naplánována žádná místa', - 'dayplan.addNote': 'Přidat poznámku', - 'dayplan.editNote': 'Upravit poznámku', - 'dayplan.noteAdd': 'Přidat poznámku', - 'dayplan.noteEdit': 'Upravit poznámku', - 'dayplan.noteTitle': 'Poznámka', - 'dayplan.noteSubtitle': 'Poznámka ke dni', - 'dayplan.totalCost': 'Celkové náklady', - 'dayplan.days': 'Dny', - 'dayplan.dayN': 'Den {n}', - 'dayplan.calculating': 'Počítání...', - 'dayplan.route': 'Trasa', - 'dayplan.optimize': 'Optimalizovat', - 'dayplan.optimized': 'Trasa optimalizována', - 'dayplan.routeError': 'Nepodařilo se vypočítat trasu', - 'dayplan.toast.needTwoPlaces': 'Pro optimalizaci trasy jsou potřeba alespoň dvě místa', - 'dayplan.toast.routeOptimized': 'Trasa byla optimalizována', - 'dayplan.toast.noGeoPlaces': 'Nebyla nalezena žádná místa se souřadnicemi pro výpočet trasy', - 'dayplan.confirmed': 'Potvrzeno', - 'dayplan.pendingRes': 'Čeká na potvrzení', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Exportovat denní plán do PDF', - 'dayplan.pdfError': 'Export do PDF se nezdařil', - 'dayplan.cannotReorderTransport': 'Rezervace s pevným časem nelze přeuspořádat', - 'dayplan.confirmRemoveTimeTitle': 'Odebrat čas?', - 'dayplan.confirmRemoveTimeBody': 'Toto místo má pevný čas ({time}). Přesunutím se čas odebere a povolí se volné řazení.', - 'dayplan.confirmRemoveTimeAction': 'Odebrat čas a přesunout', - 'dayplan.cannotDropOnTimed': 'Položky nelze umístit mezi záznamy s pevným časem', - 'dayplan.cannotBreakChronology': 'Toto by porušilo chronologické pořadí naplánovaných položek a rezervací', - - // Boční panel míst (Places Sidebar) - 'places.addPlace': 'Přidat místo/aktivitu', - 'places.importFile': 'Importovat soubor', - 'places.sidebarDrop': 'Pusťte pro import', - 'places.importFileHint': 'Importujte soubory .gpx, .kml nebo .kmz z nástrojů jako Google My Maps, Google Earth nebo GPS tracker.', - 'places.importFileDropHere': 'Klikněte pro výběr souboru nebo jej přetáhněte sem', - 'places.importFileDropActive': 'Přetáhněte soubor pro výběr', - 'places.importFileUnsupported': 'Nepodporovaný typ souboru. Použijte .gpx, .kml nebo .kmz.', - 'places.importFileTooLarge': 'Soubor je příliš velký. Maximální velikost nahrání je {maxMb} MB.', - 'places.importFileError': 'Import se nezdařil', - 'places.importAllSkipped': 'Všechna místa již byla v cestě.', - 'places.gpxImported': '{count} míst importováno z GPX', - 'places.gpxImportTypes': 'Co chcete importovat?', - 'places.gpxImportWaypoints': 'Trasové body', - 'places.gpxImportRoutes': 'Trasy', - 'places.gpxImportTracks': 'Trasy GPS (s geometrií)', - 'places.gpxImportNoneSelected': 'Vyberte alespoň jeden typ k importu.', - 'places.kmlImportTypes': 'Co chcete importovat?', - 'places.kmlImportPoints': 'Body (Placemarks)', - 'places.kmlImportPaths': 'Trasy (LineStrings)', - 'places.kmlImportNoneSelected': 'Vyberte alespoň jeden typ.', - 'places.selectionCount': '{count} vybráno', - 'places.deleteSelected': 'Smazat vybrané', - 'places.kmlKmzImported': 'Importováno {count} míst z KMZ/KML', - 'places.urlResolved': 'Místo importováno z URL', - 'places.importList': 'Import seznamu', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importováno: {created} • Přeskočeno: {skipped}', - 'places.importGoogleList': 'Google Seznam', - 'places.importNaverList': 'Naver Seznam', - 'places.googleListHint': 'Vložte sdílený odkaz na seznam Google Maps pro import všech míst.', - 'places.googleListImported': '{count} míst importováno ze seznamu "{list}"', - 'places.googleListError': 'Import seznamu Google Maps se nezdařil', - 'places.naverListHint': 'Vložte sdílený odkaz na seznam Naver Maps pro import všech míst.', - 'places.naverListImported': '{count} míst importováno ze seznamu "{list}"', - 'places.naverListError': 'Import seznamu Naver Maps se nezdařil', - 'places.viewDetails': 'Zobrazit detaily', - 'places.assignToDay': 'Přidat do kterého dne?', - 'places.all': 'Vše', - 'places.unplanned': 'Nezařazené', - 'places.filterTracks': 'Trasy', - 'places.search': 'Hledat místa...', - 'places.allCategories': 'Všechny kategorie', - 'places.categoriesSelected': 'kategorií', - 'places.clearFilter': 'Vymazat filtr', - 'places.count': '{count} míst', - 'places.countSingular': '1 místo', - 'places.allPlanned': 'Všechna místa jsou naplánována', - 'places.noneFound': 'Žádná místa nebyla nalezena', - 'places.editPlace': 'Upravit místo', - 'places.formName': 'Název', - 'places.formNamePlaceholder': 'např. Eiffelova věž', - 'places.formDescription': 'Popis', - 'places.formDescriptionPlaceholder': 'Krátký popis...', - 'places.formAddress': 'Adresa', - 'places.formAddressPlaceholder': 'Ulice, město, země', - 'places.formLat': 'Zeměpisná šířka', - 'places.formLng': 'Zeměpisná délka', - 'places.formCategory': 'Kategorie', - 'places.noCategory': 'Bez kategorie', - 'places.categoryNamePlaceholder': 'Název kategorie', - 'places.formTime': 'Čas', - 'places.startTime': 'Od', - 'places.endTime': 'Do', - 'places.endTimeBeforeStart': 'Čas konce je před časem začátku', - 'places.timeCollision': 'Časový překryv s:', - 'places.formWebsite': 'Webové stránky', - 'places.formNotes': 'Poznámky', - 'places.formNotesPlaceholder': 'Osobní poznámky...', - 'places.formReservation': 'Rezervace', - 'places.reservationNotesPlaceholder': 'Poznámky k rezervaci, potvrzovací kód...', - 'places.mapsSearchPlaceholder': 'Hledat místa...', - 'places.mapsSearchError': 'Hledání místa se nezdařilo.', - 'places.loadingDetails': 'Načítání podrobností místa…', - 'places.osmHint': 'Používáte hledání přes OpenStreetMap (bez fotek a hodnocení). Pro plné detaily přidejte Google API klíč v nastavení.', - 'places.osmActive': 'Hledání přes OpenStreetMap.', - 'places.categoryCreateError': 'Nepodařilo se vytvořit kategorii', - 'places.nameRequired': 'Prosím zadejte název', - 'places.saveError': 'Uložení se nezdařilo', - - // Inspektor míst (Place Inspector) - 'inspector.opened': 'Otevřeno', - 'inspector.closed': 'Zavřeno', - 'inspector.openingHours': 'Otevírací doba', - 'inspector.showHours': 'Zobrazit otevírací dobu', - 'inspector.files': 'Soubory', - 'inspector.filesCount': '{count} souborů', - 'inspector.removeFromDay': 'Odebrat ze dne', - 'inspector.remove': 'Odstranit', - 'inspector.addToDay': 'Přidat ke dni', - 'inspector.confirmedRes': 'Potvrzená rezervace', - 'inspector.pendingRes': 'Čekající rezervace', - 'inspector.google': 'Otevřít v Google Mapách', - 'inspector.website': 'Otevřít webové stránky', - 'inspector.addRes': 'Rezervace', - 'inspector.editRes': 'Upravit rezervaci', - 'inspector.participants': 'Účastníci', - 'inspector.trackStats': 'Data trasy', - - // Rezervace (Reservations) - 'reservations.title': 'Rezervace', - 'reservations.empty': 'Zatím žádné rezervace', - 'reservations.emptyHint': 'Přidejte rezervace letů, hotelů a dalších', - 'reservations.add': 'Přidat rezervaci', - 'reservations.addManual': 'Ruční rezervace', - 'reservations.placeHint': 'Tip: Rezervace je nejlepší vytvářet přímo z místa – propojí se tak s denním plánem.', - 'reservations.confirmed': 'Potvrzeno', - 'reservations.pending': 'Čeká na potvrzení', - 'reservations.summary': '{confirmed} potvrzených, {pending} čekajících', - 'reservations.fromPlan': 'Z plánu', - 'reservations.showFiles': 'Zobrazit soubory', - 'reservations.editTitle': 'Upravit rezervaci', - 'reservations.status': 'Stav', - 'reservations.datetime': 'Datum a čas', - 'reservations.startTime': 'Čas začátku', - 'reservations.endTime': 'Čas konce', - 'reservations.date': 'Datum', - 'reservations.time': 'Čas', - 'reservations.timeAlt': 'Čas (alternativní, např. 19:30)', - 'reservations.notes': 'Poznámky', - 'reservations.notesPlaceholder': 'Další poznámky...', - 'reservations.meta.airline': 'Letecká společnost', - 'reservations.meta.flightNumber': 'Číslo letu', - 'reservations.meta.from': 'Z', - 'reservations.meta.to': 'Do', - 'reservations.needsReview': 'Zkontrolovat', - 'reservations.needsReviewHint': 'Letiště nebylo možné automaticky rozpoznat — potvrďte prosím místo.', - 'reservations.searchLocation': 'Hledat stanici, přístav, adresu...', - 'airport.searchPlaceholder': 'Kód letiště nebo město (např. FRA)', - 'map.connections': 'Spojení', - 'map.showConnections': 'Zobrazit trasy rezervací', - 'map.hideConnections': 'Skrýt trasy rezervací', - 'settings.bookingLabels': 'Popisky tras rezervací', - 'settings.bookingLabelsHint': 'Zobrazuje názvy stanic / letišť na mapě. Pokud je vypnuto, zobrazí se pouze ikona.', - 'reservations.meta.trainNumber': 'Číslo vlaku', - 'reservations.meta.platform': 'Nástupiště', - 'reservations.meta.seat': 'Sedadlo', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in do', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Ubytování', - 'reservations.meta.pickAccommodation': 'Propojit s ubytováním', - 'reservations.meta.noAccommodation': 'Nic', - 'reservations.meta.hotelPlace': 'Ubytování', - 'reservations.meta.pickHotel': 'Vybrat ubytování', - 'reservations.meta.fromDay': 'Od dne', - 'reservations.meta.toDay': 'Do dne', - 'reservations.meta.selectDay': 'Vyberte den', - 'reservations.type.flight': 'Let', - 'reservations.type.hotel': 'Ubytování', - 'reservations.type.restaurant': 'Restaurace', - 'reservations.type.train': 'Vlak', - 'reservations.type.car': 'Auto', - 'reservations.type.cruise': 'Plavba', - 'reservations.type.event': 'Událost', - 'reservations.type.tour': 'Prohlídka', - 'reservations.type.other': 'Jiné', - 'reservations.confirm.delete': 'Opravdu chcete smazat rezervaci „{name}”?', - 'reservations.confirm.deleteTitle': 'Smazat rezervaci?', - 'reservations.confirm.deleteBody': '„{name}” bude trvale smazána.', - 'reservations.toast.updated': 'Rezervace aktualizována', - 'reservations.toast.removed': 'Rezervace smazána', - 'reservations.toast.fileUploaded': 'Soubor byl nahrán', - 'reservations.toast.uploadError': 'Nahrávání se nezdařilo', - 'reservations.newTitle': 'Nová rezervace', - 'reservations.bookingType': 'Typ rezervace', - 'reservations.titleLabel': 'Název', - 'reservations.titlePlaceholder': 'např. Let LH123, Hotel Adlon...', - 'reservations.locationAddress': 'Místo / Adresa', - 'reservations.locationPlaceholder': 'Adresa, letiště, hotel...', - 'reservations.confirmationCode': 'Rezervační kód', - 'reservations.confirmationPlaceholder': 'např. ABC12345', - 'reservations.day': 'Den', - 'reservations.noDay': 'Žádný den', - 'reservations.place': 'Místo', - 'reservations.noPlace': 'Žádné místo', - 'reservations.pendingSave': 'bude uloženo…', - 'reservations.uploading': 'Nahrávání...', - 'reservations.attachFile': 'Přiložit soubor', - 'reservations.linkExisting': 'Propojit stávající soubor', - 'reservations.toast.saveError': 'Uložení se nezdařilo', - 'reservations.toast.updateError': 'Aktualizace se nezdařila', - 'reservations.toast.deleteError': 'Smazání se nezdařilo', - 'reservations.confirm.remove': 'Odstranit rezervaci pro „{name}”?', - 'reservations.linkAssignment': 'Propojit s přiřazením dne', - 'reservations.pickAssignment': 'Vyberte přiřazení z vašeho plánu...', - 'reservations.noAssignment': 'Bez propojení (samostatné)', - 'reservations.price': 'Cena', - 'reservations.budgetCategory': 'Kategorie rozpočtu', - 'reservations.budgetCategoryPlaceholder': 'např. Doprava, Ubytování', - 'reservations.budgetCategoryAuto': 'Auto (podle typu rezervace)', - 'reservations.budgetHint': 'Při ukládání bude automaticky vytvořena položka rozpočtu.', - 'reservations.departureDate': 'Odlet', - 'reservations.arrivalDate': 'Přílet', - 'reservations.departureTime': 'Čas odletu', - 'reservations.arrivalTime': 'Čas příletu', - 'reservations.pickupDate': 'Vyzvednutí', - 'reservations.returnDate': 'Vrácení', - 'reservations.pickupTime': 'Čas vyzvednutí', - 'reservations.returnTime': 'Čas vrácení', - 'reservations.endDate': 'Datum konce', - 'reservations.meta.departureTimezone': 'TZ odletu', - 'reservations.meta.arrivalTimezone': 'TZ příletu', - 'reservations.span.departure': 'Odlet', - 'reservations.span.arrival': 'Přílet', - 'reservations.span.inTransit': 'Na cestě', - 'reservations.span.pickup': 'Vyzvednutí', - 'reservations.span.return': 'Vrácení', - 'reservations.span.active': 'Aktivní', - 'reservations.span.start': 'Začátek', - 'reservations.span.end': 'Konec', - 'reservations.span.ongoing': 'Probíhá', - 'reservations.validation.endBeforeStart': 'Datum/čas konce musí být po datu/čase začátku', - 'reservations.addBooking': 'Přidat rezervaci', - - // Rozpočet (Budget) - 'budget.title': 'Rozpočet', - 'budget.exportCsv': 'Exportovat CSV', - 'budget.emptyTitle': 'Zatím nebyl vytvořen žádný rozpočet', - 'budget.emptyText': 'Vytvořte kategorie a položky pro plánování cestovního rozpočtu', - 'budget.emptyPlaceholder': 'Zadejte název kategorie...', - 'budget.createCategory': 'Vytvořit kategorii', - 'budget.category': 'Kategorie', - 'budget.categoryName': 'Název kategorie', - 'budget.table.name': 'Název', - 'budget.table.total': 'Celkem', - 'budget.table.persons': 'Osoby', - 'budget.table.days': 'Dní', - 'budget.table.perPerson': 'Na osobu', - 'budget.table.perDay': 'Za den', - 'budget.table.perPersonDay': 'Os. / den', - 'budget.table.note': 'Poznámka', - 'budget.table.date': 'Datum', - 'budget.newEntry': 'Nová položka', - 'budget.defaultEntry': 'Nová položka', - 'budget.defaultCategory': 'Nová kategorie', - 'budget.total': 'Celkem', - 'budget.totalBudget': 'Celkový rozpočet', - 'budget.byCategory': 'Podle kategorie', - 'budget.editTooltip': 'Klikněte pro úpravu', - 'budget.linkedToReservation': 'Propojeno s rezervací — název upravte tam', - 'budget.confirm.deleteCategory': 'Opravdu chcete smazat kategorii „{name}” s {count} položkami?', - 'budget.deleteCategory': 'Smazat kategorii', - 'budget.perPerson': 'Na osobu', - 'budget.paid': 'Zaplaceno', - 'budget.open': 'Nezaplaceno', - 'budget.noMembers': 'Žádní členové nebyli přiřazeni', - 'budget.settlement': 'Vyúčtování', - 'budget.settlementInfo': 'Klikněte na avatar člena u rozpočtové položky pro zelené označení – to znamená, že zaplatil. Vyúčtování pak ukazuje, kdo komu a kolik dluží.', - 'budget.netBalances': 'Čisté zůstatky', - - // Soubory (Files) - 'files.title': 'Soubory', - 'files.pageTitle': 'Soubory a dokumenty', - 'files.subtitle': '{count} souborů pro {trip}', - 'files.download': 'Stáhnout', - 'files.openError': 'Soubor nelze otevřít', - 'files.downloadPdf': 'Stáhnout PDF', - 'files.count': '{count} souborů', - 'files.countSingular': '1 soubor', - 'files.uploaded': '{count} nahráno', - 'files.uploadError': 'Nahrávání se nezdařilo', - 'files.dropzone': 'Přetáhněte soubory sem', - 'files.dropzoneHint': 'nebo klikněte pro výběr', - 'files.allowedTypes': 'Obrázky, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Nahrávání...', - 'files.filterAll': 'Vše', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Obrázky', - 'files.filterDocs': 'Dokumenty', - 'files.filterCollab': 'Poznámky spolupráce', - 'files.sourceCollab': 'Z poznámek spolupráce', - 'files.empty': 'Zatím žádné soubory', - 'files.emptyHint': 'Nahrajte soubory k vaší cestě', - 'files.openTab': 'Otevřít v nové kartě', - 'files.confirm.delete': 'Opravdu chcete smazat tento soubor?', - 'files.toast.deleted': 'Soubor byl smazán', - 'files.toast.deleteError': 'Nepodařilo se smazat soubor', - 'files.sourcePlan': 'Denní plán', - 'files.sourceBooking': 'Rezervace', - 'files.sourceTransport': 'Doprava', - 'files.attach': 'Přiložit', - 'files.pasteHint': 'Můžete také vložit obrázek ze schránky (Ctrl+V)', - 'files.trash': 'Koš', - 'files.trashEmpty': 'Koš je prázdný', - 'files.emptyTrash': 'Vysypat koš', - 'files.restore': 'Obnovit', - 'files.star': 'Označit hvězdičkou', - 'files.unstar': 'Odebrat hvězdičku', - 'files.assign': 'Přiřadit', - 'files.assignTitle': 'Přiřadit soubor', - 'files.assignPlace': 'Místo', - 'files.assignBooking': 'Rezervace', - 'files.assignTransport': 'Doprava', - 'files.unassigned': 'Nepřiřazeno', - 'files.unlink': 'Zrušit propojení', - 'files.toast.trashed': 'Přesunuto do koše', - 'files.toast.restored': 'Soubor byl obnoven', - 'files.toast.trashEmptied': 'Koš byl vysypán', - 'files.toast.assigned': 'Soubor byl přiřazen', - 'files.toast.assignError': 'Přiřazení se nezdařilo', - 'files.toast.restoreError': 'Obnovení se nezdařilo', - 'files.confirm.permanentDelete': 'Trvale smazat tento soubor? Tuto akci nelze vrátit.', - 'files.confirm.emptyTrash': 'Trvale smazat všechny soubory v koši? Tuto akci nelze vrátit.', - 'files.noteLabel': 'Poznámka', - 'files.notePlaceholder': 'Přidat poznámku...', - - // Balení (Packing) - 'packing.title': 'Seznam věcí', - 'packing.empty': 'Seznam věcí je prázdný', - 'packing.import': 'Importovat', - 'packing.importTitle': 'Importovat seznam', - 'packing.importHint': 'Jedna položka na řádek. Formát: Kategorie, Název, Váha v g (volitelné), Zavazadlo (volitelné), checked/unchecked (volitelné)', - 'packing.importPlaceholder': 'Hygiena, Zubní kartáček\nOblečení, Trička, 200\nDokumenty, Pas, , Příruční zavazadlo\nElektronika, Nabíječka, 50, Kufr, checked', - 'packing.importCsv': 'Načíst CSV/TXT', - 'packing.importAction': 'Importovat {count}', - 'packing.importSuccess': '{count} položek importováno', - 'packing.importError': 'Import se nezdařil', - 'packing.importEmpty': 'Žádné položky k importu', - 'packing.progress': '{packed} z {total} zabaleno ({percent} %)', - 'packing.clearChecked': 'Odstranit {count} hotových', - 'packing.clearCheckedShort': 'Odstranit {count}', - 'packing.suggestions': 'Návrhy', - 'packing.suggestionsTitle': 'Přidat návrhy', - 'packing.allSuggested': 'Všechny návrhy byly přidány', - 'packing.allPacked': 'Vše je zabaleno!', - 'packing.addPlaceholder': 'Přidat novou položku...', - 'packing.categoryPlaceholder': 'Kategorie...', - 'packing.filterAll': 'Vše', - 'packing.filterOpen': 'K zabalení', - 'packing.filterDone': 'Hotovo', - 'packing.emptyTitle': 'Seznam věcí je prázdný', - 'packing.emptyHint': 'Přidejte položky nebo použijte návrhy', - 'packing.emptyFiltered': 'Žádné položky neodpovídají filtru', - 'packing.menuRename': 'Přejmenovat', - 'packing.menuCheckAll': 'Označit vše', - 'packing.menuUncheckAll': 'Odznačit vše', - 'packing.menuDeleteCat': 'Smazat kategorii', - 'packing.noMembers': 'Žádní členové cesty', - 'packing.addItem': 'Přidat položku', - 'packing.addItemPlaceholder': 'Název položky...', - 'packing.addCategory': 'Přidat kategorii', - 'packing.newCategoryPlaceholder': 'Název kategorie (např. Oblečení)', - 'packing.applyTemplate': 'Použít šablonu', - 'packing.template': 'Šablona', - 'packing.templateApplied': '{count} položek přidáno ze šablony', - 'packing.templateError': 'Šablonu se nepodařilo použít', - 'packing.saveAsTemplate': 'Uložit jako šablonu', - 'packing.templateName': 'Název šablony', - 'packing.templateSaved': 'Seznam balení uložen jako šablona', - 'packing.bags': 'Zavazadla', - 'packing.noBag': 'Nepřiřazeno', - 'packing.totalWeight': 'Celková váha', - 'packing.bagName': 'Název zavazadla...', - 'packing.addBag': 'Přidat zavazadlo', - 'packing.changeCategory': 'Změnit kategorii', - 'packing.confirm.clearChecked': 'Opravdu chcete odstranit {count} zabalených položek?', - 'packing.confirm.deleteCat': 'Opravdu chcete smazat kategorii „{name}" s {count} položkami?', - 'packing.defaultCategory': 'Ostatní', - 'packing.toast.saveError': 'Uložení se nezdařilo', - 'packing.toast.deleteError': 'Smazání se nezdařilo', - 'packing.toast.renameError': 'Přejmenování se nezdařilo', - 'packing.toast.addError': 'Přidání se nezdařilo', - - // Návrhy balení (Packing suggestions) - 'packing.suggestions.items': [ - { name: 'Pas', category: 'Dokumenty' }, - { name: 'Občanský průkaz', category: 'Dokumenty' }, - { name: 'Cestovní pojištění', category: 'Dokumenty' }, - { name: 'Letenky', category: 'Dokumenty' }, - { name: 'Platební karta', category: 'Finance' }, - { name: 'Hotovost', category: 'Finance' }, - { name: 'Víza', category: 'Dokumenty' }, - { name: 'Trička', category: 'Oblečení' }, - { name: 'Kalhoty', category: 'Oblečení' }, - { name: 'Spodní prádlo', category: 'Oblečení' }, - { name: 'Ponožky', category: 'Oblečení' }, - { name: 'Bunda', category: 'Oblečení' }, - { name: 'Pyžamo', category: 'Oblečení' }, - { name: 'Plavky', category: 'Oblečení' }, - { name: 'Pláštěnka', category: 'Oblečení' }, - { name: 'Pohodlné boty', category: 'Oblečení' }, - { name: 'Zubní kartáček', category: 'Hygiena' }, - { name: 'Zubní pasta', category: 'Hygiena' }, - { name: 'Šampón', category: 'Hygiena' }, - { name: 'Deodorant', category: 'Hygiena' }, - { name: 'Opalovací krém', category: 'Hygiena' }, - { name: 'Holicí strojek', category: 'Hygiena' }, - { name: 'Nabíječka', category: 'Elektronika' }, - { name: 'Powerbanka', category: 'Elektronika' }, - { name: 'Sluchátka', category: 'Elektronika' }, - { name: 'Cestovní adaptér', category: 'Elektronika' }, - { name: 'Fotoaparát', category: 'Elektronika' }, - { name: 'Léky proti bolesti', category: 'Zdraví' }, - { name: 'Náplasti', category: 'Zdraví' }, - { name: 'Dezinfekce', category: 'Zdraví' }, - ], - - // Členové / Sdílení (Members) - 'members.shareTrip': 'Sdílet cestu', - 'members.inviteUser': 'Pozvat uživatele', - 'members.selectUser': 'Vyberte uživatele…', - 'members.invite': 'Pozvat', - 'members.allHaveAccess': 'Všichni uživatelé již mají přístup.', - 'members.access': 'Přístup', - 'members.person': 'osoba', - 'members.persons': 'osob', - 'members.you': 'vy', - 'members.owner': 'Vlastník', - 'members.leaveTrip': 'Opustit cestu', - 'members.removeAccess': 'Odebrat přístup', - 'members.confirmLeave': 'Opustit cestu? Ztratíte přístup.', - 'members.confirmRemove': 'Odebrat přístup tomuto uživateli?', - 'members.loadError': 'Nepodařilo se načíst členy', - 'members.added': 'přidán/a', - 'members.addError': 'Nepodařilo se přidat', - 'members.removed': 'Člen odebrán', - 'members.removeError': 'Nepodařilo se odebrat', - - // Kategorie (Admin) - 'categories.title': 'Kategorie', - 'categories.subtitle': 'Správa kategorií pro místa', - 'categories.new': 'Nová kategorie', - 'categories.empty': 'Zatím žádné kategorie', - 'categories.namePlaceholder': 'Název kategorie', - 'categories.icon': 'Ikona', - 'categories.color': 'Barva', - 'categories.customColor': 'Vybrat vlastní barvu', - 'categories.preview': 'Náhled', - 'categories.defaultName': 'Kategorie', - 'categories.update': 'Aktualizovat', - 'categories.create': 'Vytvořit', - 'categories.confirm.delete': 'Smazat kategorii? Místa v této kategorii nebudou smazána.', - 'categories.toast.loadError': 'Nepodařilo se načíst kategorie', - 'categories.toast.nameRequired': 'Prosím zadejte název', - 'categories.toast.updated': 'Kategorie aktualizována', - 'categories.toast.created': 'Kategorie vytvořena', - 'categories.toast.saveError': 'Uložení se nezdařilo', - 'categories.toast.deleted': 'Kategorie smazána', - 'categories.toast.deleteError': 'Smazání se nezdařilo', - - // Zálohování (Backup) - 'backup.title': 'Záloha dat', - 'backup.subtitle': 'Databáze a všechny nahrané soubory', - 'backup.refresh': 'Obnovit', - 'backup.upload': 'Nahrát zálohu', - 'backup.uploading': 'Nahrávání…', - 'backup.create': 'Vytvořit zálohu', - 'backup.creating': 'Vytváření…', - 'backup.empty': 'Zatím žádné zálohy', - 'backup.createFirst': 'Vytvořit první zálohu', - 'backup.download': 'Stáhnout', - 'backup.restore': 'Obnovit', - 'backup.confirm.restore': 'Obnovit zálohu „{name}"?\n\nVšechna aktuální data budou nahrazena zálohou.', - 'backup.confirm.uploadRestore': 'Nahrát a obnovit zálohu „{name}"?\n\nVšechna aktuální data budou přepsána.', - 'backup.confirm.delete': 'Smazat zálohu „{name}"?', - 'backup.toast.loadError': 'Nepodařilo se načíst zálohy', - 'backup.toast.created': 'Záloha byla úspěšně vytvořena', - 'backup.toast.createError': 'Nepodařilo se vytvořit zálohu', - 'backup.toast.restored': 'Záloha obnovena. Stránka se znovu načte…', - 'backup.toast.restoreError': 'Obnovení se nezdařilo', - 'backup.toast.uploadError': 'Nahrávání se nezdařilo', - 'backup.toast.deleted': 'Záloha smazána', - 'backup.toast.deleteError': 'Smazání se nezdařilo', - 'backup.toast.downloadError': 'Stahování se nezdařilo', - 'backup.toast.settingsSaved': 'Nastavení automatického zálohování uloženo', - 'backup.toast.settingsError': 'Nepodařilo se uložit nastavení', - 'backup.auto.title': 'Automatické zálohování', - 'backup.auto.subtitle': 'Automatické zálohování podle plánu', - 'backup.auto.enable': 'Povolit automatické zálohování', - 'backup.auto.enableHint': 'Zálohy budou vytvářeny automaticky podle zvoleného plánu', - 'backup.auto.interval': 'Interval', - 'backup.auto.hour': 'Spustit v hodinu', - 'backup.auto.hourHint': 'Místní čas serveru (formát {format})', - 'backup.auto.dayOfWeek': 'Den v týdnu', - 'backup.auto.dayOfMonth': 'Den v měsíci', - 'backup.auto.dayOfMonthHint': 'Omezeno na 1–28 pro kompatibilitu se všemi měsíci', - 'backup.auto.scheduleSummary': 'Plán', - 'backup.auto.summaryDaily': 'Každý den v {hour}:00', - 'backup.auto.summaryWeekly': 'Každý {day} v {hour}:00', - 'backup.auto.summaryMonthly': '{day}. každého měsíce v {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Automatické zálohování je konfigurováno přes Docker proměnné prostředí. Pro změnu nastavení aktualizujte docker-compose.yml a restartujte kontejner.', - 'backup.auto.copyEnv': 'Zkopírovat Docker proměnné', - 'backup.auto.envCopied': 'Docker proměnné prostředí zkopírovány do schránky', - 'backup.auto.keepLabel': 'Smazat staré zálohy po', - 'backup.dow.sunday': 'Ne', - 'backup.dow.monday': 'Po', - 'backup.dow.tuesday': 'Út', - 'backup.dow.wednesday': 'St', - 'backup.dow.thursday': 'Čt', - 'backup.dow.friday': 'Pá', - 'backup.dow.saturday': 'So', - 'backup.interval.hourly': 'Každou hodinu', - 'backup.interval.daily': 'Denně', - 'backup.interval.weekly': 'Týdně', - 'backup.interval.monthly': 'Měsíčně', - 'backup.keep.1day': '1 den', - 'backup.keep.3days': '3 dny', - 'backup.keep.7days': '7 dní', - 'backup.keep.14days': '14 dní', - 'backup.keep.30days': '30 dní', - 'backup.keep.forever': 'Uchovávat navždy', - - // Fotky - 'photos.title': 'Fotografie', - 'photos.subtitle': '{count} fotek pro {trip}', - 'photos.dropHere': 'Přetáhněte fotografie sem...', - 'photos.dropHereActive': 'Přetáhněte fotografie sem', - 'photos.captionForAll': 'Popisek (pro všechny)', - 'photos.captionPlaceholder': 'Volitelný popisek...', - 'photos.addCaption': 'Přidat popisek...', - 'photos.allDays': 'Všechny dny', - 'photos.noPhotos': 'Zatím žádné fotky', - 'photos.uploadHint': 'Nahrajte své cestovní fotky', - 'photos.clickToSelect': 'nebo klikněte pro výběr', - 'photos.linkPlace': 'Propojit s místem', - 'photos.noPlace': 'Žádné místo', - 'photos.uploadN': 'Nahrát {n} fotek', - 'photos.linkDay': 'Propojit den', - 'photos.noDay': 'Žádný den', - 'photos.dayLabel': 'Den {number}', - 'photos.photoSelected': 'Fotografie vybrána', - 'photos.photosSelected': 'Fotografie vybrány', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · až 30 fotografií', - - // Obnovení zálohy - 'backup.restoreConfirmTitle': 'Obnovit zálohu?', - 'backup.restoreWarning': 'Všechna aktuální data (cesty, místa, uživatelé, nahrané soubory) budou trvale nahrazena zálohou. Tuto akci nelze vrátit.', - 'backup.restoreTip': 'Tip: Před obnovením vytvořte zálohu aktuálního stavu.', - 'backup.restoreConfirm': 'Ano, obnovit', - - // PDF - 'pdf.travelPlan': 'Cestovní plán', - 'pdf.planned': 'Naplánováno', - 'pdf.costLabel': 'Náklady EUR', - 'pdf.preview': 'Náhled PDF', - 'pdf.saveAsPdf': 'Uložit jako PDF', - - // Plánovač (Planner) - 'planner.places': 'Místa', - 'planner.bookings': 'Rezervace', - 'planner.packingList': 'Seznam věcí', - 'planner.documents': 'Dokumenty', - 'planner.dayPlan': 'Denní plán', - 'planner.reservations': 'Rezervace', - 'planner.minTwoPlaces': 'Potřebujete alespoň 2 místa se souřadnicemi', - 'planner.noGeoPlaces': 'Žádná místa se souřadnicemi nejsou k dispozici', - 'planner.routeCalculated': 'Trasa vypočtena', - 'planner.routeCalcFailed': 'Trasu se nepodařilo vypočítat', - 'planner.routeError': 'Chyba při výpočtu trasy', - 'planner.icsExportFailed': 'Export ICS se nezdařil', - 'planner.routeOptimized': 'Trasa optimalizována', - 'planner.reservationUpdated': 'Rezervace aktualizována', - 'planner.reservationAdded': 'Rezervace přidána', - 'planner.confirmDeleteReservation': 'Smazat rezervaci?', - 'planner.reservationDeleted': 'Rezervace smazána', - 'planner.days': 'Dny', - 'planner.allPlaces': 'Všechna místa', - 'planner.totalPlaces': 'Celkem {n} míst', - 'planner.noDaysPlanned': 'Zatím nejsou naplánovány žádné dny', - 'planner.editTrip': 'Upravit cestu \u2192', - 'planner.placeOne': '1 místo', - 'planner.placeN': '{n} míst', - 'planner.addNote': 'Přidat poznámku', - 'planner.noEntries': 'Pro tento den nejsou žádné záznamy', - 'planner.addPlace': 'Přidat místo/aktivitu', - 'planner.addPlaceShort': '+ Přidat místo/aktivitu', - 'planner.resPending': 'Rezervace čeká · ', - 'planner.resConfirmed': 'Rezervace potvrzena · ', - 'planner.notePlaceholder': 'Poznámka\u2026', - 'planner.noteTimePlaceholder': 'Čas (volitelné)', - 'planner.noteExamplePlaceholder': 'např. S3 ve 14:30 z hlavního nádraží, trajekt z přístaviště 7, přestávka na oběd\u2026', - 'planner.totalCost': 'Celkové náklady', - 'planner.searchPlaces': 'Hledat místa\u2026', - 'planner.allCategories': 'Všechny kategorie', - 'planner.noPlacesFound': 'Žádná místa nenalezena', - 'planner.addFirstPlace': 'Přidat první místo', - 'planner.noReservations': 'Žádné rezervace', - 'planner.addFirstReservation': 'Přidat první rezervaci', - 'planner.new': 'Nový', - 'planner.addToDay': '+ Den', - 'planner.calculating': 'Počítání\u2026', - 'planner.route': 'Trasa', - 'planner.optimize': 'Optimalizovat', - 'planner.openGoogleMaps': 'Otevřít v Google Mapách', - 'planner.selectDayHint': 'Vyberte den ze seznamu vlevo pro zobrazení denního plánu', - 'planner.noPlacesForDay': 'Zatím žádná místa pro tento den', - 'planner.addPlacesLink': 'Přidat místa \u2192', - 'planner.minTotal': 'min. celkem', - 'planner.noReservation': 'Žádná rezervace', - 'planner.removeFromDay': 'Odebrat ze dne', - 'planner.addToThisDay': 'Přidat ke dni', - 'planner.overview': 'Přehled', - 'planner.noDays': 'Zatím žádné dny', - 'planner.editTripToAddDays': 'Upravte cestu pro přidání dnů', - 'planner.dayCount': '{n} dní', - 'planner.clickToUnlock': 'Klikněte pro odemčení', - 'planner.keepPosition': 'Zachovat pozici při optimalizaci trasy', - 'planner.dayDetails': 'Podrobnosti dne', - 'planner.dayN': 'Den {n}', - - // Statistiky (Dashboard Stats) - 'stats.countries': 'Země', - 'stats.cities': 'Města', - 'stats.trips': 'Cesty', - 'stats.places': 'Místa', - 'stats.worldProgress': 'Průzkum světa', - 'stats.visited': 'navštíveno', - 'stats.remaining': 'zbývá', - 'stats.visitedCountries': 'Navštívené země', - - // Detail dne (Day Detail Panel) - 'day.precipProb': 'Pravděpodobnost srážek', - 'day.precipitation': 'Srážky', - 'day.wind': 'Vítr', - 'day.sunrise': 'Východ slunce', - 'day.sunset': 'Západ slunce', - 'day.hourlyForecast': 'Hodinová předpověď', - 'day.climateHint': 'Historické průměry — reálná předpověď je k dispozici do 16 dnů od tohoto data.', - 'day.noWeather': 'Nejsou k dispozici žádná data o počasí. Přidejte místo se souřadnicemi.', - 'day.overview': 'Denní přehled', - 'day.accommodation': 'Ubytování', - 'day.addAccommodation': 'Přidat ubytování', - 'day.hotelDayRange': 'Použít na dny', - 'day.noPlacesForHotel': 'Nejprve přidejte místa ke své cestě', - 'day.allDays': 'Vše', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Do', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Potvrzení', - 'day.editAccommodation': 'Upravit ubytování', - 'day.reservations': 'Rezervace', - - // Fotky / Immich - 'memories.title': 'Fotky', - 'memories.notConnected': 'Immich není připojen', - 'memories.notConnectedHint': 'Připojte svoji instanci Immich v Nastavení, abyste zde viděli fotky z cest.', - 'memories.notConnectedMultipleHint': 'Pro přidání fotek k tomuto výletu připojte v Nastavení jednoho z těchto poskytovatelů fotek: {provider_names}.', - 'memories.noDates': 'Přidejte data k cestě pro načtení fotek.', - 'memories.noPhotos': 'Nenalezeny žádné fotky', - 'memories.noPhotosHint': 'V Immich nebyly nalezeny žádné fotky pro období této cesty.', - 'memories.photosFound': 'fotek', - 'memories.fromOthers': 'od ostatních', - 'memories.sharePhotos': 'Sdílet fotky', - 'memories.sharing': 'Sdílení', - 'memories.reviewTitle': 'Zkontrolujte své fotky', - 'memories.reviewHint': 'Klikněte na fotky pro vyloučení ze sdílení.', - 'memories.shareCount': 'Sdílet {count} fotek', - 'memories.providerUrl': 'URL serveru', - 'memories.providerApiKey': 'API klíč', - 'memories.providerUsername': 'Uživatelské jméno', - 'memories.providerPassword': 'Heslo', - 'memories.providerOTP': 'MFA kód (pokud je povoleno)', - 'memories.skipSSLVerification': 'Přeskočit ověření SSL certifikátu', - 'memories.immichAutoUpload': 'Zrcadlit fotky journey při nahrávání také do Immich', - 'memories.providerUrlHintSynology': 'Zahrňte cestu aplikace Photos do URL, např. https://nas:5001/photo', - 'memories.testConnection': 'Otestovat připojení', - 'memories.testShort': 'Otestovat', - 'memories.testFirst': 'Nejprve otestujte připojení', - 'memories.connected': 'Připojeno', - 'memories.disconnected': 'Nepřipojeno', - 'memories.connectionSuccess': 'Připojeno k Immich', - 'memories.connectionError': 'Nepodařilo se připojit k Immich', - 'memories.saved': 'Nastavení {provider_name} uloženo', - 'memories.providerDisconnectedBanner': 'Vaše připojení k {provider_name} bylo ztraceno. Obnovte připojení v Nastavení pro zobrazení fotek.', - 'memories.saveError': 'Nepodařilo se uložit nastavení {provider_name}', - 'memories.addPhotos': 'Přidat fotky', - 'memories.linkAlbum': 'Propojit album', - 'memories.selectAlbum': 'Vybrat album z Immich', - 'memories.selectAlbumMultiple': 'Vybrat album', - 'memories.noAlbums': 'Žádná alba nenalezena', - 'memories.syncAlbum': 'Synchronizovat album', - 'memories.unlinkAlbum': 'Odpojit', - 'memories.photos': 'fotek', - 'memories.selectPhotos': 'Vybrat fotky z Immich', - 'memories.selectPhotosMultiple': 'Vybrat fotky', - 'memories.selectHint': 'Klepněte na fotky pro jejich výběr.', - 'memories.selected': 'vybráno', - 'memories.addSelected': 'Přidat {count} fotek', - 'memories.alreadyAdded': 'Přidáno', - 'memories.private': 'Soukromé', - 'memories.stopSharing': 'Zastavit sdílení', - 'memories.oldest': 'Nejstarší', - 'memories.newest': 'Nejnovější', - 'memories.allLocations': 'Všechna místa', - 'memories.tripDates': 'Data cesty', - 'memories.allPhotos': 'Všechny fotky', - 'memories.confirmShareTitle': 'Sdílet se členy cesty?', - 'memories.confirmShareHint': '{count} fotek bude viditelných pro všechny členy této cesty. Jednotlivé fotky můžete později nastavit jako soukromé.', - 'memories.confirmShareButton': 'Sdílet fotky', - - // Spolupráce (Collab) - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Poznámky', - 'collab.tabs.polls': 'Hlasování', - 'collab.whatsNext.title': 'Co následuje', - 'collab.whatsNext.today': 'Dnes', - 'collab.whatsNext.tomorrow': 'Zítra', - 'collab.whatsNext.empty': 'Žádné nadcházející aktivity', - 'collab.whatsNext.until': 'do', - 'collab.whatsNext.emptyHint': 'Aktivity s časem se zde zobrazí', - 'collab.chat.send': 'Odeslat', - 'collab.chat.placeholder': 'Napište zprávu...', - 'collab.chat.empty': 'Začněte konverzaci', - 'collab.chat.emptyHint': 'Zprávy jsou sdíleny se všemi členy cesty', - 'collab.chat.emptyDesc': 'Sdílejte nápady, plány a novinky se svou cestovatelskou skupinou', - 'collab.chat.today': 'Dnes', - 'collab.chat.yesterday': 'Včera', - 'collab.chat.deletedMessage': 'smazal zprávu', - 'collab.chat.reply': 'Odpovědět', - 'collab.chat.loadMore': 'Načíst starší zprávy', - 'collab.chat.justNow': 'právě teď', - 'collab.chat.minutesAgo': 'před {n} min', - 'collab.chat.hoursAgo': 'před {n} h', - 'collab.notes.title': 'Poznámky', - 'collab.notes.new': 'Nová poznámka', - 'collab.notes.empty': 'Zatím žádné poznámky', - 'collab.notes.emptyHint': 'Začněte zapisovat nápady a plány', - 'collab.notes.all': 'Vše', - 'collab.notes.titlePlaceholder': 'Poznámka...', - 'collab.notes.noCategory': 'Bez kategorie', - 'collab.notes.color': 'Barva', - 'collab.notes.save': 'Uložit', - 'collab.notes.cancel': 'Zrušit', - 'collab.notes.edit': 'Upravit', - 'collab.notes.delete': 'Smazat', - 'collab.notes.pin': 'Připnout', - 'collab.notes.unpin': 'Odepnout', - 'collab.notes.daysAgo': 'před {n} dny', - 'collab.notes.categorySettings': 'Spravovat kategorie', - 'collab.notes.create': 'Vytvořit', - 'collab.notes.website': 'Webové stránky', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Přiložit soubory', - 'collab.notes.noCategoriesYet': 'Zatím žádné kategorie', - 'collab.notes.emptyDesc': 'Vytvořte poznámku a začněte', - 'collab.notes.contentPlaceholder': 'Napište něco...', - 'collab.notes.categoryPlaceholder': 'Kategorie', - 'collab.notes.newCategory': 'Nová kategorie...', - 'collab.notes.category': 'Kategorie', - 'collab.polls.title': 'Hlasování', - 'collab.polls.new': 'Nové hlasování', - 'collab.polls.empty': 'Zatím žádná hlasování', - 'collab.polls.emptyHint': 'Zeptejte se skupiny a hlasujte společně', - 'collab.polls.question': 'Otázka', - 'collab.polls.questionPlaceholder': 'Co bychom měli dělat?', - 'collab.polls.addOption': '+ Přidat možnost', - 'collab.polls.optionPlaceholder': 'Možnost {n}', - 'collab.polls.create': 'Vytvořit hlasování', - 'collab.polls.close': 'Uzavřít', - 'collab.polls.closed': 'Uzavřeno', - 'collab.polls.votes': '{n} hlasů', - 'collab.polls.vote': '{n} hlas', - 'collab.polls.multipleChoice': 'Více možností', - 'collab.polls.multiChoice': 'Více možností', - 'collab.polls.deadline': 'Termín', - 'collab.polls.option': 'Možnost', - 'collab.polls.options': 'Možnosti', - 'collab.polls.delete': 'Smazat', - 'collab.polls.closedSection': 'Uzavřené', - - // Permissions - 'admin.tabs.permissions': 'Oprávnění', - 'perm.title': 'Nastavení oprávnění', - 'perm.subtitle': 'Určete, kdo může provádět akce v aplikaci', - 'perm.saved': 'Nastavení oprávnění uloženo', - 'perm.resetDefaults': 'Obnovit výchozí', - 'perm.customized': 'upraveno', - 'perm.level.admin': 'Pouze administrátor', - 'perm.level.tripOwner': 'Vlastník výletu', - 'perm.level.tripMember': 'Členové výletu', - 'perm.level.everybody': 'Všichni', - 'perm.cat.trip': 'Správa výletů', - 'perm.cat.members': 'Správa členů', - 'perm.cat.files': 'Soubory', - 'perm.cat.content': 'Obsah a plán', - 'perm.cat.extras': 'Rozpočet, balení a spolupráce', - 'perm.action.trip_create': 'Vytvářet výlety', - 'perm.action.trip_edit': 'Upravit detaily výletu', - 'perm.action.trip_delete': 'Smazat výlety', - 'perm.action.trip_archive': 'Archivovat / odarchivovat výlety', - 'perm.action.trip_cover_upload': 'Nahrát titulní obrázek', - 'perm.action.member_manage': 'Přidat / odebrat členy', - 'perm.action.file_upload': 'Nahrát soubory', - 'perm.action.file_edit': 'Upravit metadata souborů', - 'perm.action.file_delete': 'Smazat soubory', - 'perm.action.place_edit': 'Přidat / upravit / smazat místa', - 'perm.action.day_edit': 'Upravit dny, poznámky a přiřazení', - 'perm.action.reservation_edit': 'Spravovat rezervace', - 'perm.action.budget_edit': 'Spravovat rozpočet', - 'perm.action.packing_edit': 'Spravovat seznamy balení', - 'perm.action.collab_edit': 'Spolupráce (poznámky, hlasování, chat)', - 'perm.action.share_manage': 'Spravovat odkazy ke sdílení', - 'perm.actionHint.trip_create': 'Kdo může vytvářet nové výlety', - 'perm.actionHint.trip_edit': 'Kdo může měnit název, data, popis a měnu výletu', - 'perm.actionHint.trip_delete': 'Kdo může trvale smazat výlet', - 'perm.actionHint.trip_archive': 'Kdo může archivovat nebo odarchivovat výlet', - 'perm.actionHint.trip_cover_upload': 'Kdo může nahrát nebo změnit titulní obrázek', - 'perm.actionHint.member_manage': 'Kdo může pozvat nebo odebrat členy výletu', - 'perm.actionHint.file_upload': 'Kdo může nahrávat soubory k výletu', - 'perm.actionHint.file_edit': 'Kdo může upravovat popisy a odkazy souborů', - 'perm.actionHint.file_delete': 'Kdo může přesunout soubory do koše nebo je trvale smazat', - 'perm.actionHint.place_edit': 'Kdo může přidávat, upravovat nebo mazat místa', - 'perm.actionHint.day_edit': 'Kdo může upravovat dny, poznámky ke dnům a přiřazení míst', - 'perm.actionHint.reservation_edit': 'Kdo může vytvářet, upravovat nebo mazat rezervace', - 'perm.actionHint.budget_edit': 'Kdo může vytvářet, upravovat nebo mazat položky rozpočtu', - 'perm.actionHint.packing_edit': 'Kdo může spravovat položky balení a tašky', - 'perm.actionHint.collab_edit': 'Kdo může vytvářet poznámky, hlasování a posílat zprávy', - 'perm.actionHint.share_manage': 'Kdo může vytvářet nebo mazat veřejné odkazy ke sdílení', - // Undo - 'undo.button': 'Zpět', - 'undo.tooltip': 'Zpět: {action}', - 'undo.assignPlace': 'Místo přiřazeno ke dni', - 'undo.removeAssignment': 'Místo odebráno ze dne', - 'undo.reorder': 'Místa přeseřazena', - 'undo.optimize': 'Trasa optimalizována', - 'undo.deletePlace': 'Místo smazáno', - 'undo.deletePlaces': 'Místa smazána', - 'undo.moveDay': 'Místo přesunuto na jiný den', - 'undo.lock': 'Zámek místa přepnut', - 'undo.importGpx': 'Import GPX', - 'undo.importKeyholeMarkup': 'Import KMZ/KML', - 'undo.importGoogleList': 'Import z Google Maps', - 'undo.importNaverList': 'Import z Naver Maps', - - // Notifications - 'notifications.title': 'Oznámení', - 'notifications.markAllRead': 'Označit vše jako přečtené', - 'notifications.deleteAll': 'Smazat vše', - 'notifications.showAll': 'Zobrazit všechna oznámení', - 'notifications.empty': 'Žádná oznámení', - 'notifications.emptyDescription': 'Vše máte přečteno!', - 'notifications.all': 'Vše', - 'notifications.unreadOnly': 'Nepřečtené', - 'notifications.markRead': 'Označit jako přečtené', - 'notifications.markUnread': 'Označit jako nepřečtené', - 'notifications.delete': 'Smazat', - 'notifications.system': 'Systém', - 'notifications.synologySessionCleared.title': 'Synology Photos odpojeno', - 'notifications.synologySessionCleared.text': 'Váš server nebo účet se změnil — přejděte do Nastavení a znovu otestujte připojení.', - 'settings.mustChangePassword': 'Před pokračováním musíte změnit heslo.', - 'atlas.searchCountry': 'Hledat zemi...', - 'memories.error.loadAlbums': 'Načtení alb se nezdařilo', - 'memories.error.linkAlbum': 'Propojení alba se nezdařilo', - 'memories.error.unlinkAlbum': 'Odpojení alba se nezdařilo', - 'memories.error.syncAlbum': 'Synchronizace alba se nezdařila', - 'memories.error.loadPhotos': 'Načtení fotek se nezdařilo', - 'memories.error.addPhotos': 'Přidání fotek se nezdařilo', - 'memories.error.removePhoto': 'Odebrání fotky se nezdařilo', - 'memories.error.toggleSharing': 'Aktualizace sdílení se nezdařila', - 'undo.addPlace': 'Místo přidáno', - 'undo.done': 'Vráceno zpět: {action}', - 'notifications.test.title': 'Testovací oznámení od {actor}', - 'notifications.test.text': 'Toto je jednoduché testovací oznámení.', - 'notifications.test.booleanTitle': '{actor} žádá o vaše schválení', - 'notifications.test.booleanText': 'Testovací oznámení s volbou.', - 'notifications.test.accept': 'Schválit', - 'notifications.test.decline': 'Odmítnout', - 'notifications.test.navigateTitle': 'Podívejte se na toto', - 'notifications.test.navigateText': 'Testovací navigační oznámení.', - 'notifications.test.goThere': 'Přejít tam', - 'notifications.test.adminTitle': 'Hromadná zpráva pro správce', - 'notifications.test.adminText': '{actor} odeslal testovací oznámení všem správcům.', - 'notifications.test.tripTitle': '{actor} přispěl do vašeho výletu', - 'notifications.test.tripText': 'Testovací oznámení pro výlet "{trip}".', - - // Todo - 'todo.subtab.packing': 'Balicí seznam', - 'todo.subtab.todo': 'Úkoly', - 'todo.completed': 'dokončeno', - 'todo.filter.all': 'Vše', - 'todo.filter.open': 'Otevřené', - 'todo.filter.done': 'Hotové', - 'todo.uncategorized': 'Bez kategorie', - 'todo.namePlaceholder': 'Název úkolu', - 'todo.descriptionPlaceholder': 'Popis (volitelné)', - 'todo.unassigned': 'Nepřiřazeno', - 'todo.noCategory': 'Bez kategorie', - 'todo.hasDescription': 'Má popis', - 'todo.addItem': 'Přidat nový úkol', - 'todo.sidebar.sortBy': 'Řadit podle', - 'todo.priority': 'Priorita', - 'todo.newCategoryLabel': 'nová', - 'budget.categoriesLabel': 'kategorie', - 'todo.newCategory': 'Název kategorie', - 'todo.addCategory': 'Přidat kategorii', - 'todo.newItem': 'Nový úkol', - 'todo.empty': 'Zatím žádné úkoly. Přidejte úkol a začněte!', - 'todo.filter.my': 'Moje úkoly', - 'todo.filter.overdue': 'Po termínu', - 'todo.sidebar.tasks': 'Úkoly', - 'todo.sidebar.categories': 'Kategorie', - 'todo.detail.title': 'Úkol', - 'todo.detail.description': 'Popis', - 'todo.detail.category': 'Kategorie', - 'todo.detail.dueDate': 'Termín splnění', - 'todo.detail.assignedTo': 'Přiřazeno', - 'todo.detail.delete': 'Smazat', - 'todo.detail.save': 'Uložit změny', - 'todo.detail.create': 'Vytvořit úkol', - 'todo.detail.priority': 'Priorita', - 'todo.detail.noPriority': 'Žádná', - 'todo.sortByPrio': 'Priorita', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nová verze k dispozici', - 'settings.notificationPreferences.noChannels': 'Nejsou nakonfigurovány žádné kanály oznámení. Požádejte správce o nastavení e-mailových nebo webhook oznámení.', - 'settings.webhookUrl.label': 'URL webhooku', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Zadejte URL vašeho Discord, Slack nebo vlastního webhooku pro příjem oznámení.', - 'settings.webhookUrl.saved': 'URL webhooku uložena', - 'settings.webhookUrl.test': 'Otestovat', - 'settings.webhookUrl.testSuccess': 'Testovací webhook byl úspěšně odeslán', - 'settings.webhookUrl.testFailed': 'Testovací webhook selhal', - 'settings.ntfyUrl.topicLabel': 'Téma Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL serveru Ntfy (volitelné)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Zadejte své téma Ntfy pro příjem push notifikací. Pole serveru ponechte prázdné pro použití výchozího nastavení správce.', - 'settings.ntfyUrl.tokenLabel': 'Přístupový token (volitelné)', - 'settings.ntfyUrl.tokenHint': 'Vyžadováno pro témata chráněná heslem.', - 'settings.ntfyUrl.saved': 'Nastavení Ntfy uloženo', - 'settings.ntfyUrl.test': 'Otestovat', - 'settings.ntfyUrl.testSuccess': 'Testovací notifikace Ntfy byla úspěšně odeslána', - 'settings.ntfyUrl.testFailed': 'Testovací notifikace Ntfy selhala', - 'settings.ntfyUrl.tokenCleared': 'Přístupový token byl vymazán', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'In-app oznámení jsou vždy aktivní a nelze je globálně vypnout.', - 'admin.notifications.adminWebhookPanel.title': 'Admin webhook', - 'admin.notifications.adminWebhookPanel.hint': 'Tento webhook se používá výhradně pro admin oznámení (např. upozornění na verze). Je nezávislý na uživatelských webhooků a odesílá automaticky, pokud je nastavena URL.', - 'admin.notifications.adminWebhookPanel.saved': 'URL admin webhooku uložena', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Testovací webhook byl úspěšně odeslán', - 'admin.notifications.adminWebhookPanel.testFailed': 'Testovací webhook selhal', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook odesílá automaticky, pokud je nastavena URL', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Umožňuje uživatelům nakonfigurovat vlastní témata ntfy pro přijímání push notifikací. Níže nastavte výchozí server pro předvyplnění nastavení uživatelů.', - 'admin.notifications.testNtfy': 'Odeslat testovací Ntfy', - 'admin.notifications.testNtfySuccess': 'Testovací Ntfy bylo úspěšně odesláno', - 'admin.notifications.testNtfyFailed': 'Testovací Ntfy selhalo', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Toto téma Ntfy se používá výhradně pro admin oznámení (např. upozornění na verze). Je nezávislé na tématech uživatelů a odesílá vždy, když je nakonfigurováno.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serveru Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Slouží také jako výchozí server pro ntfy notifikace uživatelů. Ponechte prázdné pro použití ntfy.sh. Uživatelé si to mohou změnit ve vlastním nastavení.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Přístupový token (volitelné)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Přístupový token admina byl vymazán', - 'admin.notifications.adminNtfyPanel.saved': 'Nastavení admin Ntfy uloženo', - 'admin.notifications.adminNtfyPanel.test': 'Odeslat testovací Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Testovací Ntfy bylo úspěšně odesláno', - 'admin.notifications.adminNtfyPanel.testFailed': 'Testovací Ntfy selhalo', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy odesílá vždy, když je nakonfigurováno téma', - 'admin.notifications.adminNotificationsHint': 'Nastavte, které kanály doručují admin oznámení (např. upozornění na verze). Webhook odesílá automaticky, pokud je nastavena URL admin webhooku.', - 'admin.notifications.tripReminders.title': 'Připomínky výletů', - 'admin.notifications.tripReminders.hint': 'Odešle upozornění před začátkem výletu (vyžaduje nastavené dny připomínky na výletu).', - 'admin.notifications.tripReminders.enabled': 'Připomínky výletů aktivovány', - 'admin.notifications.tripReminders.disabled': 'Připomínky výletů deaktivovány', - 'admin.tabs.notifications': 'Oznámení', - 'notifications.versionAvailable.title': 'Dostupná aktualizace', - 'notifications.versionAvailable.text': 'TREK {version} je nyní k dispozici.', - 'notifications.versionAvailable.button': 'Zobrazit podrobnosti', - 'notif.test.title': '[Test] Oznámení', - 'notif.test.simple.text': 'Toto je jednoduché testovací oznámení.', - 'notif.test.boolean.text': 'Přijmete toto testovací oznámení?', - 'notif.test.navigate.text': 'Klikněte níže pro přechod na přehled.', - - // Notifications - 'notif.trip_invite.title': 'Pozvánka na výlet', - 'notif.trip_invite.text': '{actor} vás pozval na {trip}', - 'notif.booking_change.title': 'Rezervace aktualizována', - 'notif.booking_change.text': '{actor} aktualizoval rezervaci v {trip}', - 'notif.trip_reminder.title': 'Připomínka výletu', - 'notif.trip_reminder.text': 'Váš výlet {trip} se blíží!', - 'notif.todo_due.title': 'Úkol se blíží', - 'notif.todo_due.text': '{todo} ve výletě {trip} má termín {due}', - 'notif.vacay_invite.title': 'Pozvánka Vacay Fusion', - 'notif.vacay_invite.text': '{actor} vás pozval ke spojení dovolenkových plánů', - 'notif.photos_shared.title': 'Fotky sdíleny', - 'notif.photos_shared.text': '{actor} sdílel {count} foto v {trip}', - 'notif.collab_message.title': 'Nová zpráva', - 'notif.collab_message.text': '{actor} poslal zprávu v {trip}', - 'notif.packing_tagged.title': 'Přiřazení balení', - 'notif.packing_tagged.text': '{actor} vás přiřadil k {category} v {trip}', - 'notif.version_available.title': 'Nová verze dostupná', - 'notif.version_available.text': 'TREK {version} je nyní dostupný', - 'notif.action.view_trip': 'Zobrazit výlet', - 'notif.action.view_collab': 'Zobrazit zprávy', - 'notif.action.view_packing': 'Zobrazit balení', - 'notif.action.view_photos': 'Zobrazit fotky', - 'notif.action.view_vacay': 'Zobrazit Vacay', - 'notif.action.view_admin': 'Jít do adminu', - 'notif.action.view': 'Zobrazit', - 'notif.action.accept': 'Přijmout', - 'notif.action.decline': 'Odmítnout', - 'notif.generic.title': 'Oznámení', - 'notif.generic.text': 'Máte nové oznámení', - 'notif.dev.unknown_event.title': '[DEV] Neznámá událost', - 'notif.dev.unknown_event.text': 'Typ události "{event}" není registrován v EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'právě teď', - 'common.hoursAgo': 'před {count} h', - 'common.daysAgo': 'před {count} d', - 'memories.saveRouteNotConfigured': 'Trasa uložení není nakonfigurována pro tohoto poskytovatele', - 'memories.testRouteNotConfigured': 'Testovací trasa není nakonfigurována pro tohoto poskytovatele', - 'memories.fillRequiredFields': 'Prosím vyplňte všechna povinná pole', - 'journey.search.placeholder': 'Hledat cesty…', - 'journey.search.noResults': 'Žádné cesty neodpovídají „{query}"', - 'journey.title': 'Cestovní deník', - 'journey.subtitle': 'Zaznamenávejte své cesty průběžně', - 'journey.new': 'Nový cestovní deník', - 'journey.create': 'Vytvořit', - 'journey.titlePlaceholder': 'Kam jedete?', - 'journey.empty': 'Zatím žádné cestovní deníky', - 'journey.emptyHint': 'Začněte dokumentovat svůj další výlet', - 'journey.deleted': 'Cestovní deník smazán', - 'journey.createError': 'Nepodařilo se vytvořit cestovní deník', - 'journey.deleteError': 'Nepodařilo se smazat cestovní deník', - 'journey.deleteConfirmTitle': 'Smazat', - 'journey.deleteConfirmMessage': 'Smazat „{title}"? Tuto akci nelze vrátit zpět.', - 'journey.deleteConfirmGeneric': 'Opravdu to chcete smazat?', - 'journey.notFound': 'Cestovní deník nenalezen', - 'journey.photos': 'Fotky', - 'journey.timelineEmpty': 'Zatím žádné zastávky', - 'journey.timelineEmptyHint': 'Přidejte odbavení nebo napište záznam do deníku', - 'journey.status.draft': 'Koncept', - 'journey.status.active': 'Aktivní', - 'journey.status.completed': 'Dokončeno', - 'journey.status.upcoming': 'Nadcházející', - 'journey.status.archived': 'Archivováno', - 'journey.checkin.add': 'Odbavit se', - 'journey.checkin.namePlaceholder': 'Název místa', - 'journey.checkin.notesPlaceholder': 'Poznámky (volitelné)', - 'journey.checkin.save': 'Uložit', - 'journey.checkin.error': 'Nepodařilo se uložit odbavení', - 'journey.entry.add': 'Deník', - 'journey.entry.edit': 'Upravit záznam', - 'journey.entry.titlePlaceholder': 'Název (volitelný)', - 'journey.entry.bodyPlaceholder': 'Co se dnes stalo?', - 'journey.entry.save': 'Uložit', - 'journey.entry.error': 'Nepodařilo se uložit záznam', - 'journey.photo.add': 'Fotka', - 'journey.photo.uploadError': 'Nahrávání selhalo', - 'journey.share.share': 'Sdílet', - 'journey.share.public': 'Veřejný', - 'journey.share.linkCopied': 'Veřejný odkaz zkopírován', - 'journey.share.disabled': 'Veřejné sdílení vypnuto', - 'journey.editor.titlePlaceholder': 'Pojmenujte tento okamžik...', - 'journey.editor.bodyPlaceholder': 'Vyprávějte příběh tohoto dne...', - 'journey.editor.placePlaceholder': 'Místo (volitelné)', - 'journey.editor.tagsPlaceholder': 'Tagy: skrytý klenot, nejlepší jídlo, musím se vrátit...', - 'journey.visibility.private': 'Soukromý', - 'journey.visibility.shared': 'Sdílený', - 'journey.visibility.public': 'Veřejný', - 'journey.emptyState.title': 'Váš příběh začíná zde', - 'journey.emptyState.subtitle': 'Odba‍vte se na místě nebo napište svůj první záznam do deníku', - 'journey.frontpage.subtitle': 'Proměňte své cesty v příběhy, na které nikdy nezapomenete', - 'journey.frontpage.createJourney': 'Vytvořit cestovní deník', - 'journey.frontpage.activeJourney': 'Aktivní cestovní deník', - 'journey.frontpage.allJourneys': 'Všechny cestovní deníky', - 'journey.frontpage.journeys': 'cestovní deníky', - 'journey.frontpage.createNew': 'Vytvořit nový cestovní deník', - 'journey.frontpage.createNewSub': 'Vyberte cesty, pište příběhy, sdílejte dobrodružství', - 'journey.frontpage.live': 'Živě', - 'journey.frontpage.synced': 'Synchronizováno', - 'journey.frontpage.continueWriting': 'Pokračovat v psaní', - 'journey.frontpage.updated': 'Aktualizováno {time}', - 'journey.frontpage.suggestionLabel': 'Cesta právě skončila', - 'journey.frontpage.suggestionText': 'Proměňte {title} v cestovní deník', - 'journey.frontpage.dismiss': 'Zavřít', - 'journey.frontpage.journeyName': 'Název cestovního deníku', - 'journey.frontpage.namePlaceholder': 'např. Jihovýchodní Asie 2026', - 'journey.frontpage.selectTrips': 'Vybrat cesty', - 'journey.frontpage.tripsSelected': 'cest vybráno', - 'journey.frontpage.trips': 'cesty', - 'journey.frontpage.placesImported': 'míst bude importováno', - 'journey.frontpage.places': 'místa', - 'journey.detail.backToJourney': 'Zpět na cestovní deník', - 'journey.detail.syncedWithTrips': 'Synchronizováno s cestami', - 'journey.detail.addEntry': 'Přidat záznam', - 'journey.detail.newEntry': 'Nový záznam', - 'journey.detail.editEntry': 'Upravit záznam', - 'journey.detail.noEntries': 'Zatím žádné záznamy', - 'journey.detail.noEntriesHint': 'Přidejte cestu pro začátek s kostrovými záznamy', - 'journey.detail.noPhotos': 'Zatím žádné fotky', - 'journey.detail.noPhotosHint': 'Nahrajte fotky k záznamům nebo procházejte knihovnu Immich/Synology', - 'journey.detail.journeyStats': 'Statistiky cesty', - 'journey.detail.syncedTrips': 'Synchronizované cesty', - 'journey.detail.noTripsLinked': 'Zatím žádné propojené cesty', - 'journey.detail.contributors': 'Přispěvatelé', - 'journey.detail.readMore': 'Číst dále', - 'journey.detail.prosCons': 'Klady a zápory', - 'journey.detail.photos': 'fotky', - 'journey.detail.day': 'Den {number}', - 'journey.detail.places': 'míst', - 'journey.stats.days': 'Dny', - 'journey.stats.cities': 'Města', - 'journey.stats.entries': 'Záznamy', - 'journey.stats.photos': 'Fotky', - 'journey.stats.places': 'Místa', - 'journey.skeletons.show': 'Zobrazit návrhy', - 'journey.skeletons.hide': 'Skrýt návrhy', - 'journey.verdict.lovedIt': 'Skvělé', - 'journey.verdict.couldBeBetter': 'Mohlo by být lepší', - 'journey.synced.places': 'místa', - 'journey.synced.synced': 'synchronizováno', - 'journey.editor.discardChangesConfirm': 'Máte neuložené změny. Zahodit?', - 'journey.editor.uploadFailed': 'Nahrávání fotek selhalo', - 'journey.editor.uploadPhotos': 'Nahrát fotky', - 'journey.editor.uploading': 'Nahrávání...', - 'journey.editor.uploadingProgress': 'Nahrávání {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} z {total} fotek selhalo — uložte znovu pro opakování', - 'journey.editor.fromGallery': 'Z galerie', - 'journey.editor.allPhotosAdded': 'Všechny fotky již přidány', - 'journey.editor.writeStory': 'Napište svůj příběh...', - 'journey.editor.prosCons': 'Klady a zápory', - 'journey.editor.pros': 'Klady', - 'journey.editor.cons': 'Zápory', - 'journey.editor.proPlaceholder': 'Něco skvělého...', - 'journey.editor.conPlaceholder': 'Ne tak skvělé...', - 'journey.editor.addAnother': 'Přidat další', - 'journey.editor.date': 'Datum', - 'journey.editor.location': 'Místo', - 'journey.editor.searchLocation': 'Hledat místo...', - 'journey.editor.mood': 'Nálada', - 'journey.editor.weather': 'Počasí', - 'journey.editor.photoFirst': '1.', - 'journey.editor.makeFirst': 'Nastavit jako 1.', - 'journey.editor.searching': 'Hledání...', - 'journey.mood.amazing': 'Úžasný', - 'journey.mood.good': 'Dobrý', - 'journey.mood.neutral': 'Neutrální', - 'journey.mood.rough': 'Těžký', - 'journey.weather.sunny': 'Slunečno', - 'journey.weather.partly': 'Polojasno', - 'journey.weather.cloudy': 'Zataženo', - 'journey.weather.rainy': 'Deštivo', - 'journey.weather.stormy': 'Bouřlivo', - 'journey.weather.cold': 'Sněžení', - 'journey.trips.linkTrip': 'Propojit cestu', - 'journey.trips.searchTrip': 'Hledat cestu', - 'journey.trips.searchPlaceholder': 'Název cesty nebo cíl...', - 'journey.trips.noTripsAvailable': 'Žádné dostupné cesty', - 'journey.trips.link': 'Propojit', - 'journey.trips.tripLinked': 'Cesta propojena', - 'journey.trips.linkFailed': 'Propojení cesty selhalo', - 'journey.trips.addTrip': 'Přidat cestu', - 'journey.trips.unlinkTrip': 'Odpojit cestu', - 'journey.trips.unlinkMessage': 'Odpojit „{title}"? Všechny synchronizované záznamy a fotky z této cesty budou trvale smazány. Tuto akci nelze vrátit zpět.', - 'journey.trips.unlink': 'Odpojit', - 'journey.trips.tripUnlinked': 'Cesta odpojena', - 'journey.trips.unlinkFailed': 'Odpojení cesty selhalo', - 'journey.trips.noTripsLinkedSettings': 'Žádné propojené cesty', - 'journey.contributors.invite': 'Pozvat přispěvatele', - 'journey.contributors.searchUser': 'Hledat uživatele', - 'journey.contributors.searchPlaceholder': 'Uživatelské jméno nebo e-mail...', - 'journey.contributors.noUsers': 'Žádní uživatelé nenalezeni', - 'journey.contributors.role': 'Role', - 'journey.contributors.added': 'Přispěvatel přidán', - 'journey.contributors.addFailed': 'Přidání přispěvatele selhalo', - 'journey.share.publicShare': 'Veřejné sdílení', - 'journey.share.createLink': 'Vytvořit odkaz ke sdílení', - 'journey.share.linkCreated': 'Odkaz ke sdílení vytvořen', - 'journey.share.createFailed': 'Vytvoření odkazu selhalo', - 'journey.share.copy': 'Kopírovat', - 'journey.share.copied': 'Zkopírováno!', - 'journey.share.timeline': 'Časová osa', - 'journey.share.gallery': 'Galerie', - 'journey.share.map': 'Mapa', - 'journey.share.removeLink': 'Odstranit odkaz ke sdílení', - 'journey.share.linkDeleted': 'Odkaz ke sdílení smazán', - 'journey.share.deleteFailed': 'Smazání selhalo', - 'journey.share.updateFailed': 'Aktualizace selhala', - - // Journey — Invite - 'journey.invite.role': 'Role', - 'journey.invite.viewer': 'Čtenář', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Pozvat', - 'journey.invite.inviting': 'Zveme...', - 'journey.settings.title': 'Nastavení cestovního deníku', - 'journey.settings.coverImage': 'Titulní obrázek', - 'journey.settings.changeCover': 'Změnit obal', - 'journey.settings.addCover': 'Přidat titulní obrázek', - 'journey.settings.name': 'Název', - 'journey.settings.subtitle': 'Podtitul', - 'journey.settings.subtitlePlaceholder': 'např. Thajsko, Vietnam a Kambodža', - 'journey.settings.endJourney': 'Archivovat cestu', - 'journey.settings.reopenJourney': 'Obnovit cestu', - 'journey.settings.archived': 'Cesta archivována', - 'journey.settings.reopened': 'Cesta znovu otevřena', - 'journey.settings.endDescription': 'Skryje odznak Živě. Kdykoli jej lze znovu otevřít.', - 'journey.settings.delete': 'Smazat', - 'journey.settings.deleteJourney': 'Smazat cestovní deník', - 'journey.settings.deleteMessage': 'Smazat „{title}"? Všechny záznamy a fotky budou ztraceny.', - 'journey.settings.saved': 'Nastavení uloženo', - 'journey.settings.saveFailed': 'Uložení selhalo', - 'journey.settings.coverUpdated': 'Obal aktualizován', - 'journey.settings.coverFailed': 'Nahrávání selhalo', - 'journey.settings.failedToDelete': 'Smazání se nezdařilo', - 'journey.entries.deleteTitle': 'Smazat záznam', - 'journey.photosUploaded': '{count} fotografií nahráno', - 'journey.photosUploadFailed': 'Některé fotky se nepodařilo nahrát', - 'journey.photosAdded': '{count} fotografií přidáno', - 'journey.public.notFound': 'Nenalezeno', - 'journey.public.notFoundMessage': 'Tento cestovní deník neexistuje nebo odkaz vypršel.', - 'journey.public.readOnly': 'Pouze ke čtení · Veřejný cestovní deník', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Sdíleno přes', - 'journey.public.madeWith': 'Vytvořeno pomocí', - 'journey.pdf.journeyBook': 'Cestovní kniha', - 'journey.pdf.madeWith': 'Vytvořeno pomocí TREK', - 'journey.pdf.day': 'Den', - 'journey.pdf.theEnd': 'Konec', - 'journey.pdf.saveAsPdf': 'Uložit jako PDF', - 'journey.pdf.pages': 'stran', - 'journey.picker.tripPeriod': 'Období cesty', - 'journey.picker.dateRange': 'Časové období', - 'journey.picker.allPhotos': 'Všechny fotky', - 'journey.picker.albums': 'Alba', - 'journey.picker.selected': 'vybráno', - 'journey.picker.addTo': 'Přidat do', - 'journey.picker.newGallery': 'Nová galerie', - 'journey.picker.selectAll': 'Vybrat vše', - 'journey.picker.deselectAll': 'Zrušit výběr', - 'journey.picker.noAlbums': 'Žádná alba nenalezena', - 'journey.picker.selectDate': 'Vyberte datum', - 'journey.picker.search': 'Hledat', - 'dashboard.greeting.morning': 'Dobré ráno,', - 'dashboard.greeting.afternoon': 'Dobré odpoledne,', - 'dashboard.greeting.evening': 'Dobrý večer,', - 'dashboard.mobile.liveNow': 'Živě', - 'dashboard.mobile.tripProgress': 'Průběh cesty', - 'dashboard.mobile.daysLeft': 'Zbývá {count} dní', - 'dashboard.mobile.places': 'Místa', - 'dashboard.mobile.buddies': 'Spolucestující', - 'dashboard.mobile.newTrip': 'Nová cesta', - 'dashboard.mobile.currency': 'Měna', - 'dashboard.mobile.timezone': 'Časové pásmo', - 'dashboard.mobile.upcomingTrips': 'Nadcházející cesty', - 'dashboard.mobile.yourTrips': 'Vaše cesty', - 'dashboard.mobile.trips': 'cesty', - 'dashboard.mobile.starts': 'Začátek', - 'dashboard.mobile.duration': 'Doba trvání', - 'dashboard.mobile.day': 'den', - 'dashboard.mobile.days': 'dní', - 'dashboard.mobile.ongoing': 'Probíhající', - 'dashboard.mobile.startsToday': 'Začíná dnes', - 'dashboard.mobile.tomorrow': 'Zítra', - 'dashboard.mobile.inDays': 'Za {count} dní', - 'dashboard.mobile.inMonths': 'Za {count} měsíců', - 'dashboard.mobile.completed': 'Dokončeno', - 'dashboard.mobile.currencyConverter': 'Převodník měn', - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Nastavení', - 'nav.bottomAdmin': 'Nastavení správce', - 'nav.bottomLogout': 'Odhlásit se', - 'nav.bottomAdminBadge': 'Správce', - 'dayplan.mobile.addPlace': 'Přidat místo', - 'dayplan.mobile.searchPlaces': 'Hledat místa...', - 'dayplan.mobile.allAssigned': 'Všechna místa přiřazena', - 'dayplan.mobile.noMatch': 'Žádná shoda', - 'dayplan.mobile.createNew': 'Vytvořit nové místo', - 'admin.addons.catalog.journey.name': 'Cestovní deník', - 'admin.addons.catalog.journey.description': 'Sledování cest a cestovní deník s odbaveními, fotkami a denními příběhy', - // OAuth scope groups - 'oauth.scope.group.trips': 'Výlety', - 'oauth.scope.group.places': 'Místa', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Balení', - 'oauth.scope.group.todos': 'Úkoly', - 'oauth.scope.group.budget': 'Rozpočet', - 'oauth.scope.group.reservations': 'Rezervace', - 'oauth.scope.group.collab': 'Spolupráce', - 'oauth.scope.group.notifications': 'Oznámení', - 'oauth.scope.group.vacay': 'Dovolená', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Počasí', - 'oauth.scope.group.journey': 'Cestovní deník', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Zobrazit výlety a itineráře', - 'oauth.scope.trips:read.description': 'Číst výlety, dny, poznámky a členy', - 'oauth.scope.trips:write.label': 'Upravit výlety a itineráře', - 'oauth.scope.trips:write.description': 'Vytvářet a aktualizovat výlety, dny, poznámky a spravovat členy', - 'oauth.scope.trips:delete.label': 'Mazat výlety', - 'oauth.scope.trips:delete.description': 'Trvale smazat celé výlety — tato akce je nevratná', - 'oauth.scope.trips:share.label': 'Spravovat sdílené odkazy', - 'oauth.scope.trips:share.description': 'Vytvářet, aktualizovat a rušit veřejné sdílené odkazy', - 'oauth.scope.places:read.label': 'Zobrazit místa a mapová data', - 'oauth.scope.places:read.description': 'Číst místa, denní přiřazení, štítky a kategorie', - 'oauth.scope.places:write.label': 'Spravovat místa', - 'oauth.scope.places:write.description': 'Vytvářet, aktualizovat a mazat místa, přiřazení a štítky', - 'oauth.scope.atlas:read.label': 'Zobrazit Atlas', - 'oauth.scope.atlas:read.description': 'Číst navštívené země, regiony a seznam přání', - 'oauth.scope.atlas:write.label': 'Spravovat Atlas', - 'oauth.scope.atlas:write.description': 'Označovat navštívené země a regiony, spravovat seznam přání', - 'oauth.scope.packing:read.label': 'Zobrazit seznamy balení', - 'oauth.scope.packing:read.description': 'Číst položky, tašky a přiřazení kategorií', - 'oauth.scope.packing:write.label': 'Spravovat seznamy balení', - 'oauth.scope.packing:write.description': 'Přidávat, aktualizovat, mazat, označovat a řadit položky a tašky', - 'oauth.scope.todos:read.label': 'Zobrazit seznamy úkolů', - 'oauth.scope.todos:read.description': 'Číst úkoly výletu a přiřazení kategorií', - 'oauth.scope.todos:write.label': 'Spravovat seznamy úkolů', - 'oauth.scope.todos:write.description': 'Vytvářet, aktualizovat, označovat, mazat a řadit úkoly', - 'oauth.scope.budget:read.label': 'Zobrazit rozpočet', - 'oauth.scope.budget:read.description': 'Číst položky rozpočtu a přehled výdajů', - 'oauth.scope.budget:write.label': 'Spravovat rozpočet', - 'oauth.scope.budget:write.description': 'Vytvářet, aktualizovat a mazat položky rozpočtu', - 'oauth.scope.reservations:read.label': 'Zobrazit rezervace', - 'oauth.scope.reservations:read.description': 'Číst rezervace a podrobnosti ubytování', - 'oauth.scope.reservations:write.label': 'Spravovat rezervace', - 'oauth.scope.reservations:write.description': 'Vytvářet, aktualizovat, mazat a řadit rezervace', - 'oauth.scope.collab:read.label': 'Zobrazit spolupráci', - 'oauth.scope.collab:read.description': 'Číst poznámky, ankety a zprávy spolupráce', - 'oauth.scope.collab:write.label': 'Spravovat spolupráci', - 'oauth.scope.collab:write.description': 'Vytvářet, aktualizovat a mazat poznámky, ankety a zprávy', - 'oauth.scope.notifications:read.label': 'Zobrazit oznámení', - 'oauth.scope.notifications:read.description': 'Číst oznámení v aplikaci a počty nepřečtených', - 'oauth.scope.notifications:write.label': 'Spravovat oznámení', - 'oauth.scope.notifications:write.description': 'Označovat oznámení jako přečtená a reagovat na ně', - 'oauth.scope.vacay:read.label': 'Zobrazit plány dovolené', - 'oauth.scope.vacay:read.description': 'Číst data plánování dovolené, záznamy a statistiky', - 'oauth.scope.vacay:write.label': 'Spravovat plány dovolené', - 'oauth.scope.vacay:write.description': 'Vytvářet a spravovat záznamy dovolené, svátky a týmové plány', - 'oauth.scope.geo:read.label': 'Mapy a geokódování', - 'oauth.scope.geo:read.description': 'Vyhledávat místa, řešit URL map a zpětně geokódovat souřadnice', - 'oauth.scope.weather:read.label': 'Předpovědi počasí', - 'oauth.scope.weather:read.description': 'Získávat předpovědi počasí pro místa a data výletu', - 'oauth.scope.journey:read.label': 'Zobrazit cestovní deníky', - 'oauth.scope.journey:read.description': 'Číst cestovní deníky, záznamy a seznam přispěvatelů', - 'oauth.scope.journey:write.label': 'Spravovat cestovní deníky', - 'oauth.scope.journey:write.description': 'Vytvářet, aktualizovat a mazat cestovní deníky a jejich záznamy', - 'oauth.scope.journey:share.label': 'Spravovat odkazy na cestovní deníky', - 'oauth.scope.journey:share.description': 'Vytvářet, aktualizovat a rušit veřejné sdílené odkazy na cestovní deníky', - - // System notices - 'system_notice.welcome_v1.title': 'Vítejte v TREK', - 'system_notice.welcome_v1.body': 'Váš kompletní plánovač cest. Vytvářejte itineráře, sdílejte výlety s přáteli a zůstaňte organizovaní — online i offline.', - 'system_notice.welcome_v1.cta_label': 'Naplánovat cestu', - 'system_notice.welcome_v1.hero_alt': 'Malebné cestovní místo s rozhraním TREK', - 'system_notice.welcome_v1.highlight_plan': 'Denní itineráře pro každou cestu', - 'system_notice.welcome_v1.highlight_share': 'Spolupráce s cestovními partnery', - 'system_notice.welcome_v1.highlight_offline': 'Funguje offline na mobilu', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Předchozí oznámení', - 'system_notice.pager.next': 'Další oznámení', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Přejít na oznámení {n}', - 'system_notice.pager.position': 'Oznámení {current} z {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Fotografie přesunuty ve verzi 3.0', - 'system_notice.v3_photos.body': '**Fotografie** v Plánovacím nástroji byly odebrány. Vaše fotografie jsou v bezpečí — TREK nikdy neupravoval vaši knihovnu Immich nebo Synology.\n\nFotografie jsou nyní dostupné v doplňku **Journey**. Journey je volitelný — pokud ještě není k dispozici, požádejte svého správce, aby ho aktivoval v Admin → Doplňky.', - 'system_notice.v3_journey.title': 'Poznejte Journey — cest. denník', - 'system_notice.v3_journey.body': 'Dokumentujte své cesty jako bohaté příběhy s časovnicemi, galeriemi fotek a interaktivními mapami.', - 'system_notice.v3_journey.cta_label': 'Otevřít Journey', - 'system_notice.v3_journey.highlight_timeline': 'Denní časovnice a galerie', - 'system_notice.v3_journey.highlight_photos': 'Import z Immich nebo Synology', - 'system_notice.v3_journey.highlight_share': 'Sdílet veřejně — bez přihlašování', - 'system_notice.v3_journey.highlight_export': 'Export jako PDF fotokniha', - 'system_notice.v3_features.title': 'Další novinky ve verzi 3.0', - 'system_notice.v3_features.body': 'Několik dalších změn, které stojí za pozornost.', - 'system_notice.v3_features.highlight_dashboard': 'Předesign dashboardu mobile-first', - 'system_notice.v3_features.highlight_offline': 'Plný offline režim jako PWA', - 'system_notice.v3_features.highlight_search': 'Autodoplňování vyhledávání míst', - 'system_notice.v3_features.highlight_import': 'Import míst ze souborů KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: aktualizace OAuth 2.1', - 'system_notice.v3_mcp.body': 'Integrace MCP byla kompletně přepracována. OAuth 2.1 je nyní doporučenou metodou ověřování. Statické tokeny (trek_…) jsou zastaralé a budou v budoucí verzi odstraněny.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 doporučeno (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 jemnozrnných oprávnění', - 'system_notice.v3_mcp.highlight_deprecated': 'Statické tokeny trek_ zastaralé', - 'system_notice.v3_mcp.highlight_tools': 'Rozšířená sada nástrojů a promptů', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Osobní slovo ode mě', - 'system_notice.v3_thankyou.body': 'Než budete pokračovat — chci se na chvíli zastavit.\n\nTREK začal jako vedlejší projekt, který jsem vytvořil pro své vlastní cesty. Nikdy jsem si nepředstavoval, že vyroste v něco, čemu 4 000 z vás důvěřuje při plánování svých dobrodružství. Každou hvězdičku, každý issue, každý požadavek na funkci — všechny čtu a právě ony mě drží při životě během pozdních nocí mezi prací na plný úvazek a univerzitou.\n\nChci, abyste věděli: TREK bude vždy open source, vždy self-hosted, vždy váš. Žádné sledování, žádná předplatná, žádné háčky. Jen nástroj vytvořený někým, kdo miluje cestování stejně jako vy.\n\nZvláštní poděkování patří [jubnl](https://github.com/jubnl) — stal ses neuvěřitelným spolupracovníkem. Tolik z toho, co dělá verzi 3.0 skvělou, nese tvůj rukopis. Děkuji, že jsi věřil tomuto projektu, když byl ještě v plenkách.\n\nA každému z vás, kdo nahlásil chybu, přeložil řetězec, sdílel TREK s přítelem nebo ho jednoduše použil k plánování cesty — **děkuji**. Vy jste důvod, proč tohle existuje.\n\nNa mnoho dalších dobrodružství společně.\n\n— Maurice\n\n---\n\n[Přidej se ke komunitě na Discordu](https://discord.gg/7Q6M6jDwzf)\n\nPokud ti TREK zlepšuje cestování, [malá káva](https://ko-fi.com/mauriceboe) vždy pomůže udržet světla rozsvícená.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Vyžadována akce: konflikt uživatelského účtu', - 'system_notice.v3014_whitespace_collision.body': 'Aktualizace 3.0.14 zjistila jeden nebo více konfliktů uživatelského jména nebo e-mailu způsobených mezerami na začátku nebo konci uložených hodnot. Dotčené účty byly automaticky přejmenovány. Zkontrolujte protokoly serveru na řádky začínající **[migration] WHITESPACE COLLISION** a zjistěte, které účty vyžadují kontrolu.', - 'transport.addTransport': 'Přidat dopravu', - 'transport.modalTitle.create': 'Přidat dopravu', - 'transport.modalTitle.edit': 'Upravit dopravu', - 'transport.title': 'Doprava', - 'transport.addManual': 'Ruční doprava', -} - -export default cs - diff --git a/client/src/i18n/translations/de.ts b/client/src/i18n/translations/de.ts deleted file mode 100644 index 5fd4823a..00000000 --- a/client/src/i18n/translations/de.ts +++ /dev/null @@ -1,2377 +0,0 @@ -const de: Record = { - // Allgemein - 'common.save': 'Speichern', - 'common.showMore': 'Mehr anzeigen', - 'common.showLess': 'Weniger anzeigen', - 'common.cancel': 'Abbrechen', - 'common.clear': 'Löschen', - 'common.delete': 'Löschen', - 'common.edit': 'Bearbeiten', - 'common.add': 'Hinzufügen', - 'common.loading': 'Laden...', - 'common.import': 'Importieren', - 'common.select': 'Auswählen', - 'common.selectAll': 'Alle auswählen', - 'common.deselectAll': 'Alle abwählen', - 'common.error': 'Fehler', - 'common.unknownError': 'Unbekannter Fehler', - 'common.tooManyAttempts': 'Zu viele Versuche. Bitte versuchen Sie es später erneut.', - 'common.back': 'Zurück', - 'common.all': 'Alle', - 'common.close': 'Schließen', - 'common.open': 'Öffnen', - 'common.upload': 'Hochladen', - 'common.search': 'Suchen', - 'common.confirm': 'Bestätigen', - 'common.ok': 'OK', - 'common.yes': 'Ja', - 'common.no': 'Nein', - 'common.or': 'oder', - 'common.none': 'Keine', - 'common.date': 'Datum', - 'common.rename': 'Umbenennen', - 'common.discardChanges': 'Änderungen verwerfen', - 'common.discard': 'Verwerfen', - 'common.name': 'Name', - 'common.email': 'E-Mail', - 'common.password': 'Passwort', - 'common.saving': 'Speichern...', - 'common.expand': 'Erweitern', - 'common.collapse': 'Einklappen', - 'common.justNow': 'gerade eben', - 'common.hoursAgo': 'vor {count}h', - 'common.daysAgo': 'vor {count}T', - 'common.saved': 'Gespeichert', - 'trips.reminder': 'Erinnerung', - 'trips.reminderNone': 'Keine', - 'trips.reminderDay': 'Tag', - 'trips.reminderDays': 'Tage', - 'trips.reminderCustom': 'Benutzerdefiniert', - 'trips.memberRemoved': '{username} entfernt', - 'trips.memberRemoveError': 'Entfernen fehlgeschlagen', - 'trips.memberAdded': '{username} hinzugefügt', - 'trips.memberAddError': 'Hinzufügen fehlgeschlagen', - 'trips.reminderDaysBefore': 'Tage vor Abreise', - 'trips.reminderDisabledHint': 'Reiseerinnerungen sind deaktiviert. Aktivieren Sie sie unter Admin > Einstellungen > Benachrichtigungen.', - 'common.update': 'Aktualisieren', - 'common.change': 'Ändern', - 'common.uploading': 'Hochladen…', - 'common.backToPlanning': 'Zurück zur Planung', - 'common.reset': 'Zurücksetzen', - - // Navbar - 'nav.trip': 'Reise', - 'nav.share': 'Teilen', - 'nav.settings': 'Einstellungen', - 'nav.admin': 'Admin', - 'nav.logout': 'Abmelden', - 'nav.lightMode': 'Heller Modus', - 'nav.darkMode': 'Dunkler Modus', - 'nav.autoMode': 'Automatischer Modus', - 'nav.administrator': 'Administrator', - - // Dashboard - 'dashboard.title': 'Meine Reisen', - 'dashboard.subtitle.loading': 'Reisen werden geladen...', - 'dashboard.subtitle.trips': '{count} Reisen ({archived} archiviert)', - 'dashboard.subtitle.empty': 'Starte deine erste Reise', - 'dashboard.subtitle.activeOne': '{count} aktive Reise', - 'dashboard.subtitle.activeMany': '{count} aktive Reisen', - 'dashboard.subtitle.archivedSuffix': ' · {count} archiviert', - 'dashboard.newTrip': 'Neue Reise', - 'dashboard.gridView': 'Kachelansicht', - 'dashboard.listView': 'Listenansicht', - 'dashboard.currency': 'Währung', - 'dashboard.timezone': 'Zeitzonen', - 'dashboard.localTime': 'Lokal', - 'dashboard.timezoneCustomTitle': 'Eigene Zeitzone', - 'dashboard.timezoneCustomLabelPlaceholder': 'Bezeichnung (optional)', - 'dashboard.timezoneCustomTzPlaceholder': 'z.B. America/New_York', - 'dashboard.timezoneCustomAdd': 'Hinzufügen', - 'dashboard.timezoneCustomErrorEmpty': 'Zeitzone eingeben', - 'dashboard.timezoneCustomErrorInvalid': 'Ungültige Zeitzone. Format: Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Bereits hinzugefügt', - 'dashboard.emptyTitle': 'Noch keine Reisen', - 'dashboard.emptyText': 'Erstelle deine erste Reise und beginne mit der Planung von Orten, Tagesabläufen und Packlisten.', - 'dashboard.emptyButton': 'Erste Reise erstellen', - 'dashboard.nextTrip': 'Nächste Reise', - 'dashboard.shared': 'Geteilt', - 'dashboard.sharedBy': 'Geteilt von {name}', - 'dashboard.days': 'Tage', - 'dashboard.places': 'Orte', - 'dashboard.members': 'Reise-Buddies', - 'dashboard.archive': 'Archivieren', - 'dashboard.copyTrip': 'Kopieren', - 'dashboard.copySuffix': 'Kopie', - 'dashboard.restore': 'Wiederherstellen', - 'dashboard.archived': 'Archiviert', - 'dashboard.status.ongoing': 'Laufend', - 'dashboard.status.today': 'Heute', - 'dashboard.status.tomorrow': 'Morgen', - 'dashboard.status.past': 'Vergangen', - 'dashboard.status.daysLeft': 'Noch {count} Tage', - 'dashboard.toast.loadError': 'Fehler beim Laden der Reisen', - 'dashboard.toast.created': 'Reise erfolgreich erstellt!', - 'dashboard.toast.createError': 'Fehler beim Erstellen', - 'dashboard.toast.updated': 'Reise aktualisiert!', - 'dashboard.toast.updateError': 'Fehler beim Aktualisieren', - 'dashboard.toast.deleted': 'Reise gelöscht', - 'dashboard.toast.deleteError': 'Fehler beim Löschen', - 'dashboard.toast.archived': 'Reise archiviert', - 'dashboard.toast.archiveError': 'Fehler beim Archivieren', - 'dashboard.toast.restored': 'Reise wiederhergestellt', - 'dashboard.toast.restoreError': 'Fehler beim Wiederherstellen', - 'dashboard.toast.copied': 'Reise kopiert!', - 'dashboard.toast.copyError': 'Fehler beim Kopieren der Reise', - 'dashboard.confirm.delete': 'Reise "{title}" löschen? Alle Orte und Pläne werden unwiderruflich gelöscht.', - 'dashboard.editTrip': 'Reise bearbeiten', - 'dashboard.createTrip': 'Neue Reise erstellen', - 'dashboard.tripTitle': 'Titel', - 'dashboard.tripTitlePlaceholder': 'z.B. Sommer in Japan', - 'dashboard.tripDescription': 'Beschreibung', - 'dashboard.tripDescriptionPlaceholder': 'Worum geht es bei dieser Reise?', - 'dashboard.startDate': 'Startdatum', - 'dashboard.endDate': 'Enddatum', - 'dashboard.dayCount': 'Anzahl Tage', - 'dashboard.dayCountHint': 'Wie viele Tage geplant werden sollen, wenn kein Reisezeitraum gesetzt ist.', - 'dashboard.noDateHint': 'Kein Datum gesetzt — es werden 7 Standardtage erstellt. Du kannst das jederzeit ändern.', - 'dashboard.coverImage': 'Titelbild', - 'dashboard.addCoverImage': 'Titelbild hinzufügen (oder per Drag & Drop)', - 'dashboard.addMembers': 'Reisebegleiter', - 'dashboard.addMember': 'Mitglied hinzufügen', - 'dashboard.coverSaved': 'Titelbild gespeichert', - 'dashboard.coverUploadError': 'Fehler beim Hochladen', - 'dashboard.coverRemoveError': 'Fehler beim Entfernen', - 'dashboard.titleRequired': 'Titel ist erforderlich', - 'dashboard.endDateError': 'Enddatum muss nach dem Startdatum liegen', - - // Settings - 'settings.title': 'Einstellungen', - 'settings.subtitle': 'Konfigurieren Sie Ihre persönlichen Einstellungen', - 'settings.tabs.display': 'Anzeige', - 'settings.tabs.map': 'Karte', - 'settings.tabs.notifications': 'Mitteilungen', - 'settings.tabs.integrations': 'Integrationen', - 'settings.tabs.account': 'Konto', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Über', - 'settings.map': 'Karte', - 'settings.mapTemplate': 'Karten-Vorlage', - 'settings.mapTemplatePlaceholder.select': 'Vorlage auswählen...', - 'settings.mapDefaultHint': 'Leer lassen für OpenStreetMap (Standard)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL-Template für die Kartenkacheln', - 'settings.mapProvider': 'Kartenanbieter', - 'settings.mapProviderHint': 'Gilt für Trip Planner und Journey. Atlas nutzt immer Leaflet.', - 'settings.mapLeafletSubtitle': 'Klassisch 2D, beliebige Raster-Kacheln', - 'settings.mapMapboxSubtitle': 'Vektor-Kacheln, 3D-Gebäude & Terrain', - 'settings.mapExperimental': 'Experimentell', - 'settings.mapMapboxToken': 'Mapbox Access Token', - 'settings.mapMapboxTokenHint': 'Öffentliches Token (pk.*) von', - 'settings.mapMapboxTokenLink': 'mapbox.com → Access Tokens', - 'settings.mapStyle': 'Kartenstil', - 'settings.mapStylePlaceholder': 'Mapbox-Stil wählen', - 'settings.mapStyleHint': 'Preset oder eigene mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D-Gebäude & Terrain', - 'settings.map3dHint': 'Neigung + echte 3D-Gebäude-Extrusionen — funktioniert mit jedem Stil, auch Satellit.', - 'settings.mapHighQuality': 'Hochqualitäts-Modus', - 'settings.mapHighQualityHint': 'Antialiasing + Globus-Projektion für schärfere Kanten und eine realistische Weltsicht.', - 'settings.mapHighQualityWarning': 'Kann die Performance auf schwächeren Geräten beeinträchtigen.', - 'settings.mapTipLabel': 'Tipp:', - 'settings.mapTip': 'Rechtsklick und ziehen, um die Karte zu drehen/neigen. Mittelklick, um einen Ort hinzuzufügen (Rechtsklick ist für die Rotation reserviert).', - 'settings.latitude': 'Breitengrad', - 'settings.longitude': 'Längengrad', - 'settings.saveMap': 'Karte speichern', - 'settings.apiKeys': 'API-Schlüssel', - 'settings.mapsKey': 'Google Maps API-Schlüssel', - 'settings.mapsKeyHint': 'Für Ortsuche. Benötigt Places API (New). Erhalten unter console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API-Schlüssel', - 'settings.weatherKeyHint': 'Für Wetterdaten. Kostenlos unter openweathermap.org/api', - 'settings.keyPlaceholder': 'Schlüssel eingeben...', - 'settings.configured': 'Konfiguriert', - 'settings.saveKeys': 'Schlüssel speichern', - 'settings.display': 'Darstellung', - 'settings.colorMode': 'Farbmodus', - 'settings.light': 'Hell', - 'settings.dark': 'Dunkel', - 'settings.auto': 'Automatisch', - 'settings.language': 'Sprache', - 'settings.temperature': 'Temperatureinheit', - 'settings.timeFormat': 'Zeitformat', - 'settings.bookingLabels': 'Orts-Labels auf Buchungsrouten', - 'settings.bookingLabelsHint': 'Zeigt Bahnhofs-/Flughafennamen auf der Karte. Wenn aus, wird nur das Icon angezeigt.', - 'settings.blurBookingCodes': 'Buchungscodes verbergen', - 'settings.notifications': 'Mitteilungen', - 'settings.notifyTripInvite': 'Trip-Einladungen', - 'settings.notifyBookingChange': 'Buchungsänderungen', - 'settings.notifyTripReminder': 'Trip-Erinnerungen', - 'settings.notifyTodoDue': 'Aufgabe bald fällig', - 'settings.notifyVacayInvite': 'Vacay Fusion-Einladungen', - 'settings.notifyPhotosShared': 'Geteilte Fotos (Immich)', - 'settings.notifyCollabMessage': 'Chat-Nachrichten (Collab)', - 'settings.notifyPackingTagged': 'Packliste: Zuweisungen', - 'settings.notifyWebhook': 'Webhook-Benachrichtigungen', - 'settings.notificationsDisabled': 'Benachrichtigungen sind nicht konfiguriert. Bitten Sie einen Administrator, E-Mail- oder Webhook-Benachrichtungen zu aktivieren.', - 'settings.notificationsActive': 'Aktiver Kanal', - 'settings.notificationsManagedByAdmin': 'Benachrichtigungsereignisse werden vom Administrator konfiguriert.', - 'admin.notifications.title': 'Benachrichtigungen', - 'admin.notifications.hint': 'Wählen Sie einen Benachrichtigungskanal. Es kann nur einer gleichzeitig aktiv sein.', - 'admin.notifications.none': 'Deaktiviert', - 'admin.notifications.email': 'E-Mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Benachrichtigungseinstellungen speichern', - 'admin.notifications.saved': 'Benachrichtigungseinstellungen gespeichert', - 'admin.notifications.testWebhook': 'Test-Webhook senden', - 'admin.notifications.testWebhookSuccess': 'Test-Webhook erfolgreich gesendet', - 'admin.notifications.testWebhookFailed': 'Test-Webhook fehlgeschlagen', - 'admin.smtp.title': 'E-Mail & Benachrichtigungen', - 'admin.smtp.hint': 'SMTP-Konfiguration zum Versenden von E-Mail-Benachrichtigungen.', - 'admin.smtp.testButton': 'Test-E-Mail senden', - 'admin.webhook.hint': 'Benachrichtigungen an einen externen Webhook senden (Discord, Slack usw.).', - 'admin.smtp.testSuccess': 'Test-E-Mail erfolgreich gesendet', - 'admin.smtp.testFailed': 'Test-E-Mail fehlgeschlagen', - 'dayplan.icsTooltip': 'Kalender exportieren (ICS)', - 'share.linkTitle': 'Öffentlicher Link', - 'share.linkHint': 'Erstelle einen Link den jeder ohne Login nutzen kann, um diese Reise anzuschauen. Nur lesen — keine Bearbeitung möglich.', - 'share.createLink': 'Link erstellen', - 'share.deleteLink': 'Link löschen', - 'share.createError': 'Link konnte nicht erstellt werden', - 'common.copy': 'Kopieren', - 'common.copied': 'Kopiert', - 'share.permMap': 'Karte & Plan', - 'share.permBookings': 'Buchungen', - 'share.permPacking': 'Packliste', - 'shared.expired': 'Link abgelaufen oder ungültig', - 'shared.expiredHint': 'Dieser geteilte Reise-Link ist nicht mehr aktiv.', - 'shared.readOnly': 'Nur-Lesen Ansicht', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Buchungen', - 'shared.tabPacking': 'Packliste', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'Tage', - 'shared.places': 'Orte', - 'shared.other': 'Sonstige', - 'shared.totalBudget': 'Gesamtbudget', - 'shared.messages': 'Nachrichten', - 'shared.sharedVia': 'Geteilt über', - 'shared.confirmed': 'Bestätigt', - 'shared.pending': 'Ausstehend', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - 'settings.on': 'An', - 'settings.off': 'Aus', - 'settings.mcp.title': 'MCP-Konfiguration', - 'settings.mcp.endpoint': 'MCP-Endpunkt', - 'settings.mcp.clientConfig': 'Client-Konfiguration', - 'settings.mcp.clientConfigHint': 'Ersetze durch ein API-Token aus der Liste unten. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Ersetze und durch die Zugangsdaten des oben erstellten OAuth 2.1-Clients. mcp-remote öffnet beim ersten Verbindungsaufbau deinen Browser zur Autorisierung. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).', - 'settings.mcp.copy': 'Kopieren', - 'settings.mcp.copied': 'Kopiert!', - 'settings.mcp.apiTokens': 'API-Tokens', - 'settings.mcp.createToken': 'Neuen Token erstellen', - 'settings.mcp.noTokens': 'Noch keine Tokens. Erstelle einen, um MCP-Clients zu verbinden.', - 'settings.mcp.tokenCreatedAt': 'Erstellt', - 'settings.mcp.tokenUsedAt': 'Verwendet', - 'settings.mcp.deleteTokenTitle': 'Token löschen', - 'settings.mcp.deleteTokenMessage': 'Dieser Token wird sofort ungültig. Jeder MCP-Client, der ihn verwendet, verliert den Zugang.', - 'settings.mcp.modal.createTitle': 'API-Token erstellen', - 'settings.mcp.modal.tokenName': 'Token-Name', - 'settings.mcp.modal.tokenNamePlaceholder': 'z. B. Claude Desktop, Arbeits-Laptop', - 'settings.mcp.modal.creating': 'Wird erstellt…', - 'settings.mcp.modal.create': 'Token erstellen', - 'settings.mcp.modal.createdTitle': 'Token erstellt', - 'settings.mcp.modal.createdWarning': 'Dieser Token wird nur einmal angezeigt. Kopiere und speichere ihn jetzt — er kann nicht wiederhergestellt werden.', - 'settings.mcp.modal.done': 'Fertig', - 'settings.mcp.toast.created': 'Token erstellt', - 'settings.mcp.toast.createError': 'Token konnte nicht erstellt werden', - 'settings.mcp.toast.deleted': 'Token gelöscht', - 'settings.mcp.toast.deleteError': 'Token konnte nicht gelöscht werden', - 'settings.mcp.apiTokensDeprecated': 'API-Tokens sind veraltet und werden in einer zukünftigen Version entfernt. Bitte verwende stattdessen OAuth 2.1-Clients.', - 'settings.oauth.clients': 'OAuth 2.1-Clients', - 'settings.oauth.clientsHint': 'Registriere OAuth 2.1-Clients, damit externe MCP-Anwendungen (Claude Web, Cursor usw.) sich ohne statische Tokens verbinden können.', - 'settings.oauth.createClient': 'Neuer Client', - 'settings.oauth.noClients': 'Keine OAuth-Clients registriert.', - 'settings.oauth.clientId': 'Client-ID', - 'settings.oauth.clientSecret': 'Client-Secret', - 'settings.oauth.deleteClient': 'Client löschen', - 'settings.oauth.deleteClientMessage': 'Dieser Client und alle aktiven Sessions werden dauerhaft entfernt. Jede Anwendung, die ihn nutzt, verliert sofort den Zugriff.', - 'settings.oauth.rotateSecret': 'Secret erneuern', - 'settings.oauth.rotateSecretMessage': 'Ein neues Client-Secret wird generiert und alle bestehenden Sessions werden sofort ungültig. Aktualisiere deine Anwendung, bevor du diesen Dialog schließt.', - 'settings.oauth.rotateSecretConfirm': 'Erneuern', - 'settings.oauth.rotateSecretConfirming': 'Wird erneuert…', - 'settings.oauth.rotateSecretDoneTitle': 'Neues Secret generiert', - 'settings.oauth.rotateSecretDoneWarning': 'Dieses Secret wird nur einmal angezeigt. Kopiere es jetzt und aktualisiere deine Anwendung — alle vorherigen Sessions wurden ungültig gemacht.', - 'settings.oauth.activeSessions': 'Aktive OAuth-Sessions', - 'settings.oauth.sessionScopes': 'Berechtigungen', - 'settings.oauth.sessionExpires': 'Läuft ab', - 'settings.oauth.revoke': 'Widerrufen', - 'settings.oauth.revokeSession': 'Session widerrufen', - 'settings.oauth.revokeSessionMessage': 'Dadurch wird der Zugriff für diese OAuth-Session sofort widerrufen.', - 'settings.oauth.modal.createTitle': 'OAuth-Client registrieren', - 'settings.oauth.modal.presets': 'Schnellvorlagen', - 'settings.oauth.modal.clientName': 'Anwendungsname', - 'settings.oauth.modal.clientNamePlaceholder': 'z. B. Claude Web, Meine MCP-App', - 'settings.oauth.modal.redirectUris': 'Redirect-URIs', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Eine URI pro Zeile. HTTPS erforderlich (localhost ausgenommen). Exakte Übereinstimmung erforderlich.', - 'settings.oauth.modal.scopes': 'Erlaubte Berechtigungen', - 'settings.oauth.modal.scopesHint': 'list_trips und get_trip_summary sind immer verfügbar — keine Berechtigung nötig. Sie helfen der KI, Trip-IDs zu ermitteln.', - 'settings.oauth.modal.selectAll': 'Alle auswählen', - 'settings.oauth.modal.deselectAll': 'Alle abwählen', - 'settings.oauth.modal.creating': 'Wird registriert…', - 'settings.oauth.modal.create': 'Client registrieren', - 'settings.oauth.modal.createdTitle': 'Client registriert', - 'settings.oauth.modal.createdWarning': 'Das Client-Secret wird nur einmal angezeigt. Kopiere es jetzt — es kann nicht wiederhergestellt werden.', - 'settings.oauth.toast.createError': 'OAuth-Client konnte nicht registriert werden', - 'settings.oauth.toast.deleted': 'OAuth-Client gelöscht', - 'settings.oauth.toast.deleteError': 'OAuth-Client konnte nicht gelöscht werden', - 'settings.oauth.toast.revoked': 'Session widerrufen', - 'settings.oauth.toast.revokeError': 'Session konnte nicht widerrufen werden', - 'settings.oauth.toast.rotateError': 'Client-Secret konnte nicht erneuert werden', - 'settings.oauth.modal.machineClient': 'Maschineller Client (kein Browser-Login)', - 'settings.oauth.modal.machineClientHint': 'Verwendet den client_credentials Grant — keine Redirect-URIs erforderlich. Das Token wird direkt über client_id + client_secret ausgestellt und handelt in Ihrem Namen innerhalb der gewählten Scopes.', - 'settings.oauth.modal.machineClientUsage': 'Token abrufen: POST /oauth/token mit grant_type=client_credentials, client_id und client_secret. Kein Browser, kein Refresh-Token.', - 'settings.oauth.badge.machine': 'Maschine', - 'settings.account': 'Konto', - 'settings.about': 'Über', - 'settings.about.reportBug': 'Bug melden', - 'settings.about.reportBugHint': 'Problem gefunden? Melde es uns', - 'settings.about.featureRequest': 'Feature vorschlagen', - 'settings.about.featureRequestHint': 'Schlage ein neues Feature vor', - 'settings.about.wikiHint': 'Dokumentation & Anleitungen', - 'settings.about.supporters.badge': 'Monatliche Unterstützer', - 'settings.about.supporters.title': 'Reisebegleitung für TREK', - 'settings.about.supporters.subtitle': 'Während du deine nächste Route planst, planen diese Leute mit, wie TREK weitergeht. Ihr monatlicher Beitrag fließt direkt in Entwicklung und echten Zeitaufwand — damit TREK Open Source bleibt.', - 'settings.about.supporters.since': 'Unterstützer seit {date}', - 'settings.about.supporters.tierEmpty': 'Sei die/der Erste', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK ist ein selbst gehosteter Reiseplaner, der dir hilft, deine Trips von der ersten Idee bis zur letzten Erinnerung zu organisieren. Tagesplanung, Budget, Packlisten, Fotos und vieles mehr — alles an einem Ort, auf deinem eigenen Server.', - 'settings.about.madeWith': 'Entwickelt mit', - 'settings.about.madeBy': 'von Maurice und einer wachsenden Open-Source-Community.', - 'settings.username': 'Benutzername', - 'settings.email': 'E-Mail', - 'settings.role': 'Rolle', - 'settings.roleAdmin': 'Administrator', - 'settings.oidcLinked': 'Verknüpft mit', - 'settings.changePassword': 'Passwort ändern', - 'settings.mustChangePassword': 'Sie müssen Ihr Passwort ändern, bevor Sie fortfahren können. Bitte legen Sie unten ein neues Passwort fest.', - 'settings.currentPassword': 'Aktuelles Passwort', - 'settings.currentPasswordRequired': 'Aktuelles Passwort wird benötigt', - 'settings.newPassword': 'Neues Passwort', - 'settings.confirmPassword': 'Neues Passwort bestätigen', - 'settings.updatePassword': 'Passwort aktualisieren', - 'settings.passwordRequired': 'Bitte aktuelles und neues Passwort eingeben', - 'settings.passwordTooShort': 'Passwort muss mindestens 8 Zeichen lang sein', - 'settings.passwordMismatch': 'Passwörter stimmen nicht überein', - 'settings.passwordWeak': 'Passwort muss Groß-, Kleinbuchstaben, eine Zahl und ein Sonderzeichen enthalten', - 'settings.passwordChanged': 'Passwort erfolgreich geändert', - 'settings.deleteAccount': 'Löschen', - 'settings.deleteAccountTitle': 'Account wirklich löschen?', - 'settings.deleteAccountWarning': 'Dein Account und alle deine Reisen, Orte und Dateien werden unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.', - 'settings.deleteAccountConfirm': 'Endgültig löschen', - 'settings.deleteBlockedTitle': 'Löschung nicht möglich', - 'settings.deleteBlockedMessage': 'Du bist der einzige Administrator. Ernenne zuerst einen anderen Benutzer zum Admin, bevor du deinen Account löschen kannst.', - 'settings.roleUser': 'Benutzer', - 'settings.saveProfile': 'Speichern', - 'settings.toast.mapSaved': 'Karteneinstellungen gespeichert', - 'settings.toast.keysSaved': 'API-Schlüssel gespeichert', - 'settings.toast.displaySaved': 'Anzeigeeinstellungen gespeichert', - 'settings.toast.profileSaved': 'Profil aktualisiert', - 'settings.uploadAvatar': 'Profilbild hochladen', - 'settings.removeAvatar': 'Profilbild entfernen', - 'settings.avatarUploaded': 'Profilbild aktualisiert', - 'settings.avatarRemoved': 'Profilbild entfernt', - 'settings.avatarError': 'Fehler beim Hochladen', - 'settings.mfa.title': 'Zwei-Faktor-Authentifizierung (2FA)', - 'settings.mfa.description': 'Zusätzlicher Schritt bei der Anmeldung mit E-Mail und Passwort. Nutze eine Authenticator-App (Google Authenticator, Authy, …).', - 'settings.mfa.requiredByPolicy': 'Dein Administrator verlangt Zwei-Faktor-Authentifizierung. Richte unten eine Authenticator-App ein, bevor du fortfährst.', - 'settings.mfa.backupTitle': 'Backup-Codes', - 'settings.mfa.backupDescription': 'Verwende diese Einmal-Codes, wenn du keinen Zugriff mehr auf deine Authenticator-App hast.', - 'settings.mfa.backupWarning': 'Jetzt speichern. Jeder Code kann nur einmal verwendet werden.', - 'settings.mfa.backupCopy': 'Codes kopieren', - 'settings.mfa.backupDownload': 'TXT herunterladen', - 'settings.mfa.backupPrint': 'Drucken / PDF', - 'settings.mfa.backupCopied': 'Backup-Codes kopiert', - 'settings.mfa.enabled': '2FA ist für dein Konto aktiv.', - 'settings.mfa.disabled': '2FA ist nicht aktiviert.', - 'settings.mfa.setup': 'Authenticator einrichten', - 'settings.mfa.scanQr': 'QR-Code mit der App scannen oder den Schlüssel manuell eingeben.', - 'settings.mfa.secretLabel': 'Geheimer Schlüssel (manuell)', - 'settings.mfa.codePlaceholder': '6-stelliger Code', - 'settings.mfa.enable': '2FA aktivieren', - 'settings.mfa.cancelSetup': 'Abbrechen', - 'settings.mfa.disableTitle': '2FA deaktivieren', - 'settings.mfa.disableHint': 'Passwort und einen aktuellen Code aus der Authenticator-App eingeben.', - 'settings.mfa.disable': '2FA deaktivieren', - 'settings.mfa.toastEnabled': 'Zwei-Faktor-Authentifizierung aktiviert', - 'settings.mfa.toastDisabled': 'Zwei-Faktor-Authentifizierung deaktiviert', - 'settings.mfa.demoBlocked': 'In der Demo nicht verfügbar', - - // Login - 'login.error': 'Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen.', - 'login.tagline': 'Deine Reisen.\nDein Plan.', - 'login.description': 'Plane Reisen gemeinsam mit interaktiven Karten, Budgets und Echtzeit-Sync.', - 'login.features.maps': 'Interaktive Karten', - 'login.features.mapsDesc': 'Google Places, Routen & Clustering', - 'login.features.realtime': 'Echtzeit-Sync', - 'login.features.realtimeDesc': 'Gemeinsam planen via WebSocket', - 'login.features.budget': 'Budget-Tracking', - 'login.features.budgetDesc': 'Kategorien, Diagramme & Pro-Kopf', - 'login.features.collab': 'Zusammenarbeit', - 'login.features.collabDesc': 'Multi-User mit geteilten Reisen', - 'login.features.packing': 'Packlisten', - 'login.features.packingDesc': 'Kategorien & Fortschritt', - 'login.features.bookings': 'Buchungen', - 'login.features.bookingsDesc': 'Flüge, Hotels, Restaurants & mehr', - 'login.features.files': 'Dokumente', - 'login.features.filesDesc': 'Dateien hochladen & verwalten', - 'login.features.routes': 'Routenoptimierung', - 'login.features.routesDesc': 'Auto-Optimierung & Google Maps Export', - 'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Deine Daten bleiben bei dir', - 'login.title': 'Anmelden', - 'login.subtitle': 'Willkommen zurück', - 'login.signingIn': 'Anmelden…', - 'login.signIn': 'Anmelden', - 'login.createAdmin': 'Admin-Konto erstellen', - 'login.createAdminHint': 'Erstelle das erste Admin-Konto für TREK.', - 'login.setNewPassword': 'Neues Passwort festlegen', - 'login.setNewPasswordHint': 'Sie müssen Ihr Passwort ändern, bevor Sie fortfahren können.', - 'login.createAccount': 'Konto erstellen', - 'login.createAccountHint': 'Neues Konto registrieren.', - 'login.creating': 'Erstelle…', - 'login.noAccount': 'Noch kein Konto?', - 'login.hasAccount': 'Bereits ein Konto?', - 'login.register': 'Registrieren', - 'login.emailPlaceholder': 'deine@email.de', - 'login.username': 'Benutzername', - 'login.oidc.registrationDisabled': 'Registrierung ist deaktiviert. Kontaktiere den Administrator.', - 'login.oidc.noEmail': 'Keine E-Mail vom Provider erhalten.', - 'login.oidc.tokenFailed': 'Authentifizierung fehlgeschlagen.', - 'login.oidc.invalidState': 'Ungültige Sitzung. Bitte erneut versuchen.', - 'login.demoFailed': 'Demo-Login fehlgeschlagen', - 'login.oidcSignIn': 'Anmelden mit {name}', - 'login.oidcOnly': 'Passwort-Authentifizierung ist deaktiviert. Bitte melde dich über deinen SSO-Anbieter an.', - 'login.oidcLoggedOut': 'Du wurdest abgemeldet. Melde dich erneut über deinen SSO-Anbieter an.', - 'login.demoHint': 'Demo ausprobieren — ohne Registrierung', - 'login.mfaTitle': 'Zwei-Faktor-Authentifizierung', - 'login.mfaSubtitle': 'Gib den 6-stelligen Code aus deiner Authenticator-App ein.', - 'login.mfaCodeLabel': 'Bestätigungscode', - 'login.mfaCodeRequired': 'Bitte den Code aus der Authenticator-App eingeben.', - 'login.mfaHint': 'Google Authenticator, Authy oder eine andere TOTP-App öffnen.', - 'login.mfaBack': '← Zurück zur Anmeldung', - 'login.mfaVerify': 'Bestätigen', - 'login.invalidInviteLink': 'Ungültiger oder abgelaufener Einladungslink', - 'login.oidcFailed': 'OIDC-Anmeldung fehlgeschlagen', - 'login.usernameRequired': 'Benutzername ist erforderlich', - 'login.passwordMinLength': 'Das Passwort muss mindestens 8 Zeichen lang sein', - 'login.forgotPassword': 'Passwort vergessen?', - 'login.forgotPasswordTitle': 'Passwort zurücksetzen', - 'login.forgotPasswordBody': 'Gib die E-Mail-Adresse deines Kontos ein. Falls ein Konto existiert, schicken wir dir einen Reset-Link.', - 'login.forgotPasswordSubmit': 'Reset-Link senden', - 'login.forgotPasswordSentTitle': 'Prüfe deine E-Mails', - 'login.forgotPasswordSentBody': 'Falls ein Konto mit dieser Adresse existiert, ist ein Reset-Link unterwegs. Er läuft in 60 Minuten ab.', - 'login.forgotPasswordSmtpHintOff': 'Hinweis: Der Administrator hat SMTP nicht konfiguriert. Der Reset-Link wird statt per E-Mail in die Server-Konsole geschrieben.', - 'login.backToLogin': 'Zurück zur Anmeldung', - 'login.newPassword': 'Neues Passwort', - 'login.confirmPassword': 'Neues Passwort bestätigen', - 'login.passwordsDontMatch': 'Passwörter stimmen nicht überein', - 'login.mfaCode': '2FA-Code', - 'login.resetPasswordTitle': 'Neues Passwort festlegen', - 'login.resetPasswordBody': 'Wähle ein starkes Passwort, das du hier noch nicht verwendet hast. Mindestens 8 Zeichen.', - 'login.resetPasswordMfaBody': 'Gib deinen 2FA-Code oder einen Backup-Code ein, um den Reset abzuschließen.', - 'login.resetPasswordSubmit': 'Passwort zurücksetzen', - 'login.resetPasswordVerify': 'Prüfen & zurücksetzen', - 'login.resetPasswordSuccessTitle': 'Passwort aktualisiert', - 'login.resetPasswordSuccessBody': 'Du kannst dich jetzt mit deinem neuen Passwort anmelden.', - 'login.resetPasswordInvalidLink': 'Ungültiger Reset-Link', - 'login.resetPasswordInvalidLinkBody': 'Dieser Link fehlt oder ist beschädigt. Fordere einen neuen an, um fortzufahren.', - 'login.resetPasswordFailed': 'Zurücksetzen fehlgeschlagen. Der Link ist möglicherweise abgelaufen.', - - // Register - 'register.passwordMismatch': 'Passwörter stimmen nicht überein', - 'register.passwordTooShort': 'Passwort muss mindestens 8 Zeichen lang sein', - 'register.failed': 'Registrierung fehlgeschlagen', - 'register.getStarted': 'Jetzt starten', - 'register.subtitle': 'Erstellen Sie ein Konto und beginnen Sie, Ihre Traumreisen zu planen.', - 'register.feature1': 'Unbegrenzte Reisepläne', - 'register.feature2': 'Interaktive Kartenansicht', - 'register.feature3': 'Orte und Kategorien verwalten', - 'register.feature4': 'Reservierungen tracken', - 'register.feature5': 'Packlisten erstellen', - 'register.feature6': 'Fotos und Dateien speichern', - 'register.createAccount': 'Konto erstellen', - 'register.startPlanning': 'Beginnen Sie Ihre Reiseplanung', - 'register.minChars': 'Mind. 6 Zeichen', - 'register.confirmPassword': 'Passwort bestätigen', - 'register.repeatPassword': 'Passwort wiederholen', - 'register.registering': 'Registrieren...', - 'register.register': 'Registrieren', - 'register.hasAccount': 'Bereits ein Konto?', - 'register.signIn': 'Anmelden', - - // Admin - 'admin.title': 'Administration', - 'admin.subtitle': 'Benutzerverwaltung und Systemeinstellungen', - 'admin.tabs.users': 'Benutzer', - 'admin.tabs.categories': 'Kategorien', - 'admin.tabs.backup': 'Backup', - 'admin.tabs.audit': 'Audit', - 'admin.stats.users': 'Benutzer', - 'admin.stats.trips': 'Reisen', - 'admin.stats.places': 'Orte', - 'admin.stats.photos': 'Fotos', - 'admin.stats.files': 'Dateien', - 'admin.table.user': 'Benutzer', - 'admin.table.email': 'E-Mail', - 'admin.table.role': 'Rolle', - 'admin.table.created': 'Erstellt', - 'admin.table.lastLogin': 'Letzter Login', - 'admin.table.actions': 'Aktionen', - 'admin.you': '(Du)', - 'admin.editUser': 'Benutzer bearbeiten', - 'admin.newPassword': 'Neues Passwort', - 'admin.newPasswordHint': 'Leer lassen, um das Passwort nicht zu ändern', - 'admin.deleteUser': 'Benutzer "{name}" löschen? Alle Reisen werden unwiderruflich gelöscht.', - 'admin.deleteUserTitle': 'Benutzer löschen', - 'admin.newPasswordPlaceholder': 'Neues Passwort eingeben…', - 'admin.toast.loadError': 'Fehler beim Laden der Admin-Daten', - 'admin.toast.userUpdated': 'Benutzer aktualisiert', - 'admin.toast.updateError': 'Fehler beim Aktualisieren', - 'admin.toast.userDeleted': 'Benutzer gelöscht', - 'admin.toast.deleteError': 'Fehler beim Löschen', - 'admin.toast.cannotDeleteSelf': 'Eigenes Konto kann nicht gelöscht werden', - 'admin.toast.userCreated': 'Benutzer erstellt', - 'admin.toast.createError': 'Fehler beim Erstellen des Benutzers', - 'admin.toast.fieldsRequired': 'Benutzername, E-Mail und Passwort sind erforderlich', - 'admin.createUser': 'Benutzer anlegen', - 'admin.invite.title': 'Einladungslinks', - 'admin.invite.subtitle': 'Einmal-Links für die Registrierung erstellen', - 'admin.invite.create': 'Link erstellen', - 'admin.invite.createAndCopy': 'Erstellen & kopieren', - 'admin.invite.empty': 'Noch keine Einladungslinks erstellt', - 'admin.invite.maxUses': 'Max. Nutzungen', - 'admin.invite.expiry': 'Gültig für', - 'admin.invite.uses': 'genutzt', - 'admin.invite.expiresAt': 'läuft ab am', - 'admin.invite.createdBy': 'von', - 'admin.invite.active': 'Aktiv', - 'admin.invite.expired': 'Abgelaufen', - 'admin.invite.usedUp': 'Aufgebraucht', - 'admin.invite.copied': 'Einladungslink in Zwischenablage kopiert', - 'admin.invite.copyLink': 'Link kopieren', - 'admin.invite.deleted': 'Einladungslink gelöscht', - 'admin.invite.createError': 'Fehler beim Erstellen des Einladungslinks', - 'admin.invite.deleteError': 'Fehler beim Löschen des Einladungslinks', - 'admin.tabs.settings': 'Einstellungen', - 'admin.allowRegistration': 'Registrierung erlauben', - 'admin.allowRegistrationHint': 'Neue Benutzer können sich selbst registrieren', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Zwei-Faktor-Authentifizierung (2FA) für alle verlangen', - 'admin.requireMfaHint': 'Benutzer ohne 2FA müssen die Einrichtung unter Einstellungen abschließen, bevor sie die App nutzen können.', - 'admin.apiKeys': 'API-Schlüssel', - 'admin.apiKeysHint': 'Optional. Aktiviert erweiterte Ortsdaten wie Fotos und Wetter.', - 'admin.mapsKey': 'Google Maps API-Schlüssel', - 'admin.mapsKeyHint': 'Für Ortsuche benötigt. Erstellen unter console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Ohne API Key wird OpenStreetMap für die Ortssuche genutzt. Mit Google API Key können zusätzlich Bilder, Bewertungen und Öffnungszeiten geladen werden. Erstellen unter console.cloud.google.com.', - 'admin.recommended': 'Empfohlen', - 'admin.weatherKey': 'OpenWeatherMap API-Schlüssel', - 'admin.weatherKeyHint': 'Für Wetterdaten. Kostenlos unter openweathermap.org', - 'admin.validateKey': 'Test', - 'admin.keyValid': 'Verbunden', - 'admin.keyInvalid': 'Ungültig', - 'admin.keySaved': 'API-Schlüssel gespeichert', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Anmeldung über externe Anbieter wie Google, Apple, Authentik oder Keycloak.', - 'admin.oidcDisplayName': 'Anzeigename', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'Die OpenID Connect Issuer URL des Anbieters. z.B. https://accounts.google.com', - 'admin.oidcSaved': 'OIDC-Konfiguration gespeichert', - 'admin.oidcOnlyMode': 'Passwort-Authentifizierung deaktivieren', - 'admin.oidcOnlyModeHint': 'Wenn aktiviert, ist nur SSO-Login erlaubt. Passwort-Login und Registrierung werden blockiert.', - - // File Types - 'admin.fileTypes': 'Erlaubte Dateitypen', - 'admin.fileTypesHint': 'Konfiguriere welche Dateitypen hochgeladen werden dürfen.', - 'admin.fileTypesFormat': 'Kommagetrennte Endungen (z.B. jpg,png,pdf,doc). Verwende * um alle Typen zu erlauben.', - 'admin.fileTypesSaved': 'Dateityp-Einstellungen gespeichert', - - 'admin.placesPhotos.title': 'Ortsfotos', - 'admin.placesPhotos.subtitle': 'Fotos von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen. Wikimedia-Fotos sind davon nicht betroffen.', - 'admin.placesAutocomplete.title': 'Orts-Autovervollständigung', - 'admin.placesAutocomplete.subtitle': 'Google Places API für Suchvorschläge nutzen. Deaktivieren, um API-Kontingent zu sparen.', - 'admin.placesDetails.title': 'Ortsdetails', - 'admin.placesDetails.subtitle': 'Detaillierte Ortsinformationen (Öffnungszeiten, Bewertung, Website) von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen.', - // Packing Templates & Bag Tracking - 'admin.bagTracking.title': 'Gepäck-Tracking', - 'admin.bagTracking.subtitle': 'Gewicht und Gepäckstück-Zuordnung für Packlisteneinträge aktivieren', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Echtzeit-Nachrichten für die Reiseplanung', - 'admin.collab.notes.title': 'Notizen', - 'admin.collab.notes.subtitle': 'Gemeinsame Notizen und Dokumente', - 'admin.collab.polls.title': 'Umfragen', - 'admin.collab.polls.subtitle': 'Gruppen-Umfragen und Abstimmungen', - 'admin.collab.whatsnext.title': 'Was kommt als Nächstes', - 'admin.collab.whatsnext.subtitle': 'Aktivitätsvorschläge und nächste Schritte', - 'admin.tabs.config': 'Personalisierung', - 'admin.tabs.defaults': 'Benutzer-Standards', - 'admin.defaultSettings.title': 'Standard-Benutzereinstellungen', - 'admin.defaultSettings.description': 'Instanzweite Standards festlegen. Benutzer, die eine Einstellung nicht geändert haben, sehen diese Werte. Eigene Änderungen haben immer Vorrang.', - 'admin.defaultSettings.saved': 'Standard gespeichert', - 'admin.defaultSettings.reset': 'Auf eingebauten Standard zurücksetzen', - 'admin.defaultSettings.resetToBuiltIn': 'zurücksetzen', - 'admin.tabs.templates': 'Packvorlagen', - 'admin.packingTemplates.title': 'Packvorlagen', - 'admin.packingTemplates.subtitle': 'Wiederverwendbare Packlisten für deine Reisen erstellen', - 'admin.packingTemplates.create': 'Neue Vorlage', - 'admin.packingTemplates.namePlaceholder': 'Vorlagenname (z.B. Strandurlaub)', - 'admin.packingTemplates.empty': 'Noch keine Vorlagen erstellt', - 'admin.packingTemplates.items': 'Einträge', - 'admin.packingTemplates.categories': 'Kategorien', - 'admin.packingTemplates.itemName': 'Artikelname', - 'admin.packingTemplates.itemCategory': 'Kategorie', - 'admin.packingTemplates.categoryName': 'Kategoriename (z.B. Kleidung)', - 'admin.packingTemplates.addCategory': 'Kategorie hinzufügen', - 'admin.packingTemplates.created': 'Vorlage erstellt', - 'admin.packingTemplates.deleted': 'Vorlage gelöscht', - 'admin.packingTemplates.loadError': 'Vorlagen konnten nicht geladen werden', - 'admin.packingTemplates.createError': 'Vorlage konnte nicht erstellt werden', - 'admin.packingTemplates.deleteError': 'Vorlage konnte nicht gelöscht werden', - 'admin.packingTemplates.saveError': 'Fehler beim Speichern', - - // Addons - 'admin.tabs.addons': 'Addons', - 'admin.addons.title': 'Addons', - 'admin.addons.subtitle': 'Aktiviere oder deaktiviere Funktionen, um TREK nach deinen Wünschen anzupassen.', - 'admin.addons.catalog.packing.name': 'Listen', - 'admin.addons.catalog.packing.description': 'Packlisten und To-Do-Aufgaben für deine Reisen', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Ausgaben verfolgen und Reisebudget planen', - 'admin.addons.catalog.documents.name': 'Dokumente', - 'admin.addons.catalog.documents.description': 'Reisedokumente speichern und verwalten', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Persönlicher Urlaubsplaner mit Kalenderansicht', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Weltkarte mit besuchten Ländern und Reisestatistiken', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Echtzeit-Notizen, Umfragen und Chat für die Reiseplanung', - 'admin.addons.catalog.memories.name': 'Fotos (Immich)', - 'admin.addons.catalog.memories.description': 'Reisefotos über deine Immich-Instanz teilen', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol für die KI-Assistenten-Integration', - 'admin.addons.subtitleBefore': 'Aktiviere oder deaktiviere Funktionen, um ', - 'admin.addons.subtitleAfter': ' nach deinen Wünschen anzupassen.', - 'admin.addons.enabled': 'Aktiviert', - 'admin.addons.disabled': 'Deaktiviert', - 'admin.addons.type.trip': 'Reise', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integration', - 'admin.addons.tripHint': 'Verfügbar als Tab innerhalb jedes Trips', - 'admin.addons.globalHint': 'Verfügbar als eigenständiger Bereich in der Navigation', - 'admin.addons.integrationHint': 'Backend-Dienste und API-Integrationen ohne eigene Seite', - 'admin.addons.toast.updated': 'Addon aktualisiert', - 'admin.addons.toast.error': 'Addon konnte nicht aktualisiert werden', - 'admin.addons.noAddons': 'Keine Addons verfügbar', - // Weather info - 'admin.weather.title': 'Wetterdaten', - 'admin.weather.badge': 'Seit 24. März 2026', - 'admin.weather.description': 'TREK nutzt Open-Meteo als Wetterdatenquelle. Open-Meteo ist ein kostenloser, quelloffener Wetterdienst — es wird kein API-Schlüssel benötigt.', - 'admin.weather.forecast': '16-Tage-Vorhersage', - 'admin.weather.forecastDesc': 'Statt bisher 5 Tage (OpenWeatherMap)', - 'admin.weather.climate': 'Historische Klimadaten', - 'admin.weather.climateDesc': 'Durchschnittswerte der letzten 85 Jahre für Tage jenseits der 16-Tage-Vorhersage', - 'admin.weather.requests': '10.000 Anfragen / Tag', - 'admin.weather.requestsDesc': 'Kostenlos, kein API-Schlüssel erforderlich', - 'admin.weather.locationHint': 'Das Wetter wird anhand des ersten Ortes mit Koordinaten im jeweiligen Tag berechnet. Ist kein Ort am Tag eingeplant, wird ein beliebiger Ort aus der Ortsliste als Referenz verwendet.', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP-Zugang', - 'admin.mcpTokens.title': 'MCP-Zugang', - 'admin.mcpTokens.subtitle': 'OAuth-Sitzungen und API-Tokens aller Benutzer verwalten', - 'admin.mcpTokens.sectionTitle': 'API-Tokens', - 'admin.mcpTokens.owner': 'Besitzer', - 'admin.mcpTokens.tokenName': 'Token-Name', - 'admin.mcpTokens.created': 'Erstellt', - 'admin.mcpTokens.lastUsed': 'Zuletzt verwendet', - 'admin.mcpTokens.never': 'Nie', - 'admin.mcpTokens.empty': 'Es wurden noch keine MCP-Tokens erstellt', - 'admin.mcpTokens.deleteTitle': 'Token löschen', - 'admin.mcpTokens.deleteMessage': 'Dieser Token wird sofort widerrufen. Der Benutzer verliert den MCP-Zugang über diesen Token.', - 'admin.mcpTokens.deleteSuccess': 'Token gelöscht', - 'admin.mcpTokens.deleteError': 'Token konnte nicht gelöscht werden', - 'admin.mcpTokens.loadError': 'Tokens konnten nicht geladen werden', - 'admin.oauthSessions.sectionTitle': 'OAuth-Sitzungen', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Besitzer', - 'admin.oauthSessions.scopes': 'Berechtigungen', - 'admin.oauthSessions.created': 'Erstellt', - 'admin.oauthSessions.empty': 'Keine aktiven OAuth-Sitzungen', - 'admin.oauthSessions.revokeTitle': 'Sitzung widerrufen', - 'admin.oauthSessions.revokeMessage': 'Diese OAuth-Sitzung wird sofort widerrufen. Der Client verliert den MCP-Zugang.', - 'admin.oauthSessions.revokeSuccess': 'Sitzung widerrufen', - 'admin.oauthSessions.revokeError': 'Sitzung konnte nicht widerrufen werden', - 'admin.oauthSessions.loadError': 'OAuth-Sitzungen konnten nicht geladen werden', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Sicherheitsrelevante und administrative Ereignisse (Backups, Benutzer, MFA, Einstellungen).', - 'admin.audit.empty': 'Noch keine Audit-Einträge.', - 'admin.audit.refresh': 'Aktualisieren', - 'admin.audit.loadMore': 'Mehr laden', - 'admin.audit.showing': '{count} geladen · {total} gesamt', - 'admin.audit.col.time': 'Zeit', - 'admin.audit.col.user': 'Benutzer', - 'admin.audit.col.action': 'Aktion', - 'admin.audit.col.resource': 'Ressource', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Details', - - 'admin.github.title': 'Update-Verlauf', - 'admin.github.subtitle': 'Neueste Updates von {repo}', - 'admin.github.latest': 'Aktuell', - 'admin.github.prerelease': 'Vorabversion', - 'admin.github.showDetails': 'Details anzeigen', - 'admin.github.hideDetails': 'Details ausblenden', - 'admin.github.loadMore': 'Mehr laden', - 'admin.github.loading': 'Wird geladen...', - 'admin.github.error': 'Releases konnten nicht geladen werden', - 'admin.github.by': 'von', - 'admin.github.support': 'Hilft mir, TREK weiterzuentwickeln', - - 'admin.update.available': 'Update verfügbar', - 'admin.update.text': 'TREK {version} ist verfügbar. Du verwendest {current}.', - 'admin.update.button': 'Auf GitHub ansehen', - 'admin.update.install': 'Update installieren', - 'admin.update.confirmTitle': 'Update installieren?', - 'admin.update.confirmText': 'TREK wird von {current} auf {version} aktualisiert. Der Server startet danach automatisch neu.', - 'admin.update.dataInfo': 'Alle Daten (Reisen, Benutzer, API-Schlüssel, Uploads, Vacay, Atlas, Budgets) bleiben erhalten.', - 'admin.update.warning': 'Die App ist während des Neustarts kurz nicht erreichbar.', - 'admin.update.confirm': 'Jetzt aktualisieren', - 'admin.update.installing': 'Wird aktualisiert…', - 'admin.update.success': 'Update installiert! Server startet neu…', - 'admin.update.failed': 'Update fehlgeschlagen', - 'admin.update.backupHint': 'Wir empfehlen, vor dem Update ein Backup zu erstellen und herunterzuladen.', - 'admin.update.backupLink': 'Zum Backup', - 'admin.update.howTo': 'Update-Anleitung', - 'admin.update.dockerText': 'Deine TREK-Instanz läuft in Docker. Um auf {version} zu aktualisieren, führe folgende Befehle auf deinem Server aus:', - 'admin.update.reloadHint': 'Bitte lade die Seite in wenigen Sekunden neu.', - - // Vacay addon - 'vacay.subtitle': 'Urlaubstage planen und verwalten', - 'vacay.settings': 'Einstellungen', - 'vacay.year': 'Jahr', - 'vacay.addYear': 'Nächstes Jahr hinzufügen', - 'vacay.addPrevYear': 'Vorheriges Jahr hinzufügen', - 'vacay.removeYear': 'Jahr entfernen', - 'vacay.removeYearConfirm': '{year} entfernen?', - 'vacay.removeYearHint': 'Alle Urlaubseinträge und Betriebsferien für dieses Jahr werden unwiderruflich gelöscht.', - 'vacay.remove': 'Entfernen', - 'vacay.persons': 'Personen', - 'vacay.noPersons': 'Keine Personen angelegt', - 'vacay.addPerson': 'Person hinzufügen', - 'vacay.editPerson': 'Person bearbeiten', - 'vacay.removePerson': 'Person entfernen', - 'vacay.removePersonConfirm': '{name} wirklich entfernen?', - 'vacay.removePersonHint': 'Alle Urlaubseinträge dieser Person werden unwiderruflich gelöscht.', - 'vacay.personName': 'Name', - 'vacay.personNamePlaceholder': 'Name eingeben', - 'vacay.color': 'Farbe', - 'vacay.add': 'Hinzufügen', - 'vacay.legend': 'Legende', - 'vacay.publicHoliday': 'Feiertag', - 'vacay.companyHoliday': 'Betriebsferien', - 'vacay.weekend': 'Wochenende', - 'vacay.modeVacation': 'Urlaub', - 'vacay.modeCompany': 'Betriebsferien', - 'vacay.entitlement': 'Urlaubsanspruch', - 'vacay.entitlementDays': 'Tage', - 'vacay.used': 'Weg', - 'vacay.remaining': 'Rest', - 'vacay.carriedOver': 'aus {year}', - 'vacay.blockWeekends': 'Wochenenden sperren', - 'vacay.blockWeekendsHint': 'Verhindert Urlaubseinträge an Wochenendtagen', - 'vacay.weekendDays': 'Wochenendtage', - 'vacay.mon': 'Mo', - 'vacay.tue': 'Di', - 'vacay.wed': 'Mi', - 'vacay.thu': 'Do', - 'vacay.fri': 'Fr', - 'vacay.sat': 'Sa', - 'vacay.sun': 'So', - 'vacay.publicHolidays': 'Feiertage', - 'vacay.publicHolidaysHint': 'Feiertage im Kalender markieren', - 'vacay.selectCountry': 'Land wählen', - 'vacay.selectRegion': 'Region wählen (optional)', - 'vacay.addCalendar': 'Kalender hinzufügen', - 'vacay.calendarLabel': 'Bezeichnung (optional)', - 'vacay.calendarColor': 'Farbe', - 'vacay.noCalendars': 'Noch keine Feiertagskalender angelegt', - 'vacay.companyHolidays': 'Betriebsferien', - 'vacay.companyHolidaysHint': 'Erlaubt das Markieren von unternehmensweiten Feiertagen', - 'vacay.companyHolidaysNoDeduct': 'Betriebsferien werden nicht vom Urlaubskontingent abgezogen.', - 'vacay.weekStart': 'Woche beginnt am', - 'vacay.weekStartHint': 'Wähle ob die Kalenderwoche am Montag oder Sonntag beginnt', - 'vacay.carryOver': 'Urlaubsmitnahme', - 'vacay.carryOverHint': 'Resturlaub automatisch ins Folgejahr übertragen', - 'vacay.sharing': 'Teilen', - 'vacay.sharingHint': 'Teile deinen Urlaubsplan mit anderen TREK-Benutzern', - 'vacay.owner': 'Besitzer', - 'vacay.shareEmailPlaceholder': 'E-Mail des TREK-Benutzers', - 'vacay.shareSuccess': 'Plan erfolgreich geteilt', - 'vacay.shareError': 'Plan konnte nicht geteilt werden', - 'vacay.dissolve': 'Fusion auflösen', - 'vacay.dissolveHint': 'Kalender wieder trennen. Deine Einträge bleiben erhalten.', - 'vacay.dissolveAction': 'Auflösen', - 'vacay.dissolved': 'Kalender getrennt', - 'vacay.fusedWith': 'Fusioniert mit', - 'vacay.you': 'du', - 'vacay.noData': 'Keine Daten', - 'vacay.changeColor': 'Farbe ändern', - 'vacay.inviteUser': 'Benutzer einladen', - 'vacay.inviteHint': 'Lade einen anderen TREK-Benutzer ein, um einen gemeinsamen Urlaubskalender zu teilen.', - 'vacay.selectUser': 'Benutzer wählen', - 'vacay.sendInvite': 'Einladung senden', - 'vacay.inviteSent': 'Einladung gesendet', - 'vacay.inviteError': 'Einladung konnte nicht gesendet werden', - 'vacay.pending': 'ausstehend', - 'vacay.noUsersAvailable': 'Keine Benutzer verfügbar', - 'vacay.accept': 'Annehmen', - 'vacay.decline': 'Ablehnen', - 'vacay.acceptFusion': 'Annehmen & Fusionieren', - 'vacay.inviteTitle': 'Fusionsanfrage', - 'vacay.inviteWantsToFuse': 'möchte einen Urlaubskalender mit dir teilen.', - 'vacay.fuseInfo1': 'Beide sehen alle Urlaubseinträge in einem gemeinsamen Kalender.', - 'vacay.fuseInfo2': 'Beide können Einträge für den jeweils anderen erstellen und bearbeiten.', - 'vacay.fuseInfo3': 'Beide können Einträge löschen und den Urlaubsanspruch ändern.', - 'vacay.fuseInfo4': 'Einstellungen wie Feiertage und Betriebsferien werden geteilt.', - 'vacay.fuseInfo5': 'Die Fusion kann jederzeit von beiden Seiten aufgelöst werden. Einträge bleiben erhalten.', - 'nav.myTrips': 'Meine Trips', - - // Atlas addon - 'atlas.subtitle': 'Dein Reise-Fußabdruck auf der Welt', - 'atlas.countries': 'Länder', - 'atlas.trips': 'Reisen', - 'atlas.places': 'Orte', - 'atlas.unmark': 'Entfernen', - 'atlas.confirmMark': 'Dieses Land als besucht markieren?', - 'atlas.confirmUnmark': 'Dieses Land von der Liste entfernen?', - 'atlas.confirmUnmarkRegion': 'Diese Region von der Liste entfernen?', - 'atlas.markVisited': 'Als besucht markieren', - 'atlas.markVisitedHint': 'Dieses Land zur besuchten Liste hinzufügen', - 'atlas.markRegionVisitedHint': 'Diese Region zur besuchten Liste hinzufügen', - 'atlas.addToBucket': 'Zur Bucket List', - 'atlas.addPoi': 'Ort hinzufügen', - 'atlas.searchCountry': 'Land suchen...', - 'atlas.bucketNamePlaceholder': 'Name (Land, Stadt, Ort...)', - 'atlas.month': 'Monat', - 'atlas.year': 'Jahr', - 'atlas.addToBucketHint': 'Als Wunschziel speichern', - 'atlas.bucketWhen': 'Wann möchtest du dorthin reisen?', - 'atlas.statsTab': 'Statistik', - 'atlas.bucketTab': 'Wunschliste', - 'atlas.addBucket': 'Zur Bucket List hinzufügen', - 'atlas.bucketNotesPlaceholder': 'Notizen (optional)', - 'atlas.bucketEmpty': 'Deine Bucket List ist leer', - 'atlas.bucketEmptyHint': 'Füge Orte hinzu, die du besuchen möchtest', - 'atlas.days': 'Tage', - 'atlas.visitedCountries': 'Besuchte Länder', - 'atlas.cities': 'Städte', - 'atlas.noData': 'Noch keine Reisedaten', - 'atlas.noDataHint': 'Erstelle einen Trip und füge Orte hinzu', - 'atlas.lastTrip': 'Letzter Trip', - 'atlas.nextTrip': 'Nächster Trip', - 'atlas.daysLeft': 'Tage', - 'atlas.streak': 'Serie', - 'atlas.years': 'Jahre', - 'atlas.yearInRow': 'Jahr in Folge', - 'atlas.yearsInRow': 'Jahre in Folge', - 'atlas.tripIn': 'Reise in', - 'atlas.tripsIn': 'Reisen in', - 'atlas.since': 'seit', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Asien', - 'atlas.northAmerica': 'N-Amerika', - 'atlas.southAmerica': 'S-Amerika', - 'atlas.africa': 'Afrika', - 'atlas.oceania': 'Ozeanien', - 'atlas.other': 'Andere', - 'atlas.firstVisit': 'Erste Reise', - 'atlas.lastVisitLabel': 'Letzte Reise', - 'atlas.tripSingular': 'Reise', - 'atlas.tripPlural': 'Reisen', - 'atlas.placeVisited': 'Ort besucht', - 'atlas.placesVisited': 'Orte besucht', - - // Trip Planner - 'trip.tabs.plan': 'Karte', - 'trip.tabs.transports': 'Transport', - 'trip.tabs.reservations': 'Buchungen', - 'trip.tabs.reservationsShort': 'Buchung', - 'trip.tabs.packing': 'Liste', - 'trip.tabs.packingShort': 'Liste', - 'trip.tabs.lists': 'Listen', - 'trip.tabs.listsShort': 'Listen', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'Dateien', - 'trip.loading': 'Reise wird geladen...', - 'trip.loadingPhotos': 'Fotos der Orte werden geladen...', - 'trip.mobilePlan': 'Planung', - 'trip.mobilePlaces': 'Orte', - 'trip.toast.placeUpdated': 'Ort aktualisiert', - 'trip.toast.placeAdded': 'Ort hinzugefügt', - 'trip.toast.placeDeleted': 'Ort gelöscht', - 'trip.toast.selectDay': 'Bitte wähle zuerst einen Tag aus', - 'trip.toast.assignedToDay': 'Ort wurde dem Tag zugewiesen', - 'trip.toast.reorderError': 'Fehler beim Sortieren', - 'trip.toast.reservationUpdated': 'Reservierung aktualisiert', - 'trip.toast.reservationAdded': 'Reservierung hinzugefügt', - 'trip.toast.deleted': 'Gelöscht', - 'trip.confirm.deletePlace': 'Möchtest du diesen Ort wirklich löschen?', - 'trip.confirm.deletePlaces': '{count} Orte löschen?', - 'trip.toast.placesDeleted': '{count} Orte gelöscht', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Keine Orte für diesen Tag geplant', - 'dayplan.cannotReorderTransport': 'Buchungen mit fester Uhrzeit können nicht verschoben werden', - 'dayplan.confirmRemoveTimeTitle': 'Uhrzeit entfernen?', - 'dayplan.confirmRemoveTimeBody': 'Dieser Ort hat eine feste Uhrzeit ({time}). Durch das Verschieben wird die Uhrzeit entfernt und der Ort kann frei sortiert werden.', - 'dayplan.confirmRemoveTimeAction': 'Uhrzeit entfernen & verschieben', - 'dayplan.cannotDropOnTimed': 'Orte können nicht zwischen zeitgebundene Einträge geschoben werden', - 'dayplan.cannotBreakChronology': 'Die zeitliche Reihenfolge von Uhrzeiten und Buchungen darf nicht verletzt werden', - 'dayplan.addNote': 'Notiz hinzufügen', - 'dayplan.expandAll': 'Alle Tage ausklappen', - 'dayplan.collapseAll': 'Alle Tage einklappen', - 'dayplan.editNote': 'Notiz bearbeiten', - 'dayplan.noteAdd': 'Notiz hinzufügen', - 'dayplan.noteEdit': 'Notiz bearbeiten', - 'dayplan.noteTitle': 'Notiz', - 'dayplan.noteSubtitle': 'Tagesnotiz', - 'dayplan.totalCost': 'Gesamtkosten', - 'dayplan.days': 'Tage', - 'dayplan.dayN': 'Tag {n}', - 'dayplan.calculating': 'Berechne...', - 'dayplan.route': 'Route', - 'dayplan.optimize': 'Optimieren', - 'dayplan.optimized': 'Route optimiert', - 'dayplan.routeError': 'Fehler bei der Routenberechnung', - 'dayplan.toast.needTwoPlaces': 'Mindestens zwei Orte für Routenoptimierung nötig', - 'dayplan.toast.routeOptimized': 'Route optimiert', - 'dayplan.toast.noGeoPlaces': 'Keine Orte mit Koordinaten für Routenberechnung gefunden', - 'dayplan.confirmed': 'Bestätigt', - 'dayplan.pendingRes': 'Ausstehend', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Tagesplan als PDF exportieren', - 'dayplan.pdfError': 'Fehler beim PDF-Export', - - // Places Sidebar - 'places.addPlace': 'Ort/Aktivität hinzufügen', - 'places.importFile': 'Dateimport', - 'places.sidebarDrop': 'Ablegen zum Importieren', - 'places.importFileHint': '.gpx-, .kml- oder .kmz-Dateien aus Tools wie Google My Maps, Google Earth oder einem GPS-Tracker importieren.', - 'places.importFileDropHere': 'Datei auswählen oder hierher ziehen und ablegen', - 'places.importFileDropActive': 'Datei ablegen zum Auswählen', - 'places.importFileUnsupported': 'Nicht unterstützter Dateityp. Verwende .gpx, .kml oder .kmz.', - 'places.importFileTooLarge': 'Datei ist zu groß. Maximale Upload-Größe ist {maxMb} MB.', - 'places.importFileError': 'Import fehlgeschlagen', - 'places.importAllSkipped': 'Alle Orte waren bereits in der Reise.', - 'places.gpxImported': '{count} Orte aus GPX importiert', - 'places.gpxImportTypes': 'Was soll importiert werden?', - 'places.gpxImportWaypoints': 'Wegpunkte', - 'places.gpxImportRoutes': 'Routen', - 'places.gpxImportTracks': 'Tracks (mit Streckenverlauf)', - 'places.gpxImportNoneSelected': 'Wähle mindestens einen Typ zum Importieren.', - 'places.kmlImportTypes': 'Was möchtest du importieren?', - 'places.kmlImportPoints': 'Punkte (Placemarks)', - 'places.kmlImportPaths': 'Pfade (LineStrings)', - 'places.kmlImportNoneSelected': 'Wähle mindestens einen Typ aus.', - 'places.selectionCount': '{count} ausgewählt', - 'places.deleteSelected': 'Auswahl löschen', - 'places.kmlKmzImported': '{count} Orte aus KMZ/KML importiert', - 'places.urlResolved': 'Ort aus URL importiert', - 'places.importList': 'Listenimport', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importiert: {created} • Übersprungen: {skipped}', - 'places.importGoogleList': 'Google Liste', - 'places.importNaverList': 'Naver Liste', - 'places.googleListHint': 'Geteilten Google Maps Listen-Link einfügen, um alle Orte zu importieren.', - 'places.googleListImported': '{count} Orte aus "{list}" importiert', - 'places.googleListError': 'Google Maps Liste konnte nicht importiert werden', - 'places.naverListHint': 'Geteilten Naver Maps Listen-Link einfügen, um alle Orte zu importieren.', - 'places.naverListImported': '{count} Orte aus "{list}" importiert', - 'places.naverListError': 'Naver Maps Liste konnte nicht importiert werden', - 'places.viewDetails': 'Details anzeigen', - 'places.assignToDay': 'Zu welchem Tag hinzufügen?', - 'places.all': 'Alle', - 'places.unplanned': 'Ungeplant', - 'places.filterTracks': 'Tracks', - 'places.search': 'Orte suchen...', - 'places.allCategories': 'Alle Kategorien', - 'places.categoriesSelected': 'Kategorien', - 'places.clearFilter': 'Filter zurücksetzen', - 'places.count': '{count} Orte', - 'places.countSingular': '1 Ort', - 'places.allPlanned': 'Alle Orte sind eingeplant', - 'places.noneFound': 'Keine Orte gefunden', - 'places.editPlace': 'Ort bearbeiten', - 'places.formName': 'Name', - 'places.formNamePlaceholder': 'z.B. Eiffelturm', - 'places.formDescription': 'Beschreibung', - 'places.formDescriptionPlaceholder': 'Kurze Beschreibung...', - 'places.formAddress': 'Adresse', - 'places.formAddressPlaceholder': 'Straße, Stadt, Land', - 'places.formLat': 'Breitengrad (z.B. 48.8566)', - 'places.formLng': 'Längengrad (z.B. 2.3522)', - 'places.formCategory': 'Kategorie', - 'places.noCategory': 'Keine Kategorie', - 'places.categoryNamePlaceholder': 'Kategoriename', - 'places.formTime': 'Uhrzeit', - 'places.startTime': 'Startzeit', - 'places.endTime': 'Ende', - 'places.endTimeBeforeStart': 'Endzeit liegt vor der Startzeit', - 'places.timeCollision': 'Zeitliche Überschneidung mit:', - 'places.formWebsite': 'Website', - 'places.formNotes': 'Notizen', - 'places.formNotesPlaceholder': 'Persönliche Notizen...', - 'places.formReservation': 'Reservierung', - 'places.reservationNotesPlaceholder': 'Reservierungsnotizen, Bestätigungsnummer...', - 'places.mapsSearchPlaceholder': 'Ortssuche...', - 'places.mapsSearchError': 'Ortssuche fehlgeschlagen.', - 'places.loadingDetails': 'Ortsdetails werden geladen…', - 'places.osmHint': 'OpenStreetMap-Suche aktiv (ohne Bilder, Öffnungszeiten, Bewertungen). Für erweiterte Daten Google API Key in den Einstellungen hinterlegen.', - 'places.osmActive': 'Suche via OpenStreetMap (ohne Bilder, Bewertungen & Öffnungszeiten). Google API Key in den Einstellungen hinterlegen für erweiterte Daten.', - 'places.categoryCreateError': 'Fehler beim Erstellen der Kategorie', - 'places.nameRequired': 'Bitte einen Namen eingeben', - 'places.saveError': 'Fehler beim Speichern', - // Place Inspector - 'inspector.opened': 'Geöffnet', - 'inspector.closed': 'Geschlossen', - 'inspector.openingHours': 'Öffnungszeiten', - 'inspector.showHours': 'Öffnungszeiten anzeigen', - 'inspector.files': 'Dateien', - 'inspector.filesCount': '{count} Dateien', - 'inspector.removeFromDay': 'Vom Tag entfernen', - 'inspector.remove': 'Entfernen', - 'inspector.addToDay': 'Zum Tag hinzufügen', - 'inspector.confirmedRes': 'Bestätigte Reservierung', - 'inspector.pendingRes': 'Ausstehende Reservierung', - 'inspector.google': 'In Google Maps öffnen', - 'inspector.website': 'Webseite öffnen', - 'inspector.addRes': 'Reservierung', - 'inspector.editRes': 'Reservierung bearbeiten', - 'inspector.participants': 'Teilnehmer', - 'inspector.trackStats': 'Streckendaten', - - // Reservations - 'reservations.title': 'Buchungen', - 'reservations.empty': 'Keine Reservierungen vorhanden', - 'reservations.emptyHint': 'Füge Reservierungen für Flüge, Hotels und mehr hinzu', - 'reservations.add': 'Reservierung hinzufügen', - 'reservations.addManual': 'Manuelle Buchung', - 'reservations.placeHint': 'Tipp: Buchungen werden am besten direkt über einen angelegten Ort erstellt, um sie mit dem Tagesplan zu verknüpfen.', - 'reservations.confirmed': 'Bestätigt', - 'reservations.pending': 'Ausstehend', - 'reservations.summary': '{confirmed} bestätigt, {pending} ausstehend', - 'reservations.fromPlan': 'Aus Planung', - 'reservations.showFiles': 'Dateien anzeigen', - 'reservations.editTitle': 'Reservierung bearbeiten', - 'reservations.status': 'Status', - 'reservations.datetime': 'Datum & Uhrzeit', - 'reservations.startTime': 'Startzeit', - 'reservations.endTime': 'Endzeit', - 'reservations.date': 'Datum', - 'reservations.time': 'Uhrzeit', - 'reservations.timeAlt': 'Uhrzeit (alternativ, z.B. 19:30)', - 'reservations.notes': 'Notizen', - 'reservations.notesPlaceholder': 'Zusätzliche Notizen...', - 'reservations.meta.airline': 'Fluggesellschaft', - 'reservations.meta.flightNumber': 'Flugnr.', - 'reservations.meta.from': 'Von', - 'reservations.meta.to': 'Nach', - 'reservations.needsReview': 'Prüfen', - 'reservations.needsReviewHint': 'Flughafen konnte nicht automatisch erkannt werden — bitte Ort bestätigen.', - 'reservations.searchLocation': 'Bahnhof, Hafen, Adresse suchen…', - 'airport.searchPlaceholder': 'Flughafencode oder Stadt (z. B. FRA)', - 'map.connections': 'Verbindungen', - 'map.showConnections': 'Buchungsrouten anzeigen', - 'map.hideConnections': 'Buchungsrouten ausblenden', - 'reservations.meta.trainNumber': 'Zugnr.', - 'reservations.meta.platform': 'Gleis', - 'reservations.meta.seat': 'Sitzplatz', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in bis', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Unterkunft', - 'reservations.meta.pickAccommodation': 'Mit Unterkunft verknüpfen', - 'reservations.meta.noAccommodation': 'Keine', - 'reservations.meta.hotelPlace': 'Unterkunft', - 'reservations.meta.pickHotel': 'Unterkunft auswählen', - 'reservations.meta.fromDay': 'Von', - 'reservations.meta.toDay': 'Bis', - 'reservations.meta.selectDay': 'Tag wählen', - 'reservations.type.flight': 'Flug', - 'reservations.type.hotel': 'Unterkunft', - 'reservations.type.restaurant': 'Restaurant', - 'reservations.type.train': 'Zug', - 'reservations.type.car': 'Auto', - 'reservations.type.cruise': 'Kreuzfahrt', - 'reservations.type.event': 'Veranstaltung', - 'reservations.type.tour': 'Tour', - 'reservations.type.other': 'Sonstiges', - 'reservations.confirm.delete': 'Möchtest du die Reservierung "{name}" wirklich löschen?', - 'reservations.confirm.deleteTitle': 'Buchung löschen?', - 'reservations.confirm.deleteBody': '"{name}" wird unwiderruflich gelöscht.', - 'reservations.toast.updated': 'Reservierung aktualisiert', - 'reservations.toast.removed': 'Reservierung gelöscht', - 'reservations.toast.saveError': 'Fehler beim Speichern', - 'reservations.toast.updateError': 'Fehler beim Aktualisieren', - 'reservations.toast.deleteError': 'Fehler beim Löschen', - 'reservations.confirm.remove': 'Reservierung für "{name}" entfernen?', - 'reservations.toast.fileUploaded': 'Datei hochgeladen', - 'reservations.toast.uploadError': 'Fehler beim Hochladen', - 'reservations.newTitle': 'Neue Buchung', - 'reservations.bookingType': 'Art der Buchung', - 'reservations.titleLabel': 'Titel', - 'reservations.titlePlaceholder': 'z.B. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Ort / Adresse', - 'reservations.locationPlaceholder': 'Adresse, Flughafen, Hotel...', - 'reservations.confirmationCode': 'Buchungscode', - 'reservations.confirmationPlaceholder': 'z.B. ABC12345', - 'reservations.day': 'Tag', - 'reservations.noDay': 'Kein Tag', - 'reservations.place': 'Ort', - 'reservations.noPlace': 'Kein Ort', - 'reservations.pendingSave': 'wird gespeichert…', - 'reservations.uploading': 'Wird hochgeladen...', - 'reservations.attachFile': 'Datei anhängen', - 'reservations.linkExisting': 'Vorhandene verknüpfen', - 'reservations.linkAssignment': 'Mit Tagesplanung verknüpfen', - 'reservations.pickAssignment': 'Zuordnung aus dem Plan wählen...', - 'reservations.noAssignment': 'Keine Verknüpfung', - 'reservations.price': 'Preis', - 'reservations.budgetCategory': 'Budgetkategorie', - 'reservations.budgetCategoryPlaceholder': 'z.B. Transport, Unterkunft', - 'reservations.budgetCategoryAuto': 'Auto (aus Buchungstyp)', - 'reservations.budgetHint': 'Beim Speichern wird automatisch ein Budgeteintrag erstellt.', - 'reservations.departureDate': 'Abflug', - 'reservations.arrivalDate': 'Ankunft', - 'reservations.departureTime': 'Abflugzeit', - 'reservations.arrivalTime': 'Ankunftszeit', - 'reservations.pickupDate': 'Abholung', - 'reservations.returnDate': 'Rückgabe', - 'reservations.pickupTime': 'Abholzeit', - 'reservations.returnTime': 'Rückgabezeit', - 'reservations.endDate': 'Enddatum', - 'reservations.meta.departureTimezone': 'Abfl. TZ', - 'reservations.meta.arrivalTimezone': 'Ank. TZ', - 'reservations.span.departure': 'Abflug', - 'reservations.span.arrival': 'Ankunft', - 'reservations.span.inTransit': 'Unterwegs', - 'reservations.span.pickup': 'Abholung', - 'reservations.span.return': 'Rückgabe', - 'reservations.span.active': 'Aktiv', - 'reservations.span.start': 'Start', - 'reservations.span.end': 'Ende', - 'reservations.span.ongoing': 'Laufend', - 'reservations.validation.endBeforeStart': 'Enddatum/-zeit muss nach dem Startdatum/-zeit liegen', - 'reservations.addBooking': 'Buchung hinzufügen', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'CSV exportieren', - 'budget.emptyTitle': 'Noch kein Budget erstellt', - 'budget.emptyText': 'Erstelle Kategorien und Einträge, um dein Reisebudget zu planen', - 'budget.emptyPlaceholder': 'Kategoriename eingeben...', - 'budget.createCategory': 'Kategorie erstellen', - 'budget.category': 'Kategorie', - 'budget.categoryName': 'Kategoriename', - 'budget.table.name': 'Name', - 'budget.table.total': 'Gesamt', - 'budget.table.persons': 'Personen', - 'budget.table.days': 'Tage', - 'budget.table.perPerson': 'Pro Person', - 'budget.table.perDay': 'Pro Tag', - 'budget.table.perPersonDay': 'P. p / Tag', - 'budget.table.note': 'Notiz', - 'budget.table.date': 'Datum', - 'budget.newEntry': 'Neuer Eintrag', - 'budget.defaultEntry': 'Neuer Eintrag', - 'budget.defaultCategory': 'Neue Kategorie', - 'budget.total': 'Gesamt', - 'budget.totalBudget': 'Gesamtbudget', - 'budget.byCategory': 'Nach Kategorie', - 'budget.editTooltip': 'Klicken zum Bearbeiten', - 'budget.linkedToReservation': 'Verknüpft mit einer Buchung — Name dort bearbeiten', - 'budget.confirm.deleteCategory': 'Möchtest du die Kategorie "{name}" mit {count} Einträgen wirklich löschen?', - 'budget.deleteCategory': 'Kategorie löschen', - 'budget.perPerson': 'Pro Person', - 'budget.paid': 'Bezahlt', - 'budget.open': 'Offen', - 'budget.noMembers': 'Keine Teilnehmer zugewiesen', - 'budget.settlement': 'Ausgleich', - 'budget.settlementInfo': 'Klicke auf ein Mitglied-Bild bei einem Eintrag, um es grün zu markieren — das bedeutet, diese Person hat bezahlt. Der Ausgleich zeigt dann, wer wem wie viel schuldet.', - 'budget.netBalances': 'Netto-Salden', - - // Files - 'files.title': 'Dateien', - 'files.pageTitle': 'Dateien & Dokumente', - 'files.subtitle': '{count} Dateien für {trip}', - 'files.download': 'Herunterladen', - 'files.openError': 'Datei konnte nicht geöffnet werden', - 'files.downloadPdf': 'PDF herunterladen', - 'files.count': '{count} Dateien', - 'files.countSingular': '1 Datei', - 'files.uploaded': '{count} hochgeladen', - 'files.uploadError': 'Fehler beim Hochladen', - 'files.dropzone': 'Dateien hier ablegen', - 'files.dropzoneHint': 'oder klicken zum Auswählen', - 'files.allowedTypes': 'Bilder, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Wird hochgeladen...', - 'files.filterAll': 'Alle', - 'files.filterPdf': 'PDFs', - 'files.filterImages': 'Bilder', - 'files.filterDocs': 'Dokumente', - 'files.filterCollab': 'Collab Notizen', - 'files.sourceCollab': 'Aus Collab Notizen', - 'files.empty': 'Keine Dateien vorhanden', - 'files.emptyHint': 'Lade Dateien hoch, um sie mit deiner Reise zu verknüpfen', - 'files.openTab': 'In neuem Tab öffnen', - 'files.confirm.delete': 'Möchtest du diese Datei wirklich löschen?', - 'files.toast.deleted': 'Datei gelöscht', - 'files.toast.deleteError': 'Fehler beim Löschen der Datei', - 'files.sourcePlan': 'Tagesplan', - 'files.sourceBooking': 'Buchung', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Anhängen', - 'files.pasteHint': 'Du kannst auch Bilder aus der Zwischenablage einfügen (Strg+V)', - 'files.trash': 'Papierkorb', - 'files.trashEmpty': 'Papierkorb ist leer', - 'files.emptyTrash': 'Papierkorb leeren', - 'files.restore': 'Wiederherstellen', - 'files.star': 'Markieren', - 'files.unstar': 'Markierung entfernen', - 'files.assign': 'Zuweisen', - 'files.assignTitle': 'Datei zuweisen', - 'files.assignPlace': 'Ort', - 'files.assignBooking': 'Buchung', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Nicht zugewiesen', - 'files.unlink': 'Verknüpfung entfernen', - 'files.toast.trashed': 'In den Papierkorb verschoben', - 'files.toast.restored': 'Datei wiederhergestellt', - 'files.toast.trashEmptied': 'Papierkorb geleert', - 'files.toast.assigned': 'Datei zugewiesen', - 'files.toast.assignError': 'Zuweisung fehlgeschlagen', - 'files.toast.restoreError': 'Wiederherstellung fehlgeschlagen', - 'files.confirm.permanentDelete': 'Diese Datei endgültig löschen? Das kann nicht rückgängig gemacht werden.', - 'files.confirm.emptyTrash': 'Alle Dateien im Papierkorb endgültig löschen? Das kann nicht rückgängig gemacht werden.', - 'files.noteLabel': 'Notiz', - 'files.notePlaceholder': 'Notiz hinzufügen...', - - // Packing - 'packing.title': 'Packliste', - 'packing.empty': 'Packliste ist leer', - 'packing.import': 'Importieren', - 'packing.importTitle': 'Packliste importieren', - 'packing.importHint': 'Ein Eintrag pro Zeile. Format: Kategorie, Name, Gewicht in g (optional), Tasche (optional), checked/unchecked (optional)', - 'packing.importPlaceholder': 'Hygiene, Zahnbürste\nKleidung, T-Shirts, 200\nDokumente, Reisepass, , Handgepäck\nElektronik, Ladekabel, 50, Koffer, checked', - 'packing.importCsv': 'CSV/TXT laden', - 'packing.importAction': '{count} importieren', - 'packing.importSuccess': '{count} Einträge importiert', - 'packing.importError': 'Import fehlgeschlagen', - 'packing.importEmpty': 'Keine Einträge zum Importieren', - 'packing.progress': '{packed} von {total} gepackt ({percent}%)', - 'packing.clearChecked': '{count} abgehakte entfernen', - 'packing.clearCheckedShort': '{count} entfernen', - 'packing.suggestions': 'Vorschläge', - 'packing.suggestionsTitle': 'Vorschläge hinzufügen', - 'packing.allSuggested': 'Alle Vorschläge hinzugefügt', - 'packing.allPacked': 'Alles gepackt!', - 'packing.addPlaceholder': 'Neuen Gegenstand hinzufügen...', - 'packing.categoryPlaceholder': 'Kategorie...', - 'packing.filterAll': 'Alle', - 'packing.filterOpen': 'Offen', - 'packing.filterDone': 'Erledigt', - 'packing.emptyTitle': 'Packliste ist leer', - 'packing.emptyHint': 'Füge Gegenstände hinzu oder nutze die Vorschläge', - 'packing.emptyFiltered': 'Keine Gegenstände in diesem Filter', - 'packing.menuRename': 'Umbenennen', - 'packing.menuCheckAll': 'Alle abhaken', - 'packing.menuUncheckAll': 'Alle Haken entfernen', - 'packing.menuDeleteCat': 'Kategorie löschen', - 'packing.noMembers': 'Keine Mitglieder', - 'packing.addItem': 'Eintrag hinzufügen', - 'packing.addItemPlaceholder': 'Artikelname...', - 'packing.addCategory': 'Kategorie hinzufügen', - 'packing.newCategoryPlaceholder': 'Kategoriename (z.B. Kleidung)', - 'packing.applyTemplate': 'Vorlage anwenden', - 'packing.template': 'Vorlage', - 'packing.templateApplied': '{count} Einträge aus Vorlage hinzugefügt', - 'packing.templateError': 'Vorlage konnte nicht angewendet werden', - 'packing.saveAsTemplate': 'Als Vorlage speichern', - 'packing.templateName': 'Vorlagenname', - 'packing.templateSaved': 'Packliste als Vorlage gespeichert', - 'packing.bags': 'Gepäck', - 'packing.noBag': 'Nicht zugeordnet', - 'packing.totalWeight': 'Gesamtgewicht', - 'packing.bagName': 'Name...', - 'packing.addBag': 'Gepäck hinzufügen', - 'packing.changeCategory': 'Kategorie ändern', - 'packing.confirm.clearChecked': 'Möchtest du {count} abgehakte Gegenstände wirklich entfernen?', - 'packing.confirm.deleteCat': 'Möchtest du die Kategorie "{name}" mit {count} Gegenständen wirklich löschen?', - 'packing.defaultCategory': 'Sonstiges', - 'packing.toast.saveError': 'Fehler beim Speichern', - 'packing.toast.deleteError': 'Fehler beim Löschen', - 'packing.toast.renameError': 'Fehler beim Umbenennen', - 'packing.toast.addError': 'Fehler beim Hinzufügen', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Reisepass', category: 'Dokumente' }, - { name: 'Personalausweis', category: 'Dokumente' }, - { name: 'Reiseversicherung', category: 'Dokumente' }, - { name: 'Flugtickets', category: 'Dokumente' }, - { name: 'Kreditkarte', category: 'Finanzen' }, - { name: 'Bargeld', category: 'Finanzen' }, - { name: 'Visum', category: 'Dokumente' }, - { name: 'T-Shirts', category: 'Kleidung' }, - { name: 'Hosen', category: 'Kleidung' }, - { name: 'Unterwäsche', category: 'Kleidung' }, - { name: 'Socken', category: 'Kleidung' }, - { name: 'Jacke', category: 'Kleidung' }, - { name: 'Schlafkleidung', category: 'Kleidung' }, - { name: 'Badekleidung', category: 'Kleidung' }, - { name: 'Regenjacke', category: 'Kleidung' }, - { name: 'Bequeme Schuhe', category: 'Kleidung' }, - { name: 'Zahnbürste', category: 'Hygiene' }, - { name: 'Zahnpasta', category: 'Hygiene' }, - { name: 'Shampoo', category: 'Hygiene' }, - { name: 'Deo', category: 'Hygiene' }, - { name: 'Sonnencreme', category: 'Hygiene' }, - { name: 'Rasierer', category: 'Hygiene' }, - { name: 'Ladegerät', category: 'Elektronik' }, - { name: 'Powerbank', category: 'Elektronik' }, - { name: 'Kopfhörer', category: 'Elektronik' }, - { name: 'Reiseadapter', category: 'Elektronik' }, - { name: 'Kamera', category: 'Elektronik' }, - { name: 'Schmerzmittel', category: 'Gesundheit' }, - { name: 'Pflaster', category: 'Gesundheit' }, - { name: 'Desinfektionsmittel', category: 'Gesundheit' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Reise teilen', - 'members.inviteUser': 'Benutzer einladen', - 'members.selectUser': 'Benutzer auswählen…', - 'members.invite': 'Einladen', - 'members.allHaveAccess': 'Alle Benutzer haben bereits Zugriff.', - 'members.access': 'Zugriff', - 'members.person': 'Person', - 'members.persons': 'Personen', - 'members.you': 'du', - 'members.owner': 'Eigentümer', - 'members.leaveTrip': 'Reise verlassen', - 'members.removeAccess': 'Zugriff entfernen', - 'members.confirmLeave': 'Reise verlassen? Du verlierst den Zugriff.', - 'members.confirmRemove': 'Zugriff für diesen Benutzer entfernen?', - 'members.loadError': 'Fehler beim Laden der Mitglieder', - 'members.added': 'hinzugefügt', - 'members.addError': 'Fehler beim Hinzufügen', - 'members.removed': 'Mitglied entfernt', - 'members.removeError': 'Fehler beim Entfernen', - - // Categories (Admin) - 'categories.title': 'Kategorien', - 'categories.subtitle': 'Kategorien für Orte verwalten', - 'categories.new': 'Neue Kategorie', - 'categories.empty': 'Keine Kategorien vorhanden', - 'categories.namePlaceholder': 'Kategoriename', - 'categories.icon': 'Symbol', - 'categories.color': 'Farbe', - 'categories.customColor': 'Eigene Farbe wählen', - 'categories.preview': 'Vorschau', - 'categories.defaultName': 'Kategorie', - 'categories.update': 'Aktualisieren', - 'categories.create': 'Erstellen', - 'categories.confirm.delete': 'Kategorie löschen? Orte dieser Kategorie werden nicht gelöscht.', - 'categories.toast.loadError': 'Fehler beim Laden der Kategorien', - 'categories.toast.nameRequired': 'Bitte einen Namen eingeben', - 'categories.toast.updated': 'Kategorie aktualisiert', - 'categories.toast.created': 'Kategorie erstellt', - 'categories.toast.saveError': 'Fehler beim Speichern', - 'categories.toast.deleted': 'Kategorie gelöscht', - 'categories.toast.deleteError': 'Fehler beim Löschen', - - // Backup (Admin) - 'backup.title': 'Datensicherung', - 'backup.subtitle': 'Datenbank und alle hochgeladenen Dateien', - 'backup.refresh': 'Aktualisieren', - 'backup.upload': 'Backup hochladen', - 'backup.uploading': 'Wird hochgeladen…', - 'backup.create': 'Backup erstellen', - 'backup.creating': 'Erstelle…', - 'backup.empty': 'Noch keine Backups vorhanden', - 'backup.createFirst': 'Erstes Backup erstellen', - 'backup.download': 'Herunterladen', - 'backup.restore': 'Wiederherstellen', - 'backup.confirm.restore': 'Backup "{name}" wiederherstellen?\n\nAlle aktuellen Daten werden durch den Backup-Stand ersetzt.', - 'backup.confirm.uploadRestore': 'Backup-Datei "{name}" hochladen und wiederherstellen?\n\nAlle aktuellen Daten werden überschrieben.', - 'backup.confirm.delete': 'Backup "{name}" löschen?', - 'backup.toast.loadError': 'Fehler beim Laden der Backups', - 'backup.toast.created': 'Backup erfolgreich erstellt', - 'backup.toast.createError': 'Fehler beim Erstellen des Backups', - 'backup.toast.restored': 'Backup wiederhergestellt. Seite wird neu geladen…', - 'backup.toast.restoreError': 'Fehler beim Wiederherstellen', - 'backup.toast.uploadError': 'Fehler beim Hochladen', - 'backup.toast.deleted': 'Backup gelöscht', - 'backup.toast.deleteError': 'Fehler beim Löschen', - 'backup.toast.downloadError': 'Download fehlgeschlagen', - 'backup.toast.settingsSaved': 'Auto-Backup Einstellungen gespeichert', - 'backup.toast.settingsError': 'Fehler beim Speichern der Einstellungen', - 'backup.auto.title': 'Auto-Backup', - 'backup.auto.subtitle': 'Automatische Sicherung nach Zeitplan', - 'backup.auto.enable': 'Auto-Backup aktivieren', - 'backup.auto.enableHint': 'Backups werden automatisch nach dem gewählten Zeitplan erstellt', - 'backup.auto.interval': 'Intervall', - 'backup.auto.hour': 'Ausführung um', - 'backup.auto.hourHint': 'Lokale Serverzeit ({format}-Format)', - 'backup.auto.dayOfWeek': 'Wochentag', - 'backup.auto.dayOfMonth': 'Tag des Monats', - 'backup.auto.dayOfMonthHint': 'Auf 1–28 beschränkt, um mit allen Monaten kompatibel zu sein', - 'backup.auto.scheduleSummary': 'Zeitplan', - 'backup.auto.summaryDaily': 'Täglich um {hour}:00', - 'backup.auto.summaryWeekly': 'Jeden {day} um {hour}:00', - 'backup.auto.summaryMonthly': 'Am {day}. jedes Monats um {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Auto-Backup wird über Docker-Umgebungsvariablen konfiguriert. Ändern Sie Ihre docker-compose.yml und starten Sie den Container neu.', - 'backup.auto.copyEnv': 'Docker-Umgebungsvariablen kopieren', - 'backup.auto.envCopied': 'Docker-Umgebungsvariablen in die Zwischenablage kopiert', - 'backup.auto.keepLabel': 'Alte Backups löschen nach', - 'backup.dow.sunday': 'So', - 'backup.dow.monday': 'Mo', - 'backup.dow.tuesday': 'Di', - 'backup.dow.wednesday': 'Mi', - 'backup.dow.thursday': 'Do', - 'backup.dow.friday': 'Fr', - 'backup.dow.saturday': 'Sa', - 'backup.interval.hourly': 'Stündlich', - 'backup.interval.daily': 'Täglich', - 'backup.interval.weekly': 'Wöchentlich', - 'backup.interval.monthly': 'Monatlich', - 'backup.keep.1day': '1 Tag', - 'backup.keep.3days': '3 Tage', - 'backup.keep.7days': '7 Tage', - 'backup.keep.14days': '14 Tage', - 'backup.keep.30days': '30 Tage', - 'backup.keep.forever': 'Immer behalten', - - // Photos - 'photos.title': 'Fotos', - 'photos.subtitle': '{count} Fotos für {trip}', - 'photos.dropHere': 'Fotos hier ablegen...', - 'photos.dropHereActive': 'Fotos hier ablegen', - 'photos.captionForAll': 'Beschriftung (für alle)', - 'photos.captionPlaceholder': 'Optionale Beschriftung...', - 'photos.addCaption': 'Beschriftung hinzufügen...', - 'photos.allDays': 'Alle Tage', - 'photos.noPhotos': 'Noch keine Fotos', - 'photos.uploadHint': 'Lade deine Reisefotos hoch', - 'photos.clickToSelect': 'oder klicken zum Auswählen', - 'photos.linkPlace': 'Ort verknüpfen', - 'photos.noPlace': 'Kein Ort', - 'photos.uploadN': '{n} Foto(s) hochladen', - 'photos.linkDay': 'Tag verknüpfen', - 'photos.noDay': 'Kein Tag', - 'photos.dayLabel': 'Tag {number}', - 'photos.photoSelected': 'Foto ausgewählt', - 'photos.photosSelected': 'Fotos ausgewählt', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · bis zu 30 Fotos', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Backup wiederherstellen?', - 'backup.restoreWarning': 'Alle aktuellen Daten (Reisen, Orte, Benutzer, Uploads) werden unwiderruflich durch das Backup ersetzt. Dieser Vorgang kann nicht rückgängig gemacht werden.', - 'backup.restoreTip': 'Tipp: Erstelle zuerst ein Backup des aktuellen Stands, bevor du wiederherstellst.', - 'backup.restoreConfirm': 'Ja, wiederherstellen', - - // PDF - 'pdf.travelPlan': 'Reiseplan', - 'pdf.planned': 'Eingeplant', - 'pdf.costLabel': 'Kosten EUR', - 'pdf.preview': 'PDF Vorschau', - 'pdf.saveAsPdf': 'Als PDF speichern', - - // Planner - 'planner.places': 'Orte', - 'planner.bookings': 'Buchungen', - 'planner.packingList': 'Packliste', - 'planner.documents': 'Dokumente', - 'planner.dayPlan': 'Tagesplan', - 'planner.reservations': 'Reservierungen', - 'planner.minTwoPlaces': 'Mindestens 2 Orte mit Koordinaten benötigt', - 'planner.noGeoPlaces': 'Keine Orte mit Koordinaten vorhanden', - 'planner.routeCalculated': 'Route berechnet', - 'planner.routeCalcFailed': 'Route konnte nicht berechnet werden', - 'planner.routeError': 'Fehler bei der Routenberechnung', - 'planner.icsExportFailed': 'ICS-Export fehlgeschlagen', - 'planner.routeOptimized': 'Route optimiert', - 'planner.reservationUpdated': 'Reservierung aktualisiert', - 'planner.reservationAdded': 'Reservierung hinzugefügt', - 'planner.confirmDeleteReservation': 'Reservierung löschen?', - 'planner.reservationDeleted': 'Reservierung gelöscht', - 'planner.days': 'Tage', - 'planner.allPlaces': 'Alle Orte', - 'planner.totalPlaces': '{n} Orte gesamt', - 'planner.noDaysPlanned': 'Noch keine Tage geplant', - 'planner.editTrip': 'Reise bearbeiten \u2192', - 'planner.placeOne': '1 Ort', - 'planner.placeN': '{n} Orte', - 'planner.addNote': 'Notiz hinzufügen', - 'planner.noEntries': 'Keine Einträge für diesen Tag', - 'planner.addPlace': 'Ort/Aktivität hinzufügen', - 'planner.addPlaceShort': '+ Ort/Aktivität hinzufügen', - 'planner.resPending': 'Reservierung ausstehend · ', - 'planner.resConfirmed': 'Reservierung bestätigt · ', - 'planner.notePlaceholder': 'Notiz\u2026', - 'planner.noteTimePlaceholder': 'Zeit (optional)', - 'planner.noteExamplePlaceholder': 'z.B. S3 um 14:30 ab Hauptbahnhof, Fähre ab Pier 7, Mittagspause\u2026', - 'planner.totalCost': 'Gesamtkosten', - 'planner.searchPlaces': 'Orte suchen\u2026', - 'planner.allCategories': 'Alle Kategorien', - 'planner.noPlacesFound': 'Keine Orte gefunden', - 'planner.addFirstPlace': 'Ersten Ort hinzufügen', - 'planner.noReservations': 'Keine Reservierungen', - 'planner.addFirstReservation': 'Erste Reservierung hinzufügen', - 'planner.new': 'Neu', - 'planner.addToDay': '+ Tag', - 'planner.calculating': 'Berechne\u2026', - 'planner.route': 'Route', - 'planner.optimize': 'Optimieren', - 'planner.openGoogleMaps': 'In Google Maps öffnen', - 'planner.selectDayHint': 'Wähle einen Tag aus der linken Liste um den Tagesplan zu sehen', - 'planner.noPlacesForDay': 'Noch keine Orte für diesen Tag', - 'planner.addPlacesLink': 'Orte hinzufügen \u2192', - 'planner.minTotal': 'Min. gesamt', - 'planner.noReservation': 'Keine Reservierung', - 'planner.removeFromDay': 'Aus Tag entfernen', - 'planner.addToThisDay': 'Zum Tag hinzufügen', - 'planner.overview': 'Gesamtübersicht', - 'planner.noDays': 'Noch keine Tage', - 'planner.editTripToAddDays': 'Reise bearbeiten um Tage hinzuzufügen', - 'planner.dayCount': '{n} Tage', - 'planner.clickToUnlock': 'Klicken zum Entsperren', - 'planner.keepPosition': 'Position bei Routenoptimierung beibehalten', - 'planner.dayDetails': 'Tagesdetails', - 'planner.dayN': 'Tag {n}', - - // Dashboard Stats - 'stats.countries': 'Länder', - 'stats.cities': 'Städte', - 'stats.trips': 'Reisen', - 'stats.places': 'Orte', - 'stats.worldProgress': 'Weltfortschritt', - 'stats.visited': 'besucht', - 'stats.remaining': 'verbleibend', - 'stats.visitedCountries': 'Besuchte Länder', - - // Day Detail Panel - 'day.precipProb': 'Regenwahrscheinlichkeit', - 'day.precipitation': 'Niederschlag', - 'day.wind': 'Wind', - 'day.sunrise': 'Sonnenaufgang', - 'day.sunset': 'Sonnenuntergang', - 'day.hourlyForecast': 'Stündliche Vorhersage', - 'day.climateHint': 'Historische Durchschnittswerte — echte Vorhersage verfügbar innerhalb von 16 Tagen vor diesem Datum.', - 'day.noWeather': 'Keine Wetterdaten verfügbar. Füge einen Ort mit Koordinaten hinzu.', - 'day.overview': 'Tagesübersicht', - 'day.accommodation': 'Unterkunft', - 'day.addAccommodation': 'Unterkunft hinzufügen', - 'day.hotelDayRange': 'Auf Tage anwenden', - 'day.noPlacesForHotel': 'Füge zuerst Orte zu deiner Reise hinzu', - 'day.allDays': 'Alle', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Bis', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Bestätigung', - 'day.editAccommodation': 'Unterkunft bearbeiten', - 'day.reservations': 'Reservierungen', - - // Photos / Immich - 'memories.title': 'Fotos', - 'memories.notConnected': 'Immich nicht verbunden', - 'memories.notConnectedHint': 'Verbinde deine Immich-Instanz in den Einstellungen, um deine Reisefotos hier zu sehen.', - 'memories.notConnectedMultipleHint': 'Verbinde einen dieser Fotoanbieter: {provider_names} in den Einstellungen, um Fotos zu dieser Reise hinzufügen zu können.', - 'memories.noDates': 'Füge Daten zu deiner Reise hinzu, um Fotos zu laden.', - 'memories.noPhotos': 'Keine Fotos gefunden', - 'memories.noPhotosHint': 'Keine Fotos in Immich für den Zeitraum dieser Reise gefunden.', - 'memories.photosFound': 'Fotos', - 'memories.fromOthers': 'von anderen', - 'memories.sharePhotos': 'Fotos teilen', - 'memories.sharing': 'Wird geteilt', - 'memories.reviewTitle': 'Deine Fotos prüfen', - 'memories.reviewHint': 'Klicke auf Fotos, um sie vom Teilen auszuschließen.', - 'memories.shareCount': '{count} Fotos teilen', - 'memories.providerUrl': 'Server-URL', - 'memories.providerApiKey': 'API-Schlüssel', - 'memories.providerUsername': 'Benutzername', - 'memories.providerPassword': 'Passwort', - 'memories.providerOTP': 'MFA-Code (falls aktiviert)', - 'memories.skipSSLVerification': 'SSL-Zertifikatsprüfung überspringen', - 'memories.immichAutoUpload': 'Journey-Fotos beim Upload auch zu Immich spiegeln', - 'memories.providerUrlHintSynology': 'Füge den Fotos-App-Pfad in die URL ein, z.B. https://nas:5001/photo', - 'memories.testConnection': 'Verbindung testen', - 'memories.testShort': 'Testen', - 'memories.testFirst': 'Verbindung zuerst testen', - 'memories.connected': 'Verbunden', - 'memories.disconnected': 'Nicht verbunden', - 'memories.connectionSuccess': 'Verbindung zu Immich hergestellt', - 'memories.connectionError': 'Verbindung zu Immich fehlgeschlagen', - 'memories.saved': '{provider_name}-Einstellungen gespeichert', - 'memories.providerDisconnectedBanner': 'Deine {provider_name}-Verbindung wurde getrennt. Verbinde erneut in den Einstellungen, um Fotos anzuzeigen.', - 'memories.saveError': '{provider_name}-Einstellungen konnten nicht gespeichert werden', - 'memories.saveRouteNotConfigured': 'Speicherroute ist für diesen Anbieter nicht konfiguriert', - 'memories.testRouteNotConfigured': 'Testroute ist für diesen Anbieter nicht konfiguriert', - 'memories.fillRequiredFields': 'Bitte füllen Sie alle Pflichtfelder aus', - 'memories.addPhotos': 'Fotos hinzufügen', - 'memories.linkAlbum': 'Album verknüpfen', - 'memories.selectAlbum': 'Immich-Album auswählen', - 'memories.selectAlbumMultiple': 'Album auswählen', - 'memories.noAlbums': 'Keine Alben gefunden', - 'memories.syncAlbum': 'Album synchronisieren', - 'memories.unlinkAlbum': 'Album trennen', - 'memories.photos': 'Fotos', - 'memories.selectPhotos': 'Fotos aus Immich auswählen', - 'memories.selectPhotosMultiple': 'Fotos auswählen', - 'memories.selectHint': 'Tippe auf Fotos um sie auszuwählen.', - 'memories.selected': 'ausgewählt', - 'memories.addSelected': '{count} Fotos hinzufügen', - 'memories.alreadyAdded': 'Hinzugefügt', - 'memories.private': 'Privat', - 'memories.stopSharing': 'Nicht mehr teilen', - 'memories.oldest': 'Älteste zuerst', - 'memories.newest': 'Neueste zuerst', - 'memories.allLocations': 'Alle Orte', - 'memories.tripDates': 'Trip-Zeitraum', - 'memories.allPhotos': 'Alle Fotos', - 'memories.confirmShareTitle': 'Mit Reisebegleitern teilen?', - 'memories.confirmShareHint': '{count} Fotos werden für alle Mitglieder dieses Trips sichtbar. Du kannst einzelne Fotos nachträglich auf privat setzen.', - 'memories.confirmShareButton': 'Fotos teilen', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Notizen', - 'collab.tabs.polls': 'Umfragen', - 'collab.whatsNext.title': 'Nächste', - 'collab.whatsNext.today': 'Heute', - 'collab.whatsNext.tomorrow': 'Morgen', - 'collab.whatsNext.empty': 'Keine anstehenden Aktivitäten', - 'collab.whatsNext.until': 'bis', - 'collab.whatsNext.emptyHint': 'Aktivitäten mit Uhrzeit erscheinen hier', - 'collab.chat.send': 'Senden', - 'collab.chat.placeholder': 'Nachricht eingeben...', - 'collab.chat.empty': 'Starte die Unterhaltung', - 'collab.chat.emptyHint': 'Nachrichten werden mit allen Reiseteilnehmern geteilt', - 'collab.chat.emptyDesc': 'Teile Ideen, Pläne und Updates mit deiner Reisegruppe', - 'collab.chat.today': 'Heute', - 'collab.chat.yesterday': 'Gestern', - 'collab.chat.deletedMessage': 'hat eine Nachricht gelöscht', - 'collab.chat.reply': 'Antworten', - 'collab.chat.loadMore': 'Ältere Nachrichten laden', - 'collab.chat.justNow': 'gerade eben', - 'collab.chat.minutesAgo': 'vor {n} Min.', - 'collab.chat.hoursAgo': 'vor {n} Std.', - 'collab.notes.title': 'Notizen', - 'collab.notes.new': 'Neue Notiz', - 'collab.notes.empty': 'Noch keine Notizen', - 'collab.notes.emptyHint': 'Halte Ideen und Pläne fest', - 'collab.notes.all': 'Alle', - 'collab.notes.titlePlaceholder': 'Notiztitel', - 'collab.notes.contentPlaceholder': 'Schreibe etwas...', - 'collab.notes.categoryPlaceholder': 'Kategorie', - 'collab.notes.newCategory': 'Neue Kategorie...', - 'collab.notes.category': 'Kategorie', - 'collab.notes.noCategory': 'Keine Kategorie', - 'collab.notes.color': 'Farbe', - 'collab.notes.save': 'Speichern', - 'collab.notes.cancel': 'Abbrechen', - 'collab.notes.edit': 'Bearbeiten', - 'collab.notes.delete': 'Löschen', - 'collab.notes.pin': 'Anheften', - 'collab.notes.unpin': 'Loslösen', - 'collab.notes.daysAgo': 'vor {n} T.', - 'collab.notes.categorySettings': 'Kategorien verwalten', - 'collab.notes.create': 'Erstellen', - 'collab.notes.website': 'Website', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Dateien anhängen', - 'collab.notes.noCategoriesYet': 'Noch keine Kategorien', - 'collab.notes.emptyDesc': 'Erstelle eine Notiz um loszulegen', - 'collab.polls.title': 'Umfragen', - 'collab.polls.new': 'Neue Umfrage', - 'collab.polls.empty': 'Noch keine Umfragen', - 'collab.polls.emptyHint': 'Frage die Gruppe und stimmt gemeinsam ab', - 'collab.polls.question': 'Frage', - 'collab.polls.questionPlaceholder': 'Was sollen wir machen?', - 'collab.polls.addOption': '+ Option hinzufügen', - 'collab.polls.optionPlaceholder': 'Option {n}', - 'collab.polls.create': 'Umfrage erstellen', - 'collab.polls.close': 'Schließen', - 'collab.polls.closed': 'Geschlossen', - 'collab.polls.votes': '{n} Stimmen', - 'collab.polls.vote': '{n} Stimme', - 'collab.polls.multipleChoice': 'Mehrfachauswahl', - 'collab.polls.multiChoice': 'Mehrfachauswahl', - 'collab.polls.deadline': 'Frist', - 'collab.polls.option': 'Option', - 'collab.polls.options': 'Optionen', - 'collab.polls.delete': 'Löschen', - 'collab.polls.closedSection': 'Geschlossen', - - // Permissions - 'admin.tabs.permissions': 'Berechtigungen', - 'perm.title': 'Berechtigungseinstellungen', - 'perm.subtitle': 'Steuern Sie, wer Aktionen in der Anwendung ausführen kann', - 'perm.saved': 'Berechtigungseinstellungen gespeichert', - 'perm.resetDefaults': 'Auf Standard zurücksetzen', - 'perm.customized': 'angepasst', - 'perm.level.admin': 'Nur Administrator', - 'perm.level.tripOwner': 'Reise-Eigentümer', - 'perm.level.tripMember': 'Reise-Mitglieder', - 'perm.level.everybody': 'Alle', - 'perm.cat.trip': 'Reiseverwaltung', - 'perm.cat.members': 'Mitgliederverwaltung', - 'perm.cat.files': 'Dateien', - 'perm.cat.content': 'Inhalte & Zeitplan', - 'perm.cat.extras': 'Budget, Packlisten & Zusammenarbeit', - 'perm.action.trip_create': 'Reisen erstellen', - 'perm.action.trip_edit': 'Reisedetails bearbeiten', - 'perm.action.trip_delete': 'Reisen löschen', - 'perm.action.trip_archive': 'Reisen archivieren / dearchivieren', - 'perm.action.trip_cover_upload': 'Titelbild hochladen', - 'perm.action.member_manage': 'Mitglieder hinzufügen / entfernen', - 'perm.action.file_upload': 'Dateien hochladen', - 'perm.action.file_edit': 'Datei-Metadaten bearbeiten', - 'perm.action.file_delete': 'Dateien löschen', - 'perm.action.place_edit': 'Orte hinzufügen / bearbeiten / löschen', - 'perm.action.day_edit': 'Tage, Notizen & Zuweisungen bearbeiten', - 'perm.action.reservation_edit': 'Reservierungen verwalten', - 'perm.action.budget_edit': 'Budget verwalten', - 'perm.action.packing_edit': 'Packlisten verwalten', - 'perm.action.collab_edit': 'Zusammenarbeit (Notizen, Umfragen, Chat)', - 'perm.action.share_manage': 'Freigabelinks verwalten', - 'perm.actionHint.trip_create': 'Wer kann neue Reisen erstellen', - 'perm.actionHint.trip_edit': 'Wer kann Reisename, Daten, Beschreibung und Währung ändern', - 'perm.actionHint.trip_delete': 'Wer kann eine Reise dauerhaft löschen', - 'perm.actionHint.trip_archive': 'Wer kann eine Reise archivieren oder dearchivieren', - 'perm.actionHint.trip_cover_upload': 'Wer kann das Titelbild hochladen oder ändern', - 'perm.actionHint.member_manage': 'Wer kann Reise-Mitglieder einladen oder entfernen', - 'perm.actionHint.file_upload': 'Wer kann Dateien zu einer Reise hochladen', - 'perm.actionHint.file_edit': 'Wer kann Dateibeschreibungen und Links bearbeiten', - 'perm.actionHint.file_delete': 'Wer kann Dateien in den Papierkorb verschieben oder dauerhaft löschen', - 'perm.actionHint.place_edit': 'Wer kann Orte hinzufügen, bearbeiten oder löschen', - 'perm.actionHint.day_edit': 'Wer kann Tage, Tagesnotizen und Ort-Zuweisungen bearbeiten', - 'perm.actionHint.reservation_edit': 'Wer kann Reservierungen erstellen, bearbeiten oder löschen', - 'perm.actionHint.budget_edit': 'Wer kann Budgetposten erstellen, bearbeiten oder löschen', - 'perm.actionHint.packing_edit': 'Wer kann Packstücke und Taschen verwalten', - 'perm.actionHint.collab_edit': 'Wer kann Notizen, Umfragen erstellen und Nachrichten senden', - 'perm.actionHint.share_manage': 'Wer kann öffentliche Freigabelinks erstellen oder löschen', - // Undo - 'undo.button': 'Rückgängig', - 'undo.tooltip': 'Rückgängig: {action}', - 'undo.assignPlace': 'Ort einem Tag zugewiesen', - 'undo.removeAssignment': 'Ort von Tag entfernt', - 'undo.reorder': 'Orte neu sortiert', - 'undo.optimize': 'Route optimiert', - 'undo.deletePlace': 'Ort gelöscht', - 'undo.deletePlaces': 'Orte gelöscht', - 'undo.moveDay': 'Ort zu anderem Tag verschoben', - 'undo.lock': 'Ortssperre umgeschaltet', - 'undo.importGpx': 'GPX-Import', - 'undo.importKeyholeMarkup': 'KMZ/KML-Import', - 'undo.importGoogleList': 'Google Maps-Import', - 'undo.importNaverList': 'Naver Maps-Import', - - // Notifications - 'notifications.title': 'Benachrichtigungen', - 'notifications.markAllRead': 'Alle als gelesen markieren', - 'notifications.deleteAll': 'Alle löschen', - 'notifications.showAll': 'Alle Benachrichtigungen anzeigen', - 'notifications.empty': 'Keine Benachrichtigungen', - 'notifications.emptyDescription': 'Sie sind auf dem neuesten Stand!', - 'notifications.all': 'Alle', - 'notifications.unreadOnly': 'Ungelesen', - 'notifications.markRead': 'Als gelesen markieren', - 'notifications.markUnread': 'Als ungelesen markieren', - 'notifications.delete': 'Löschen', - 'notifications.system': 'System', - 'notifications.synologySessionCleared.title': 'Synology Photos getrennt', - 'notifications.synologySessionCleared.text': 'Dein Server oder Konto hat sich geändert — gehe zu Einstellungen, um deine Verbindung erneut zu testen.', - 'memories.error.loadAlbums': 'Alben konnten nicht geladen werden', - 'memories.error.linkAlbum': 'Album konnte nicht verknüpft werden', - 'memories.error.unlinkAlbum': 'Album konnte nicht getrennt werden', - 'memories.error.syncAlbum': 'Album konnte nicht synchronisiert werden', - 'memories.error.loadPhotos': 'Fotos konnten nicht geladen werden', - 'memories.error.addPhotos': 'Fotos konnten nicht hinzugefügt werden', - 'memories.error.removePhoto': 'Foto konnte nicht entfernt werden', - 'memories.error.toggleSharing': 'Freigabe konnte nicht aktualisiert werden', - 'undo.addPlace': 'Ort hinzugefügt', - 'undo.done': 'Rückgängig gemacht: {action}', - 'notifications.test.title': 'Testbenachrichtigung von {actor}', - 'notifications.test.text': 'Dies ist eine einfache Testbenachrichtigung.', - 'notifications.test.booleanTitle': '{actor} bittet um Ihre Zustimmung', - 'notifications.test.booleanText': 'Dies ist eine boolesche Testbenachrichtigung.', - 'notifications.test.accept': 'Genehmigen', - 'notifications.test.decline': 'Ablehnen', - 'notifications.test.navigateTitle': 'Etwas ansehen', - 'notifications.test.navigateText': 'Dies ist eine Navigations-Testbenachrichtigung.', - 'notifications.test.goThere': 'Dorthin', - 'notifications.test.adminTitle': 'Admin-Broadcast', - 'notifications.test.adminText': '{actor} hat eine Testbenachrichtigung an alle Admins gesendet.', - 'notifications.test.tripTitle': '{actor} hat in Ihrer Reise gepostet', - 'notifications.test.tripText': 'Testbenachrichtigung für Reise "{trip}".', - - // Todo - 'todo.subtab.packing': 'Packliste', - 'todo.subtab.todo': 'Aufgaben', - 'todo.completed': 'erledigt', - 'todo.filter.all': 'Alle', - 'todo.filter.open': 'Offen', - 'todo.filter.done': 'Erledigt', - 'todo.uncategorized': 'Ohne Kategorie', - 'todo.namePlaceholder': 'Aufgabenname', - 'todo.descriptionPlaceholder': 'Beschreibung (optional)', - 'todo.unassigned': 'Nicht zugewiesen', - 'todo.noCategory': 'Keine Kategorie', - 'todo.hasDescription': 'Hat Beschreibung', - 'todo.addItem': 'Neue Aufgabe hinzufügen', - 'todo.sidebar.sortBy': 'Sortieren nach', - 'todo.priority': 'Priorität', - 'todo.newCategoryLabel': 'neu', - 'budget.categoriesLabel': 'Kategorien', - 'todo.newCategory': 'Kategoriename', - 'todo.addCategory': 'Kategorie hinzufügen', - 'todo.newItem': 'Neue Aufgabe', - 'todo.empty': 'Noch keine Aufgaben. Erstelle eine Aufgabe um loszulegen!', - 'todo.filter.my': 'Meine Aufgaben', - 'todo.filter.overdue': 'Überfällig', - 'todo.sidebar.tasks': 'Aufgaben', - 'todo.sidebar.categories': 'Kategorien', - 'todo.detail.title': 'Aufgabe', - 'todo.detail.description': 'Beschreibung', - 'todo.detail.category': 'Kategorie', - 'todo.detail.dueDate': 'Fällig am', - 'todo.detail.assignedTo': 'Zuständig', - 'todo.detail.delete': 'Löschen', - 'todo.detail.save': 'Speichern', - 'todo.sortByPrio': 'Priorität', - 'todo.detail.priority': 'Priorität', - 'todo.detail.noPriority': 'Keine', - 'todo.detail.create': 'Aufgabe erstellen', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Neue Version verfügbar', - 'settings.notificationPreferences.noChannels': 'Keine Benachrichtigungskanäle konfiguriert. Bitte einen Administrator, E-Mail- oder Webhook-Benachrichtigungen einzurichten.', - 'settings.webhookUrl.label': 'Webhook-URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Gib deine Discord-, Slack- oder benutzerdefinierte Webhook-URL ein, um Benachrichtigungen zu erhalten.', - 'settings.webhookUrl.saved': 'Webhook-URL gespeichert', - 'settings.webhookUrl.test': 'Testen', - 'settings.webhookUrl.testSuccess': 'Test-Webhook erfolgreich gesendet', - 'settings.webhookUrl.testFailed': 'Test-Webhook fehlgeschlagen', - 'settings.ntfyUrl.topicLabel': 'Ntfy-Thema', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy-Server-URL (optional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Gib dein Ntfy-Thema ein, um Push-Benachrichtigungen zu erhalten. Lasse das Server-Feld leer, um den vom Administrator konfigurierten Standard zu verwenden.', - 'settings.ntfyUrl.tokenLabel': 'Zugriffstoken (optional)', - 'settings.ntfyUrl.tokenHint': 'Erforderlich für passwortgeschützte Themen.', - 'settings.ntfyUrl.saved': 'Ntfy-Einstellungen gespeichert', - 'settings.ntfyUrl.test': 'Testen', - 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-Benachrichtigung erfolgreich gesendet', - 'settings.ntfyUrl.testFailed': 'Test-Ntfy-Benachrichtigung fehlgeschlagen', - 'settings.ntfyUrl.tokenCleared': 'Zugriffstoken gelöscht', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'In-App-Benachrichtigungen sind immer aktiv und können nicht global deaktiviert werden.', - 'admin.notifications.adminWebhookPanel.title': 'Admin-Webhook', - 'admin.notifications.adminWebhookPanel.hint': 'Dieser Webhook wird ausschließlich für Admin-Benachrichtigungen verwendet (z. B. Versions-Updates). Er ist unabhängig von den Benutzer-Webhooks und sendet automatisch, wenn eine URL konfiguriert ist.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin-Webhook-URL gespeichert', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Test-Webhook erfolgreich gesendet', - 'admin.notifications.adminWebhookPanel.testFailed': 'Test-Webhook fehlgeschlagen', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-Webhook sendet automatisch, wenn eine URL konfiguriert ist', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Erlaubt Benutzern, eigene ntfy-Themen für Push-Benachrichtigungen zu konfigurieren. Legen Sie unten den Standardserver fest, um die Benutzereinstellungen vorauszufüllen.', - 'admin.notifications.testNtfy': 'Test-Ntfy senden', - 'admin.notifications.testNtfySuccess': 'Test-Ntfy erfolgreich gesendet', - 'admin.notifications.testNtfyFailed': 'Test-Ntfy fehlgeschlagen', - 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Dieses Ntfy-Thema wird ausschließlich für Admin-Benachrichtigungen verwendet (z. B. Versions-Updates). Es ist unabhängig von Benutzer-Themen und sendet immer, wenn es konfiguriert ist.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-Server-URL', - 'admin.notifications.adminNtfyPanel.serverHint': 'Wird auch als Standardserver für Benutzer-ntfy-Benachrichtigungen verwendet. Leer lassen für ntfy.sh. Benutzer können dies in ihren eigenen Einstellungen überschreiben.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-Thema', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Zugriffstoken (optional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Admin-Zugriffstoken gelöscht', - 'admin.notifications.adminNtfyPanel.saved': 'Admin-Ntfy-Einstellungen gespeichert', - 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy senden', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test-Ntfy erfolgreich gesendet', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy fehlgeschlagen', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin-Ntfy sendet immer, wenn ein Thema konfiguriert ist', - 'admin.notifications.adminNotificationsHint': 'Konfiguriere, welche Kanäle Admin-Benachrichtigungen liefern (z. B. Versions-Updates). Der Webhook sendet automatisch, wenn eine Admin-Webhook-URL gesetzt ist.', - 'admin.notifications.tripReminders.title': 'Reiseerinnerungen', - 'admin.notifications.tripReminders.hint': 'Sendet eine Erinnerungsbenachrichtigung vor Reisebeginn (erfordert gesetzte Erinnerungstage bei der Reise).', - 'admin.notifications.tripReminders.enabled': 'Reiseerinnerungen aktiviert', - 'admin.notifications.tripReminders.disabled': 'Reiseerinnerungen deaktiviert', - 'admin.tabs.notifications': 'Benachrichtigungen', - 'notifications.versionAvailable.title': 'Update verfügbar', - 'notifications.versionAvailable.text': 'TREK {version} ist jetzt verfügbar.', - 'notifications.versionAvailable.button': 'Details anzeigen', - 'notif.test.title': '[Test] Benachrichtigung', - 'notif.test.simple.text': 'Dies ist eine einfache Testbenachrichtigung.', - 'notif.test.boolean.text': 'Akzeptierst du diese Testbenachrichtigung?', - 'notif.test.navigate.text': 'Klicke unten, um zum Dashboard zu navigieren.', - - // Notifications - 'notif.trip_invite.title': 'Reiseeinladung', - 'notif.trip_invite.text': '{actor} hat dich zu {trip} eingeladen', - 'notif.booking_change.title': 'Buchung aktualisiert', - 'notif.booking_change.text': '{actor} hat eine Buchung in {trip} aktualisiert', - 'notif.trip_reminder.title': 'Reiseerinnerung', - 'notif.trip_reminder.text': 'Deine Reise {trip} steht bald an!', - 'notif.todo_due.title': 'Aufgabe fällig', - 'notif.todo_due.text': '{todo} in {trip} ist am {due} fällig', - 'notif.vacay_invite.title': 'Vacay Fusion-Einladung', - 'notif.vacay_invite.text': '{actor} hat dich zum Fusionieren von Urlaubsplänen eingeladen', - 'notif.photos_shared.title': 'Fotos geteilt', - 'notif.photos_shared.text': '{actor} hat {count} Foto(s) in {trip} geteilt', - 'notif.collab_message.title': 'Neue Nachricht', - 'notif.collab_message.text': '{actor} hat eine Nachricht in {trip} gesendet', - 'notif.packing_tagged.title': 'Packlistenzuweisung', - 'notif.packing_tagged.text': '{actor} hat dich zu {category} in {trip} zugewiesen', - 'notif.version_available.title': 'Neue Version verfügbar', - 'notif.version_available.text': 'TREK {version} ist jetzt verfügbar', - 'notif.action.view_trip': 'Reise ansehen', - 'notif.action.view_collab': 'Nachrichten ansehen', - 'notif.action.view_packing': 'Packliste ansehen', - 'notif.action.view_photos': 'Fotos ansehen', - 'notif.action.view_vacay': 'Vacay ansehen', - 'notif.action.view_admin': 'Zum Admin', - 'notif.action.view': 'Ansehen', - 'notif.action.accept': 'Annehmen', - 'notif.action.decline': 'Ablehnen', - 'notif.generic.title': 'Benachrichtigung', - 'notif.generic.text': 'Du hast eine neue Benachrichtigung', - 'notif.dev.unknown_event.title': '[DEV] Unbekanntes Ereignis', - 'notif.dev.unknown_event.text': 'Ereignistyp "{event}" ist nicht in EVENT_NOTIFICATION_CONFIG registriert', - - // Journey Addon - 'journey.search.placeholder': 'Reisen suchen…', - 'journey.search.noResults': 'Keine Reisen passen zu „{query}"', - 'journey.title': 'Journey', - 'journey.subtitle': 'Dokumentiere deine Reisen unterwegs', - 'journey.new': 'Neue Journey', - 'journey.create': 'Erstellen', - 'journey.titlePlaceholder': 'Wohin geht die Reise?', - 'journey.empty': 'Noch keine Journeys', - 'journey.emptyHint': 'Starte die Dokumentation deiner naechsten Reise', - 'journey.deleted': 'Journey geloescht', - 'journey.createError': 'Journey konnte nicht erstellt werden', - 'journey.deleteError': 'Journey konnte nicht geloescht werden', - 'journey.deleteConfirmTitle': 'Loeschen', - 'journey.deleteConfirmMessage': '"{title}" loeschen? Das kann nicht rueckgaengig gemacht werden.', - 'journey.deleteConfirmGeneric': 'Bist du sicher, dass du das loeschen moechtest?', - 'journey.notFound': 'Journey nicht gefunden', - 'journey.photos': 'Fotos', - 'journey.timelineEmpty': 'Noch keine Stationen', - 'journey.timelineEmptyHint': 'Fuege einen Check-in hinzu oder schreibe einen Tagebucheintrag', - 'journey.status.draft': 'Entwurf', - 'journey.status.active': 'Aktiv', - 'journey.status.completed': 'Abgeschlossen', - 'journey.status.upcoming': 'Anstehend', - 'journey.status.archived': 'Archiviert', - 'journey.checkin.add': 'Einchecken', - 'journey.checkin.namePlaceholder': 'Ortsname', - 'journey.checkin.notesPlaceholder': 'Notizen (optional)', - 'journey.checkin.save': 'Speichern', - 'journey.checkin.error': 'Check-in konnte nicht gespeichert werden', - 'journey.entry.add': 'Tagebuch', - 'journey.entry.edit': 'Eintrag bearbeiten', - 'journey.entry.titlePlaceholder': 'Titel (optional)', - 'journey.entry.bodyPlaceholder': 'Was ist heute passiert?', - 'journey.entry.save': 'Speichern', - 'journey.entry.error': 'Eintrag konnte nicht gespeichert werden', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Upload fehlgeschlagen', - 'journey.share.share': 'Teilen', - 'journey.share.public': 'Oeffentlich', - 'journey.share.linkCopied': 'Oeffentlicher Link kopiert', - 'journey.share.disabled': 'Oeffentliches Teilen deaktiviert', - 'journey.editor.titlePlaceholder': 'Gib diesem Moment einen Namen...', - 'journey.editor.bodyPlaceholder': 'Erzaehl die Geschichte dieses Tages...', - 'journey.editor.placePlaceholder': 'Ort (optional)', - 'journey.editor.tagsPlaceholder': 'Tags: Geheimtipp, bestes Essen, nochmal hin...', - 'journey.visibility.private': 'Privat', - 'journey.visibility.shared': 'Geteilt', - 'journey.visibility.public': 'Oeffentlich', - 'journey.emptyState.title': 'Deine Geschichte beginnt hier', - 'journey.emptyState.subtitle': 'Checke an einem Ort ein oder schreibe deinen ersten Tagebucheintrag', - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': 'Reise-Tracking & Tagebuch mit Check-ins, Fotos und Tagesberichten', - - // Journey & Mobile translations - 'journey.frontpage.subtitle': 'Verwandle deine Reisen in Geschichten, die du nie vergisst', - 'journey.frontpage.createJourney': 'Journey erstellen', - 'journey.frontpage.activeJourney': 'Aktive Journey', - 'journey.frontpage.allJourneys': 'Alle Journeys', - 'journey.frontpage.journeys': 'Journeys', - 'journey.frontpage.createNew': 'Neue Journey erstellen', - 'journey.frontpage.createNewSub': 'Trips auswählen, Geschichten schreiben, Abenteuer teilen', - 'journey.frontpage.live': 'Live', - 'journey.frontpage.synced': 'Synchronisiert', - 'journey.frontpage.continueWriting': 'Weiterschreiben', - 'journey.frontpage.updated': 'Aktualisiert {time}', - 'journey.frontpage.suggestionLabel': 'Trip gerade beendet', - 'journey.frontpage.suggestionText': 'Verwandle {title} in eine Journey', - 'journey.frontpage.dismiss': 'Schließen', - 'journey.frontpage.journeyName': 'Journey-Name', - 'journey.frontpage.namePlaceholder': 'z.B. Südostasien 2026', - 'journey.frontpage.selectTrips': 'Trips auswählen', - 'journey.frontpage.tripsSelected': 'Trips ausgewählt', - 'journey.frontpage.trips': 'Trips', - 'journey.frontpage.placesImported': 'Orte werden importiert', - 'journey.frontpage.places': 'Orte', - 'journey.detail.backToJourney': 'Zurück zur Journey', - 'journey.detail.syncedWithTrips': 'Mit Trips synchronisiert', - 'journey.detail.addEntry': 'Eintrag hinzufügen', - 'journey.detail.newEntry': 'Neuer Eintrag', - 'journey.detail.editEntry': 'Eintrag bearbeiten', - 'journey.detail.noEntries': 'Noch keine Einträge', - 'journey.detail.noEntriesHint': 'Füge einen Trip hinzu, um mit Skelett-Einträgen zu starten', - 'journey.detail.noPhotos': 'Noch keine Fotos', - 'journey.detail.noPhotosHint': 'Lade Fotos hoch oder durchsuche deine Immich/Synology-Bibliothek', - 'journey.detail.journeyStats': 'Journey-Statistiken', - 'journey.detail.syncedTrips': 'Verknüpfte Trips', - 'journey.detail.noTripsLinked': 'Noch keine Trips verknüpft', - 'journey.detail.contributors': 'Mitwirkende', - 'journey.detail.readMore': 'Mehr lesen', - 'journey.detail.prosCons': 'Pro & Contra', - 'journey.detail.photos': 'Fotos', - 'journey.detail.day': 'Tag {number}', - 'journey.detail.places': 'Orte', - 'journey.stats.days': 'Tage', - 'journey.stats.cities': 'Städte', - 'journey.stats.entries': 'Einträge', - 'journey.stats.photos': 'Fotos', - 'journey.stats.places': 'Orte', - 'journey.skeletons.show': 'Vorschläge anzeigen', - 'journey.skeletons.hide': 'Vorschläge ausblenden', - 'journey.verdict.lovedIt': 'Toll', - 'journey.verdict.couldBeBetter': 'Verbesserungswürdig', - 'journey.synced.places': 'Orte', - 'journey.synced.synced': 'synchronisiert', - 'journey.editor.discardChangesConfirm': 'Du hast ungespeicherte Änderungen. Verwerfen?', - 'journey.editor.uploadFailed': 'Foto-Upload fehlgeschlagen', - 'journey.editor.uploadPhotos': 'Fotos hochladen', - 'journey.editor.uploading': 'Hochladen...', - 'journey.editor.uploadingProgress': 'Hochladen {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} von {total} Fotos fehlgeschlagen — erneut speichern zum Wiederholen', - 'journey.editor.fromGallery': 'Aus Galerie', - 'journey.editor.allPhotosAdded': 'Alle Fotos bereits hinzugefügt', - 'journey.editor.writeStory': 'Erzähle deine Geschichte...', - 'journey.editor.prosCons': 'Pro & Contra', - 'journey.editor.pros': 'Pro', - 'journey.editor.cons': 'Contra', - 'journey.editor.proPlaceholder': 'Etwas Positives...', - 'journey.editor.conPlaceholder': 'Nicht so toll...', - 'journey.editor.addAnother': 'Hinzufügen', - 'journey.editor.date': 'Datum', - 'journey.editor.location': 'Ort', - 'journey.editor.searchLocation': 'Ort suchen...', - 'journey.editor.mood': 'Stimmung', - 'journey.editor.weather': 'Wetter', - 'journey.editor.photoFirst': '1.', - 'journey.editor.makeFirst': 'Als 1. setzen', - 'journey.editor.searching': 'Suche...', - 'journey.mood.amazing': 'Großartig', - 'journey.mood.good': 'Gut', - 'journey.mood.neutral': 'Neutral', - 'journey.mood.rough': 'Schwierig', - 'journey.weather.sunny': 'Sonnig', - 'journey.weather.partly': 'Teilweise bewölkt', - 'journey.weather.cloudy': 'Bewölkt', - 'journey.weather.rainy': 'Regnerisch', - 'journey.weather.stormy': 'Stürmisch', - 'journey.weather.cold': 'Schnee', - 'journey.trips.linkTrip': 'Trip verknüpfen', - 'journey.trips.searchTrip': 'Trip suchen', - 'journey.trips.searchPlaceholder': 'Tripname oder Reiseziel...', - 'journey.trips.noTripsAvailable': 'Keine Trips verfügbar', - 'journey.trips.link': 'Verknüpfen', - 'journey.trips.tripLinked': 'Trip verknüpft', - 'journey.trips.linkFailed': 'Verknüpfung fehlgeschlagen', - 'journey.trips.addTrip': 'Trip hinzufügen', - 'journey.trips.unlinkTrip': 'Trip trennen', - 'journey.trips.unlinkMessage': '"{title}" trennen? Alle synchronisierten Einträge und Fotos dieses Trips werden unwiderruflich gelöscht.', - 'journey.trips.unlink': 'Trennen', - 'journey.trips.tripUnlinked': 'Trip getrennt', - 'journey.trips.unlinkFailed': 'Trennung fehlgeschlagen', - 'journey.trips.noTripsLinkedSettings': 'Keine Trips verknüpft', - 'journey.contributors.invite': 'Mitwirkenden einladen', - 'journey.contributors.searchUser': 'Benutzer suchen', - 'journey.contributors.searchPlaceholder': 'Benutzername oder E-Mail...', - 'journey.contributors.noUsers': 'Keine Benutzer gefunden', - 'journey.contributors.role': 'Rolle', - 'journey.contributors.added': 'Mitwirkender hinzugefügt', - 'journey.contributors.addFailed': 'Hinzufügen fehlgeschlagen', - 'journey.contributors.remove': 'Mitwirkenden entfernen', - 'journey.contributors.removeConfirm': '{username} aus dieser Journey entfernen?', - 'journey.contributors.removed': 'Mitwirkender entfernt', - 'journey.contributors.removeFailed': 'Entfernen fehlgeschlagen', - 'journey.share.publicShare': 'Öffentlicher Link', - 'journey.share.createLink': 'Link erstellen', - 'journey.share.linkCreated': 'Link erstellt', - 'journey.share.createFailed': 'Link konnte nicht erstellt werden', - 'journey.share.copy': 'Kopieren', - 'journey.share.copied': 'Kopiert!', - 'journey.share.timeline': 'Zeitstrahl', - 'journey.share.gallery': 'Galerie', - 'journey.share.map': 'Karte', - 'journey.share.removeLink': 'Link entfernen', - 'journey.share.linkDeleted': 'Link entfernt', - 'journey.share.deleteFailed': 'Entfernen fehlgeschlagen', - 'journey.share.updateFailed': 'Aktualisierung fehlgeschlagen', - - // Journey — Invite - 'journey.invite.role': 'Rolle', - 'journey.invite.viewer': 'Betrachter', - 'journey.invite.editor': 'Bearbeiter', - 'journey.invite.invite': 'Einladen', - 'journey.invite.inviting': 'Wird eingeladen...', - 'journey.settings.title': 'Journey-Einstellungen', - 'journey.settings.coverImage': 'Titelbild', - 'journey.settings.changeCover': 'Titelbild ändern', - 'journey.settings.addCover': 'Titelbild hinzufügen', - 'journey.settings.name': 'Name', - 'journey.settings.subtitle': 'Untertitel', - 'journey.settings.subtitlePlaceholder': 'z.B. Thailand, Vietnam & Kambodscha', - 'journey.settings.endJourney': 'Reise archivieren', - 'journey.settings.reopenJourney': 'Reise wiederherstellen', - 'journey.settings.archived': 'Reise archiviert', - 'journey.settings.reopened': 'Reise erneut geöffnet', - 'journey.settings.endDescription': 'Blendet das Live-Abzeichen aus. Sie können jederzeit wieder öffnen.', - 'journey.settings.delete': 'Löschen', - 'journey.settings.deleteJourney': 'Journey löschen', - 'journey.settings.deleteMessage': '"{title}" löschen? Alle Einträge und Fotos gehen verloren.', - 'journey.settings.saved': 'Einstellungen gespeichert', - 'journey.settings.saveFailed': 'Speichern fehlgeschlagen', - 'journey.settings.coverUpdated': 'Titelbild aktualisiert', - 'journey.settings.coverFailed': 'Upload fehlgeschlagen', - 'journey.settings.failedToDelete': 'Löschen fehlgeschlagen', - 'journey.entries.deleteTitle': 'Eintrag löschen', - 'journey.photosUploaded': '{count} Fotos hochgeladen', - 'journey.photosUploadFailed': 'Einige Fotos konnten nicht hochgeladen werden', - 'journey.photosAdded': '{count} Fotos hinzugefügt', - 'journey.public.notFound': 'Nicht gefunden', - 'journey.public.notFoundMessage': 'Diese Journey existiert nicht oder der Link ist abgelaufen.', - 'journey.public.readOnly': 'Nur lesen · Öffentliche Journey', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Geteilt über', - 'journey.public.madeWith': 'Erstellt mit', - 'journey.pdf.journeyBook': 'Reisebuch', - 'journey.pdf.madeWith': 'Erstellt mit TREK', - 'journey.pdf.day': 'Tag', - 'journey.pdf.theEnd': 'Ende', - 'journey.pdf.saveAsPdf': 'Als PDF speichern', - 'journey.pdf.pages': 'Seiten', - 'journey.picker.tripPeriod': 'Reisezeitraum', - 'journey.picker.dateRange': 'Zeitraum', - 'journey.picker.allPhotos': 'Alle Fotos', - 'journey.picker.albums': 'Alben', - 'journey.picker.selected': 'ausgewählt', - 'journey.picker.addTo': 'Hinzufügen zu', - 'journey.picker.newGallery': 'Neue Galerie', - 'journey.picker.selectAll': 'Alle auswählen', - 'journey.picker.deselectAll': 'Alle abwählen', - 'journey.picker.noAlbums': 'Keine Alben gefunden', - 'journey.picker.selectDate': 'Datum wählen', - 'journey.picker.search': 'Suchen', - 'dashboard.greeting.morning': 'Guten Morgen,', - 'dashboard.greeting.afternoon': 'Guten Tag,', - 'dashboard.greeting.evening': 'Guten Abend,', - 'dashboard.mobile.liveNow': 'Jetzt live', - 'dashboard.mobile.tripProgress': 'Reisefortschritt', - 'dashboard.mobile.daysLeft': '{count} Tage übrig', - 'dashboard.mobile.places': 'Orte', - 'dashboard.mobile.buddies': 'Freunde', - 'dashboard.mobile.newTrip': 'Neuer Trip', - 'dashboard.mobile.currency': 'Währung', - 'dashboard.mobile.timezone': 'Zeitzone', - 'dashboard.mobile.upcomingTrips': 'Anstehende Trips', - 'dashboard.mobile.yourTrips': 'Deine Trips', - 'dashboard.mobile.trips': 'Trips', - 'dashboard.mobile.starts': 'Beginn', - 'dashboard.mobile.duration': 'Dauer', - 'dashboard.mobile.day': 'Tag', - 'dashboard.mobile.days': 'Tage', - 'dashboard.mobile.ongoing': 'Laufend', - 'dashboard.mobile.startsToday': 'Beginnt heute', - 'dashboard.mobile.tomorrow': 'Morgen', - 'dashboard.mobile.inDays': 'In {count} Tagen', - 'dashboard.mobile.inMonths': 'In {count} Monaten', - 'dashboard.mobile.completed': 'Abgeschlossen', - 'dashboard.mobile.currencyConverter': 'Währungsrechner', - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Einstellungen', - 'nav.bottomAdmin': 'Admin-Einstellungen', - 'nav.bottomLogout': 'Abmelden', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Ort hinzufügen', - 'dayplan.mobile.searchPlaces': 'Orte suchen...', - 'dayplan.mobile.allAssigned': 'Alle Orte zugeordnet', - 'dayplan.mobile.noMatch': 'Kein Treffer', - 'dayplan.mobile.createNew': 'Neuen Ort erstellen', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Reisen', - 'oauth.scope.group.places': 'Orte', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Packliste', - 'oauth.scope.group.todos': 'Aufgaben', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Buchungen', - 'oauth.scope.group.collab': 'Zusammenarbeit', - 'oauth.scope.group.notifications': 'Benachrichtigungen', - 'oauth.scope.group.vacay': 'Urlaub', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Wetter', - 'oauth.scope.group.journey': 'Journey', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Reisen und Reisepläne anzeigen', - 'oauth.scope.trips:read.description': 'Reisen, Tage, Tagesnotizen und Mitglieder lesen', - 'oauth.scope.trips:write.label': 'Reisen und Reisepläne bearbeiten', - 'oauth.scope.trips:write.description': 'Reisen, Tage und Notizen erstellen, aktualisieren und Mitglieder verwalten', - 'oauth.scope.trips:delete.label': 'Reisen löschen', - 'oauth.scope.trips:delete.description': 'Reisen dauerhaft löschen — diese Aktion ist unwiderruflich', - 'oauth.scope.trips:share.label': 'Freigabelinks verwalten', - 'oauth.scope.trips:share.description': 'Öffentliche Freigabelinks erstellen, aktualisieren und widerrufen', - 'oauth.scope.places:read.label': 'Orte und Kartendaten anzeigen', - 'oauth.scope.places:read.description': 'Orte, Tageszuweisungen, Tags und Kategorien lesen', - 'oauth.scope.places:write.label': 'Orte verwalten', - 'oauth.scope.places:write.description': 'Orte, Zuweisungen und Tags erstellen, aktualisieren und löschen', - 'oauth.scope.atlas:read.label': 'Atlas anzeigen', - 'oauth.scope.atlas:read.description': 'Besuchte Länder, Regionen und Wunschliste lesen', - 'oauth.scope.atlas:write.label': 'Atlas verwalten', - 'oauth.scope.atlas:write.description': 'Länder und Regionen als besucht markieren, Wunschliste verwalten', - 'oauth.scope.packing:read.label': 'Packlisten anzeigen', - 'oauth.scope.packing:read.description': 'Packgegenstände, Taschen und Kategoriezuweisungen lesen', - 'oauth.scope.packing:write.label': 'Packlisten verwalten', - 'oauth.scope.packing:write.description': 'Packgegenstände und Taschen hinzufügen, aktualisieren, löschen, abhaken und sortieren', - 'oauth.scope.todos:read.label': 'Aufgabenlisten anzeigen', - 'oauth.scope.todos:read.description': 'Reiseaufgaben und Kategoriezuweisungen lesen', - 'oauth.scope.todos:write.label': 'Aufgabenlisten verwalten', - 'oauth.scope.todos:write.description': 'Aufgaben erstellen, aktualisieren, abhaken, löschen und sortieren', - 'oauth.scope.budget:read.label': 'Budget anzeigen', - 'oauth.scope.budget:read.description': 'Budgeteinträge und Ausgabenaufschlüsselung lesen', - 'oauth.scope.budget:write.label': 'Budget verwalten', - 'oauth.scope.budget:write.description': 'Budgeteinträge erstellen, aktualisieren und löschen', - 'oauth.scope.reservations:read.label': 'Buchungen anzeigen', - 'oauth.scope.reservations:read.description': 'Buchungen und Unterkunftsdetails lesen', - 'oauth.scope.reservations:write.label': 'Buchungen verwalten', - 'oauth.scope.reservations:write.description': 'Buchungen erstellen, aktualisieren, löschen und sortieren', - 'oauth.scope.collab:read.label': 'Zusammenarbeit anzeigen', - 'oauth.scope.collab:read.description': 'Kollaborationsnotizen, Umfragen und Nachrichten lesen', - 'oauth.scope.collab:write.label': 'Zusammenarbeit verwalten', - 'oauth.scope.collab:write.description': 'Kollaborationsnotizen, Umfragen und Nachrichten erstellen, aktualisieren und löschen', - 'oauth.scope.notifications:read.label': 'Benachrichtigungen anzeigen', - 'oauth.scope.notifications:read.description': 'In-App-Benachrichtigungen und ungelesene Zählungen lesen', - 'oauth.scope.notifications:write.label': 'Benachrichtigungen verwalten', - 'oauth.scope.notifications:write.description': 'Benachrichtigungen als gelesen markieren und darauf reagieren', - 'oauth.scope.vacay:read.label': 'Urlaubspläne anzeigen', - 'oauth.scope.vacay:read.description': 'Urlaubsplanungsdaten, Einträge und Statistiken lesen', - 'oauth.scope.vacay:write.label': 'Urlaubspläne verwalten', - 'oauth.scope.vacay:write.description': 'Urlaubseinträge, Feiertage und Teampläne erstellen und verwalten', - 'oauth.scope.geo:read.label': 'Karten & Geocodierung', - 'oauth.scope.geo:read.description': 'Orte suchen, Karten-URLs auflösen und Koordinaten rückwärts geokodieren', - 'oauth.scope.weather:read.label': 'Wettervorhersagen', - 'oauth.scope.weather:read.description': 'Wettervorhersagen für Reiseorte und -daten abrufen', - 'oauth.scope.journey:read.label': 'Journeys ansehen', - 'oauth.scope.journey:read.description': 'Journeys, Einträge und Mitarbeiterliste lesen', - 'oauth.scope.journey:write.label': 'Journeys verwalten', - 'oauth.scope.journey:write.description': 'Journeys und deren Einträge erstellen, bearbeiten und löschen', - 'oauth.scope.journey:share.label': 'Journey-Links verwalten', - 'oauth.scope.journey:share.description': 'Öffentliche Freigabelinks für Journeys erstellen, aktualisieren und widerrufen', - - // System notices - 'system_notice.welcome_v1.title': 'Willkommen bei TREK', - 'system_notice.welcome_v1.body': 'Dein All-in-one-Reiseplaner. Erstelle Reisepläne, teile sie mit Freunden und bleib organisiert – online und offline.', - 'system_notice.welcome_v1.cta_label': 'Reise planen', - 'system_notice.welcome_v1.hero_alt': 'Malerisches Reiseziel mit TREK-Planungs-UI', - 'system_notice.welcome_v1.highlight_plan': 'Tagesweise Reisepläne für jede Reise', - 'system_notice.welcome_v1.highlight_share': 'Gemeinsam mit Reisepartnern planen', - 'system_notice.welcome_v1.highlight_offline': 'Funktioniert offline auf dem Handy', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Vorherige Meldung', - 'system_notice.pager.next': 'Nächste Meldung', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Zu Meldung {n}', - 'system_notice.pager.position': 'Meldung {current} von {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Fotos wurden in 3.0 verschoben', - 'system_notice.v3_photos.body': '**Fotos** im Trip-Planer wurden entfernt. Deine Fotos sind sicher — TREK hat deine Immich- oder Synology-Bibliothek nie verändert.\n\nFotos befinden sich jetzt im **Journey**-Addon. Journey ist optional — falls es noch nicht verfügbar ist, bitte deinen Admin, es unter Admin → Addons zu aktivieren.', - 'system_notice.v3_journey.title': 'Neu: Journey — dein Reisetagebuch', - 'system_notice.v3_journey.body': 'Dokumentiere deine Reisen als lebendige Geschichten mit Zeitachsen, Fotogalerien und interaktiven Karten.', - 'system_notice.v3_journey.cta_label': 'Journey öffnen', - 'system_notice.v3_journey.highlight_timeline': 'Zeitleiste und Galerie', - 'system_notice.v3_journey.highlight_photos': 'Import von Immich oder Synology', - 'system_notice.v3_journey.highlight_share': 'Öffentlich teilen — kein Login nötig', - 'system_notice.v3_journey.highlight_export': 'Als PDF-Fotobuch exportieren', - 'system_notice.v3_features.title': 'Weitere Highlights in 3.0', - 'system_notice.v3_features.body': 'Ein paar weitere Neuerungen in diesem Release.', - 'system_notice.v3_features.highlight_dashboard': 'Mobile-first Dashboard-Redesign', - 'system_notice.v3_features.highlight_offline': 'Vollständiger Offline-Modus als PWA', - 'system_notice.v3_features.highlight_search': 'Echtzeit-Autovervollständigung für Orte', - 'system_notice.v3_features.highlight_import': 'Orte aus KMZ/KML-Dateien importieren', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1-Upgrade', - 'system_notice.v3_mcp.body': 'Die MCP-Integration wurde vollständig überarbeitet. OAuth 2.1 ist jetzt die empfohlene Authentifizierungsmethode. Statische Tokens (trek_…) sind veraltet und werden in einer zukünftigen Version entfernt.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 empfohlen (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 feingranulare Berechtigungs-Scopes', - 'system_notice.v3_mcp.highlight_deprecated': 'Statische trek_-Tokens veraltet', - 'system_notice.v3_mcp.highlight_tools': 'Erweitertes Toolset & Prompts', - - // System notices — persönlicher Dank - 'system_notice.v3_thankyou.title': 'Ein persönliches Wort von mir', - 'system_notice.v3_thankyou.body': 'Bevor du weiterklickst — einen Moment noch.\n\nTREK hat als Nebenprojekt für meine eigenen Reisen angefangen. Ich hätte nie gedacht, dass es jemals so weit kommt, dass 4.000 von euch damit ihre Abenteuer planen. Jeder Stern, jedes Issue, jeder Feature-Wunsch — ich lese sie alle, und sie halten mich am Laufen durch die späten Nächte zwischen Vollzeitjob und Studium.\n\nEins will ich euch sagen: TREK wird immer Open Source bleiben, immer self-hosted, immer eures. Kein Tracking, keine Abos, keine versteckten Haken. Einfach ein Tool, gebaut von jemandem, der das Reisen genauso liebt wie ihr.\n\nBesonderer Dank an [jubnl](https://github.com/jubnl) — du bist ein unglaublicher Mitstreiter geworden. So vieles, was 3.0 großartig macht, trägt deine Handschrift. Danke, dass du an dieses Projekt geglaubt hast, als es noch holprig war.\n\nUnd an jeden einzelnen von euch, der einen Bug gemeldet, einen String übersetzt, TREK mit Freunden geteilt oder einfach damit eine Reise geplant hat — **danke**. Ihr seid der Grund, warum es das hier gibt.\n\nAuf viele weitere Abenteuer zusammen.\n\n— Maurice\n\n---\n\n[Tritt der Community auf Discord bei](https://discord.gg/7Q6M6jDwzf)\n\nWenn TREK deine Reisen besser macht, hält ein [kleiner Kaffee](https://ko-fi.com/mauriceboe) die Lichter an.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Aktion erforderlich: Benutzerkontokonflikt', - 'system_notice.v3014_whitespace_collision.body': 'Das 3.0.14-Upgrade hat einen oder mehrere Konflikte bei Benutzernamen oder E-Mail-Adressen festgestellt, die durch führende oder nachgestellte Leerzeichen in gespeicherten Konten verursacht wurden. Betroffene Konten wurden automatisch umbenannt. Prüfe die Serverprotokolle auf Zeilen, die mit **[migration] WHITESPACE COLLISION** beginnen, um die betroffenen Konten zu identifizieren.', - 'transport.addTransport': 'Transport hinzufügen', - 'transport.modalTitle.create': 'Transport hinzufügen', - 'transport.modalTitle.edit': 'Transport bearbeiten', - 'transport.title': 'Transporte', - 'transport.addManual': 'Manuelles Transportmittel', -} - -export default de - diff --git a/client/src/i18n/translations/en.ts b/client/src/i18n/translations/en.ts deleted file mode 100644 index 0fb72002..00000000 --- a/client/src/i18n/translations/en.ts +++ /dev/null @@ -1,2429 +0,0 @@ -const en: Record = { - // Common - 'common.save': 'Save', - 'common.showMore': 'Show more', - 'common.showLess': 'Show less', - 'common.cancel': 'Cancel', - 'common.clear': 'Clear', - 'common.delete': 'Delete', - 'common.edit': 'Edit', - 'common.add': 'Add', - 'common.loading': 'Loading...', - 'common.import': 'Import', - 'common.select': 'Select', - 'common.selectAll': 'Select all', - 'common.deselectAll': 'Deselect all', - 'common.error': 'Error', - 'common.unknownError': 'Unknown error', - 'common.tooManyAttempts': 'Too many attempts. Please try again later.', - 'common.back': 'Back', - 'common.all': 'All', - 'common.close': 'Close', - 'common.open': 'Open', - 'common.upload': 'Upload', - 'common.search': 'Search', - 'common.confirm': 'Confirm', - 'common.ok': 'OK', - 'common.yes': 'Yes', - 'common.no': 'No', - 'common.or': 'or', - 'common.none': 'None', - 'common.date': 'Date', - 'common.rename': 'Rename', - 'common.discardChanges': 'Discard Changes', - 'common.discard': 'Discard', - 'common.name': 'Name', - 'common.email': 'Email', - 'common.password': 'Password', - 'common.saving': 'Saving...', - 'common.justNow': 'just now', - 'common.hoursAgo': '{count}h ago', - 'common.daysAgo': '{count}d ago', - 'common.saved': 'Saved', - 'trips.memberRemoved': '{username} removed', - 'trips.memberRemoveError': 'Failed to remove', - 'trips.memberAdded': '{username} added', - 'trips.memberAddError': 'Failed to add', - 'trips.reminder': 'Reminder', - 'trips.reminderNone': 'None', - 'trips.reminderDay': 'day', - 'trips.reminderDays': 'days', - 'trips.reminderCustom': 'Custom', - 'trips.reminderDaysBefore': 'days before departure', - 'trips.reminderDisabledHint': 'Trip reminders are disabled. Enable them in Admin > Settings > Notifications.', - 'common.update': 'Update', - 'common.change': 'Change', - 'common.uploading': 'Uploading…', - 'common.backToPlanning': 'Back to Planning', - 'common.reset': 'Reset', - 'common.expand': 'Expand', - 'common.collapse': 'Collapse', - - // Navbar - 'nav.trip': 'Trip', - 'nav.share': 'Share', - 'nav.settings': 'Settings', - 'nav.admin': 'Admin', - 'nav.logout': 'Log out', - 'nav.lightMode': 'Light Mode', - 'nav.darkMode': 'Dark Mode', - 'nav.autoMode': 'Auto Mode', - 'nav.administrator': 'Administrator', - - // Dashboard - 'dashboard.title': 'My Trips', - 'dashboard.subtitle.loading': 'Loading trips...', - 'dashboard.subtitle.trips': '{count} trips ({archived} archived)', - 'dashboard.subtitle.empty': 'Start your first trip', - 'dashboard.subtitle.activeOne': '{count} active trip', - 'dashboard.subtitle.activeMany': '{count} active trips', - 'dashboard.subtitle.archivedSuffix': ' · {count} archived', - 'dashboard.newTrip': 'New Trip', - 'dashboard.gridView': 'Grid view', - 'dashboard.listView': 'List view', - 'dashboard.currency': 'Currency', - 'dashboard.timezone': 'Timezones', - 'dashboard.localTime': 'Local', - 'dashboard.timezoneCustomTitle': 'Custom Timezone', - 'dashboard.timezoneCustomLabelPlaceholder': 'Label (optional)', - 'dashboard.timezoneCustomTzPlaceholder': 'e.g. America/New_York', - 'dashboard.timezoneCustomAdd': 'Add', - 'dashboard.timezoneCustomErrorEmpty': 'Enter a timezone identifier', - 'dashboard.timezoneCustomErrorInvalid': 'Invalid timezone. Use format like Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Already added', - 'dashboard.emptyTitle': 'No trips yet', - 'dashboard.emptyText': 'Create your first trip and start planning!', - 'dashboard.emptyButton': 'Create First Trip', - 'dashboard.nextTrip': 'Next Trip', - 'dashboard.shared': 'Shared', - 'dashboard.sharedBy': 'Shared by {name}', - 'dashboard.days': 'Days', - 'dashboard.places': 'Places', - 'dashboard.members': 'Buddies', - 'dashboard.archive': 'Archive', - 'dashboard.copyTrip': 'Copy', - 'dashboard.copySuffix': 'copy', - 'dashboard.restore': 'Restore', - 'dashboard.archived': 'Archived', - 'dashboard.status.ongoing': 'Ongoing', - 'dashboard.status.today': 'Today', - 'dashboard.status.tomorrow': 'Tomorrow', - 'dashboard.status.past': 'Past', - 'dashboard.status.daysLeft': '{count} days left', - 'dashboard.toast.loadError': 'Failed to load trips', - 'dashboard.toast.created': 'Trip created successfully!', - 'dashboard.toast.createError': 'Failed to create trip', - 'dashboard.toast.updated': 'Trip updated!', - 'dashboard.toast.updateError': 'Failed to update trip', - 'dashboard.toast.deleted': 'Trip deleted', - 'dashboard.toast.deleteError': 'Failed to delete trip', - 'dashboard.toast.archived': 'Trip archived', - 'dashboard.toast.archiveError': 'Failed to archive trip', - 'dashboard.toast.restored': 'Trip restored', - 'dashboard.toast.restoreError': 'Failed to restore trip', - 'dashboard.toast.copied': 'Trip copied!', - 'dashboard.toast.copyError': 'Failed to copy trip', - 'dashboard.confirm.delete': 'Delete trip "{title}"? All places and plans will be permanently deleted.', - 'dashboard.confirm.copy.title': 'Copy this trip?', - 'dashboard.confirm.copy.willCopy': 'Will be copied', - 'dashboard.confirm.copy.will1': 'Days, places & day assignments', - 'dashboard.confirm.copy.will2': 'Accommodations & reservations', - 'dashboard.confirm.copy.will3': 'Budget items & category order', - 'dashboard.confirm.copy.will4': 'Packing lists (unchecked)', - 'dashboard.confirm.copy.will5': 'TODOs (unassigned & unchecked)', - 'dashboard.confirm.copy.will6': 'Day notes', - 'dashboard.confirm.copy.wontCopy': "Won't be copied", - 'dashboard.confirm.copy.wont1': 'Collaborators & member assignments', - 'dashboard.confirm.copy.wont2': 'Collab notes, polls & messages', - 'dashboard.confirm.copy.wont3': 'Files & photos', - 'dashboard.confirm.copy.wont4': 'Share tokens', - 'dashboard.confirm.copy.confirm': 'Copy trip', - 'dashboard.editTrip': 'Edit Trip', - 'dashboard.createTrip': 'Create New Trip', - 'dashboard.tripTitle': 'Title', - 'dashboard.tripTitlePlaceholder': 'e.g. Summer in Japan', - 'dashboard.tripDescription': 'Description', - 'dashboard.tripDescriptionPlaceholder': 'What is this trip about?', - 'dashboard.startDate': 'Start Date', - 'dashboard.endDate': 'End Date', - 'dashboard.dayCount': 'Number of Days', - 'dashboard.dayCountHint': 'How many days to plan for when no travel dates are set.', - 'dashboard.noDateHint': 'No date set — 7 default days will be created. You can change this anytime.', - 'dashboard.coverImage': 'Cover Image', - 'dashboard.addCoverImage': 'Add cover image (or drag & drop)', - 'dashboard.addMembers': 'Travel buddies', - 'dashboard.addMember': 'Add member', - 'dashboard.coverSaved': 'Cover image saved', - 'dashboard.coverUploadError': 'Failed to upload', - 'dashboard.coverRemoveError': 'Failed to remove', - 'dashboard.titleRequired': 'Title is required', - 'dashboard.endDateError': 'End date must be after start date', - - // Settings - 'settings.title': 'Settings', - 'settings.subtitle': 'Configure your personal settings', - 'settings.tabs.display': 'Display', - 'settings.tabs.map': 'Map', - 'settings.tabs.notifications': 'Notifications', - 'settings.tabs.integrations': 'Integrations', - 'settings.tabs.account': 'Account', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'About', - 'settings.map': 'Map', - 'settings.mapTemplate': 'Map Template', - 'settings.mapTemplatePlaceholder.select': 'Select template...', - 'settings.mapDefaultHint': 'Leave empty for OpenStreetMap (default)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL template for map tiles', - 'settings.mapProvider': 'Map Provider', - 'settings.mapProviderHint': 'Affects Trip Planner and Journey maps. Atlas always uses Leaflet.', - 'settings.mapLeafletSubtitle': 'Classic 2D, any raster tiles', - 'settings.mapMapboxSubtitle': 'Vector tiles, 3D buildings & terrain', - 'settings.mapExperimental': 'Experimental', - 'settings.mapMapboxToken': 'Mapbox Access Token', - 'settings.mapMapboxTokenHint': 'Public token (pk.*) from', - 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', - 'settings.mapStyle': 'Map Style', - 'settings.mapStylePlaceholder': 'Select a Mapbox style', - 'settings.mapStyleHint': 'Preset or your own mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D Buildings & Terrain', - 'settings.map3dHint': 'Pitch + real 3D building extrusions — works on every style, including satellite.', - 'settings.mapHighQuality': 'High Quality Mode', - 'settings.mapHighQualityHint': 'Antialiasing + globe projection for sharper edges and a realistic world view.', - 'settings.mapHighQualityWarning': 'May impact performance on lower-end devices.', - 'settings.mapTipLabel': 'Tip:', - 'settings.mapTip': 'right-click and drag to rotate/pitch the map. Middle-click to add a place (right-click is reserved for rotation).', - 'settings.latitude': 'Latitude', - 'settings.longitude': 'Longitude', - 'settings.saveMap': 'Save Map', - 'settings.apiKeys': 'API Keys', - 'settings.mapsKey': 'Google Maps API Key', - 'settings.mapsKeyHint': 'For place search. Requires Places API (New). Get at console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API Key', - 'settings.weatherKeyHint': 'For weather data. Free at openweathermap.org/api', - 'settings.keyPlaceholder': 'Enter key...', - 'settings.configured': 'Configured', - 'settings.saveKeys': 'Save Keys', - 'settings.display': 'Display', - 'settings.colorMode': 'Color Mode', - 'settings.light': 'Light', - 'settings.dark': 'Dark', - 'settings.auto': 'Auto', - 'settings.language': 'Language', - 'settings.temperature': 'Temperature Unit', - 'settings.timeFormat': 'Time Format', - 'settings.bookingLabels': 'Booking route labels', - 'settings.bookingLabelsHint': 'Show station / airport names on the map. When off, only the icon is shown.', - 'settings.blurBookingCodes': 'Blur Booking Codes', - 'settings.notifications': 'Notifications', - 'settings.notifyTripInvite': 'Trip invitations', - 'settings.notifyBookingChange': 'Booking changes', - 'settings.notifyTripReminder': 'Trip reminders', - 'settings.notifyTodoDue': 'Todo due soon', - 'settings.notifyVacayInvite': 'Vacay fusion invitations', - 'settings.notifyPhotosShared': 'Shared photos (Immich)', - 'settings.notifyCollabMessage': 'Chat messages (Collab)', - 'settings.notifyPackingTagged': 'Packing list: assignments', - 'settings.notifyWebhook': 'Webhook notifications', - 'settings.notifyVersionAvailable': 'New version available', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': 'No notification channels are configured. Ask an admin to set up email or webhook notifications.', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Enter your Discord, Slack, or custom webhook URL to receive notifications.', - 'settings.webhookUrl.saved': 'Webhook URL saved', - 'settings.webhookUrl.test': 'Test', - 'settings.webhookUrl.testSuccess': 'Test webhook sent successfully', - 'settings.webhookUrl.testFailed': 'Test webhook failed', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', - 'admin.notifications.title': 'Notifications', - 'admin.notifications.hint': 'Choose one notification channel. Only one can be active at a time.', - 'admin.notifications.none': 'Disabled', - 'admin.notifications.email': 'Email (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Allow users to configure their own ntfy topics for push notifications. Set the default server below to pre-fill user settings.', - 'admin.notifications.save': 'Save notification settings', - 'admin.notifications.saved': 'Notification settings saved', - 'admin.notifications.testWebhook': 'Send test webhook', - 'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully', - 'admin.notifications.testWebhookFailed': 'Test webhook failed', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'In-app notifications are always active and cannot be disabled globally.', - 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', - 'admin.notifications.adminWebhookPanel.hint': 'This webhook is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user webhooks and always fires when set.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL saved', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook sent successfully', - 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook always fires when a URL is configured', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', - 'admin.notifications.adminNtfyPanel.serverHint': 'Also used as the default server for user ntfy notifications. Leave blank to default to ntfy.sh. Users can override this in their own settings.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Admin access token cleared', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', - 'admin.notifications.adminNotificationsHint': 'Configure which channels deliver admin-only notifications (e.g. version alerts).', - 'admin.notifications.tripReminders.title': 'Trip Reminders', - 'admin.notifications.tripReminders.hint': 'Send a reminder notification before a trip starts (requires reminder days to be set on the trip).', - 'admin.notifications.tripReminders.enabled': 'Trip reminders enabled', - 'admin.notifications.tripReminders.disabled': 'Trip reminders disabled', - 'admin.smtp.title': 'Email & Notifications', - 'admin.smtp.hint': 'SMTP configuration for sending email notifications.', - 'admin.smtp.testButton': 'Send test email', - 'admin.webhook.hint': 'Allow users to configure their own webhook URLs for notifications (Discord, Slack, etc.).', - 'admin.smtp.testSuccess': 'Test email sent successfully', - 'admin.smtp.testFailed': 'Test email failed', - 'settings.notificationsDisabled': 'Notifications are not configured. Ask an admin to enable email or webhook notifications.', - 'settings.notificationsActive': 'Active channel', - 'settings.notificationsManagedByAdmin': 'Notification events are configured by your administrator.', - 'dayplan.icsTooltip': 'Export calendar (ICS)', - 'share.linkTitle': 'Public Link', - 'share.linkHint': 'Create a link anyone can use to view this trip without logging in. Read-only — no editing possible.', - 'share.createLink': 'Create link', - 'share.deleteLink': 'Delete link', - 'share.createError': 'Could not create link', - 'common.copy': 'Copy', - 'common.copied': 'Copied', - 'share.permMap': 'Map & Plan', - 'share.permBookings': 'Bookings', - 'share.permPacking': 'Packing', - 'shared.expired': 'Link expired or invalid', - 'shared.expiredHint': 'This shared trip link is no longer active.', - 'shared.readOnly': 'Read-only shared view', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Bookings', - 'shared.tabPacking': 'Packing', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'days', - 'shared.places': 'places', - 'shared.other': 'Other', - 'shared.totalBudget': 'Total Budget', - 'shared.messages': 'messages', - 'shared.sharedVia': 'Shared via', - 'shared.confirmed': 'Confirmed', - 'shared.pending': 'Pending', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - 'settings.on': 'On', - 'settings.off': 'Off', - 'settings.mcp.title': 'MCP Configuration', - 'settings.mcp.endpoint': 'MCP Endpoint', - 'settings.mcp.clientConfig': 'Client Configuration', - 'settings.mcp.clientConfigHint': 'Replace with an API token from the list below. The path to npx may need to be adjusted for your system (e.g. C:\\PROGRA~1\\nodejs\\npx.cmd on Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).', - 'settings.mcp.copy': 'Copy', - 'settings.mcp.copied': 'Copied!', - 'settings.mcp.apiTokens': 'API Tokens', - 'settings.mcp.createToken': 'Create New Token', - 'settings.mcp.noTokens': 'No tokens yet. Create one to connect MCP clients.', - 'settings.mcp.tokenCreatedAt': 'Created', - 'settings.mcp.tokenUsedAt': 'Used', - 'settings.mcp.deleteTokenTitle': 'Delete Token', - 'settings.mcp.deleteTokenMessage': 'This token will stop working immediately. Any MCP client using it will lose access.', - 'settings.mcp.modal.createTitle': 'Create API Token', - 'settings.mcp.modal.tokenName': 'Token Name', - 'settings.mcp.modal.tokenNamePlaceholder': 'e.g. Claude Desktop, Work laptop', - 'settings.mcp.modal.creating': 'Creating…', - 'settings.mcp.modal.create': 'Create Token', - 'settings.mcp.modal.createdTitle': 'Token Created', - 'settings.mcp.modal.createdWarning': 'This token will only be shown once. Copy and store it now — it cannot be recovered.', - 'settings.mcp.modal.done': 'Done', - 'settings.mcp.toast.created': 'Token created', - 'settings.mcp.toast.createError': 'Failed to create token', - 'settings.mcp.toast.deleted': 'Token deleted', - 'settings.mcp.toast.deleteError': 'Failed to delete token', - 'settings.mcp.apiTokensDeprecated': 'API Tokens are deprecated and will be removed in a future release. Please use OAuth 2.1 Clients instead.', - 'settings.oauth.clients': 'OAuth 2.1 Clients', - 'settings.oauth.clientsHint': 'Register OAuth 2.1 clients to let third-party MCP applications (Claude Web, Cursor, etc.) connect without static tokens.', - 'settings.oauth.createClient': 'New Client', - 'settings.oauth.noClients': 'No OAuth clients registered.', - 'settings.oauth.clientId': 'Client ID', - 'settings.oauth.clientSecret': 'Client Secret', - 'settings.oauth.deleteClient': 'Delete Client', - 'settings.oauth.deleteClientMessage': 'This client and all active sessions will be permanently removed. Any application using it will lose access immediately.', - 'settings.oauth.rotateSecret': 'Rotate Secret', - 'settings.oauth.rotateSecretMessage': 'A new client secret will be generated and all existing sessions will be invalidated immediately. Update your application before closing this dialog.', - 'settings.oauth.rotateSecretConfirm': 'Rotate', - 'settings.oauth.rotateSecretConfirming': 'Rotating…', - 'settings.oauth.rotateSecretDoneTitle': 'New Secret Generated', - 'settings.oauth.rotateSecretDoneWarning': 'This secret is shown only once. Copy it now and update your application — all previous sessions have been invalidated.', - 'settings.oauth.activeSessions': 'Active OAuth Sessions', - 'settings.oauth.sessionScopes': 'Scopes', - 'settings.oauth.sessionExpires': 'Expires', - 'settings.oauth.revoke': 'Revoke', - 'settings.oauth.revokeSession': 'Revoke Session', - 'settings.oauth.revokeSessionMessage': 'This will immediately revoke access for this OAuth session.', - 'settings.oauth.modal.createTitle': 'Register OAuth Client', - 'settings.oauth.modal.presets': 'Quick presets', - 'settings.oauth.modal.clientName': 'Application Name', - 'settings.oauth.modal.clientNamePlaceholder': 'e.g. Claude Web, My MCP App', - 'settings.oauth.modal.redirectUris': 'Redirect URIs', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.', - 'settings.oauth.modal.scopes': 'Allowed Scopes', - 'settings.oauth.modal.scopesHint': 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.', - 'settings.oauth.modal.selectAll': 'Select all', - 'settings.oauth.modal.deselectAll': 'Deselect all', - 'settings.oauth.modal.creating': 'Registering…', - 'settings.oauth.modal.create': 'Register Client', - 'settings.oauth.modal.createdTitle': 'Client Registered', - 'settings.oauth.modal.createdWarning': 'The client secret is shown only once. Copy it now — it cannot be recovered.', - 'settings.oauth.toast.createError': 'Failed to register OAuth client', - 'settings.oauth.toast.deleted': 'OAuth client deleted', - 'settings.oauth.toast.deleteError': 'Failed to delete OAuth client', - 'settings.oauth.toast.revoked': 'Session revoked', - 'settings.oauth.toast.revokeError': 'Failed to revoke session', - 'settings.oauth.toast.rotateError': 'Failed to rotate client secret', - 'settings.oauth.modal.machineClient': 'Machine client (no browser login)', - 'settings.oauth.modal.machineClientHint': 'Use client_credentials grant — no redirect URIs needed. The token is issued directly via client_id + client_secret and acts as you within the selected scopes.', - 'settings.oauth.modal.machineClientUsage': 'Get a token: POST /oauth/token with grant_type=client_credentials, client_id, and client_secret. No browser, no refresh token.', - 'settings.oauth.badge.machine': 'machine', - 'settings.account': 'Account', - 'settings.about': 'About', - 'settings.about.reportBug': 'Report a Bug', - 'settings.about.reportBugHint': 'Found an issue? Let us know', - 'settings.about.featureRequest': 'Feature Request', - 'settings.about.featureRequestHint': 'Suggest a new feature', - 'settings.about.wikiHint': 'Documentation & guides', - 'settings.about.supporters.badge': 'Monthly Supporters', - 'settings.about.supporters.title': 'Travel companions for TREK', - 'settings.about.supporters.subtitle': "While you're planning your next route, these folks are helping plan TREK's future. Their monthly contribution goes straight into development and real hours spent — so TREK stays Open Source.", - 'settings.about.supporters.since': 'supporter since {date}', - 'settings.about.supporters.tierEmpty': 'Be the first', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK is a self-hosted travel planner that helps you organize your trips from the first idea to the last memory. Day planning, budget, packing lists, photos and much more — all in one place, on your own server.', - 'settings.about.madeWith': 'Made with', - 'settings.about.madeBy': 'by Maurice and a growing open-source community.', - 'settings.username': 'Username', - 'settings.email': 'Email', - 'settings.role': 'Role', - 'settings.roleAdmin': 'Administrator', - 'settings.oidcLinked': 'Linked with', - 'settings.changePassword': 'Change Password', - 'settings.currentPassword': 'Current password', - 'settings.currentPasswordRequired': 'Current password is required', - 'settings.newPassword': 'New password', - 'settings.confirmPassword': 'Confirm new password', - 'settings.updatePassword': 'Update password', - 'settings.passwordRequired': 'Please enter current and new password', - 'settings.passwordTooShort': 'Password must be at least 8 characters', - 'settings.passwordMismatch': 'Passwords do not match', - 'settings.passwordWeak': 'Password must contain uppercase, lowercase, a number, and a special character', - 'settings.passwordChanged': 'Password changed successfully', - 'settings.mustChangePassword': 'You must change your password before you can continue. Please set a new password below.', - 'settings.deleteAccount': 'Delete account', - 'settings.deleteAccountTitle': 'Delete your account?', - 'settings.deleteAccountWarning': 'Your account and all your trips, places, and files will be permanently deleted. This action cannot be undone.', - 'settings.deleteAccountConfirm': 'Delete permanently', - 'settings.deleteBlockedTitle': 'Deletion not possible', - 'settings.deleteBlockedMessage': 'You are the only administrator. Promote another user to admin before deleting your account.', - 'settings.roleUser': 'User', - 'settings.saveProfile': 'Save Profile', - 'settings.toast.mapSaved': 'Map settings saved', - 'settings.toast.keysSaved': 'API keys saved', - 'settings.toast.displaySaved': 'Display settings saved', - 'settings.toast.profileSaved': 'Profile saved', - 'settings.uploadAvatar': 'Upload Profile Picture', - 'settings.removeAvatar': 'Remove Profile Picture', - 'settings.avatarUploaded': 'Profile picture updated', - 'settings.avatarRemoved': 'Profile picture removed', - 'settings.avatarError': 'Upload failed', - 'settings.mfa.title': 'Two-factor authentication (2FA)', - 'settings.mfa.description': 'Adds a second step when you sign in with email and password. Use an authenticator app (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'Your administrator requires two-factor authentication. Set up an authenticator app below before continuing.', - 'settings.mfa.backupTitle': 'Backup codes', - 'settings.mfa.backupDescription': 'Use these one-time backup codes if you lose access to your authenticator app.', - 'settings.mfa.backupWarning': 'Save these codes now. Each code can only be used once.', - 'settings.mfa.backupCopy': 'Copy codes', - 'settings.mfa.backupDownload': 'Download TXT', - 'settings.mfa.backupPrint': 'Print / PDF', - 'settings.mfa.backupCopied': 'Backup codes copied', - 'settings.mfa.enabled': '2FA is enabled on your account.', - 'settings.mfa.disabled': '2FA is not enabled.', - 'settings.mfa.setup': 'Set up authenticator', - 'settings.mfa.scanQr': 'Scan this QR code with your app, or enter the secret manually.', - 'settings.mfa.secretLabel': 'Secret key (manual entry)', - 'settings.mfa.codePlaceholder': '6-digit code', - 'settings.mfa.enable': 'Enable 2FA', - 'settings.mfa.cancelSetup': 'Cancel', - 'settings.mfa.disableTitle': 'Disable 2FA', - 'settings.mfa.disableHint': 'Enter your account password and a current code from your authenticator.', - 'settings.mfa.disable': 'Disable 2FA', - 'settings.mfa.toastEnabled': 'Two-factor authentication enabled', - 'settings.mfa.toastDisabled': 'Two-factor authentication disabled', - 'settings.mfa.demoBlocked': 'Not available in demo mode', - - // Login - 'login.error': 'Login failed. Please check your credentials.', - 'login.tagline': 'Your Trips.\nYour Plan.', - 'login.description': 'Plan trips collaboratively with interactive maps, budgets, and real-time sync.', - 'login.features.maps': 'Interactive Maps', - 'login.features.mapsDesc': 'Google Places, routes & clustering', - 'login.features.realtime': 'Real-Time Sync', - 'login.features.realtimeDesc': 'Plan together via WebSocket', - 'login.features.budget': 'Budget Tracking', - 'login.features.budgetDesc': 'Categories, charts & per-person costs', - 'login.features.collab': 'Collaboration', - 'login.features.collabDesc': 'Multi-user with shared trips', - 'login.features.packing': 'Packing Lists', - 'login.features.packingDesc': 'Categories, progress & suggestions', - 'login.features.bookings': 'Reservations', - 'login.features.bookingsDesc': 'Flights, hotels, restaurants & more', - 'login.features.files': 'Documents', - 'login.features.filesDesc': 'Upload & manage documents', - 'login.features.routes': 'Smart Routes', - 'login.features.routesDesc': 'Auto-optimize & Google Maps export', - 'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Your data stays yours', - 'login.title': 'Sign In', - 'login.subtitle': 'Welcome back', - 'login.signingIn': 'Signing in…', - 'login.signIn': 'Sign In', - 'login.createAdmin': 'Create Admin Account', - 'login.createAdminHint': 'Set up the first admin account for TREK.', - 'login.setNewPassword': 'Set New Password', - 'login.setNewPasswordHint': 'You must change your password before continuing.', - 'login.createAccount': 'Create Account', - 'login.createAccountHint': 'Register a new account.', - 'login.creating': 'Creating…', - 'login.noAccount': "Don't have an account?", - 'login.hasAccount': 'Already have an account?', - 'login.register': 'Register', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': 'Username', - 'login.oidc.registrationDisabled': 'Registration is disabled. Contact your administrator.', - 'login.oidc.noEmail': 'No email received from provider.', - 'login.oidc.tokenFailed': 'Authentication failed.', - 'login.oidc.invalidState': 'Invalid session. Please try again.', - 'login.demoFailed': 'Demo login failed', - 'login.oidcSignIn': 'Sign in with {name}', - 'login.oidcOnly': 'Password authentication is disabled. Please sign in using your SSO provider.', - 'login.oidcLoggedOut': 'You have been logged out. Sign in again using your SSO provider.', - 'login.demoHint': 'Try the demo — no registration needed', - 'login.mfaTitle': 'Two-factor authentication', - 'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.', - 'login.mfaCodeLabel': 'Verification code', - 'login.mfaCodeRequired': 'Enter the code from your authenticator app.', - 'login.mfaHint': 'Open Google Authenticator, Authy, or another TOTP app.', - 'login.mfaBack': '← Back to sign in', - 'login.mfaVerify': 'Verify', - 'login.invalidInviteLink': 'Invalid or expired invite link', - 'login.oidcFailed': 'OIDC login failed', - 'login.usernameRequired': 'Username is required', - 'login.passwordMinLength': 'Password must be at least 8 characters', - 'login.forgotPassword': 'Forgot password?', - 'login.forgotPasswordTitle': 'Reset your password', - 'login.forgotPasswordBody': 'Enter the email address you signed up with. If an account exists, we\'ll send a reset link.', - 'login.forgotPasswordSubmit': 'Send reset link', - 'login.forgotPasswordSentTitle': 'Check your email', - 'login.forgotPasswordSentBody': 'If an account exists for that email, a reset link is on its way. It expires in 60 minutes.', - 'login.forgotPasswordSmtpHintOff': 'Heads up: your administrator hasn\'t configured SMTP, so the reset link will be written to the server console instead of being emailed.', - 'login.backToLogin': 'Back to sign in', - 'login.newPassword': 'New password', - 'login.confirmPassword': 'Confirm new password', - 'login.passwordsDontMatch': 'Passwords don\'t match', - 'login.mfaCode': '2FA code', - 'login.resetPasswordTitle': 'Set a new password', - 'login.resetPasswordBody': 'Pick a strong password you haven’t used here before. Minimum 8 characters.', - 'login.resetPasswordMfaBody': 'Enter your 2FA code or a backup code to complete the reset.', - 'login.resetPasswordSubmit': 'Reset password', - 'login.resetPasswordVerify': 'Verify & reset', - 'login.resetPasswordSuccessTitle': 'Password updated', - 'login.resetPasswordSuccessBody': 'You can now sign in with your new password.', - 'login.resetPasswordInvalidLink': 'Invalid reset link', - 'login.resetPasswordInvalidLinkBody': 'This link is missing or broken. Request a new one to continue.', - 'login.resetPasswordFailed': 'Reset failed. The link may have expired.', - - // Register - 'register.passwordMismatch': 'Passwords do not match', - 'register.passwordTooShort': 'Password must be at least 8 characters', - 'register.failed': 'Registration failed', - 'register.getStarted': 'Get Started', - 'register.subtitle': 'Create an account and start planning your dream trips.', - 'register.feature1': 'Unlimited trip plans', - 'register.feature2': 'Interactive map view', - 'register.feature3': 'Manage places and categories', - 'register.feature4': 'Track reservations', - 'register.feature5': 'Create packing lists', - 'register.feature6': 'Store photos and files', - 'register.createAccount': 'Create Account', - 'register.startPlanning': 'Start your trip planning', - 'register.minChars': 'Min. 6 characters', - 'register.confirmPassword': 'Confirm Password', - 'register.repeatPassword': 'Repeat password', - 'register.registering': 'Registering...', - 'register.register': 'Register', - 'register.hasAccount': 'Already have an account?', - 'register.signIn': 'Sign In', - - // Admin - 'admin.title': 'Administration', - 'admin.subtitle': 'User management and system settings', - 'admin.tabs.users': 'Users', - 'admin.tabs.categories': 'Categories', - 'admin.tabs.backup': 'Backup', - 'admin.tabs.notifications': 'Notifications', - 'admin.tabs.audit': 'Audit', - 'admin.stats.users': 'Users', - 'admin.stats.trips': 'Trips', - 'admin.stats.places': 'Places', - 'admin.stats.photos': 'Photos', - 'admin.stats.files': 'Files', - 'admin.table.user': 'User', - 'admin.table.email': 'Email', - 'admin.table.role': 'Role', - 'admin.table.created': 'Created', - 'admin.table.lastLogin': 'Last Login', - 'admin.table.actions': 'Actions', - 'admin.you': '(You)', - 'admin.editUser': 'Edit User', - 'admin.newPassword': 'New Password', - 'admin.newPasswordHint': 'Leave empty to keep current password', - 'admin.deleteUser': 'Delete user "{name}"? All trips will be permanently deleted.', - 'admin.deleteUserTitle': 'Delete user', - 'admin.newPasswordPlaceholder': 'Enter new password…', - 'admin.toast.loadError': 'Failed to load admin data', - 'admin.toast.userUpdated': 'User updated', - 'admin.toast.updateError': 'Failed to update', - 'admin.toast.userDeleted': 'User deleted', - 'admin.toast.deleteError': 'Failed to delete', - 'admin.toast.cannotDeleteSelf': 'Cannot delete your own account', - 'admin.toast.userCreated': 'User created', - 'admin.toast.createError': 'Failed to create user', - 'admin.toast.fieldsRequired': 'Username, email and password are required', - 'admin.createUser': 'Create User', - 'admin.invite.title': 'Invite Links', - 'admin.invite.subtitle': 'Create one-time registration links', - 'admin.invite.create': 'Create Link', - 'admin.invite.createAndCopy': 'Create & Copy', - 'admin.invite.empty': 'No invite links created yet', - 'admin.invite.maxUses': 'Max. Uses', - 'admin.invite.expiry': 'Expires after', - 'admin.invite.uses': 'used', - 'admin.invite.expiresAt': 'expires', - 'admin.invite.createdBy': 'by', - 'admin.invite.active': 'Active', - 'admin.invite.expired': 'Expired', - 'admin.invite.usedUp': 'Used up', - 'admin.invite.copied': 'Invite link copied to clipboard', - 'admin.invite.copyLink': 'Copy link', - 'admin.invite.deleted': 'Invite link deleted', - 'admin.invite.createError': 'Failed to create invite link', - 'admin.invite.deleteError': 'Failed to delete invite link', - 'admin.tabs.settings': 'Settings', - 'admin.allowRegistration': 'Allow Registration', - 'admin.allowRegistrationHint': 'New users can register themselves', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Require two-factor authentication (2FA)', - 'admin.requireMfaHint': 'Users without 2FA must complete setup in Settings before using the app.', - 'admin.apiKeys': 'API Keys', - 'admin.apiKeysHint': 'Optional. Enables extended place data like photos and weather.', - 'admin.mapsKey': 'Google Maps API Key', - 'admin.mapsKeyHint': 'Required for place search. Get at console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Without an API key, OpenStreetMap is used for place search. With a Google API key, photos, ratings, and opening hours can be loaded as well. Get one at console.cloud.google.com.', - 'admin.recommended': 'Recommended', - 'admin.weatherKey': 'OpenWeatherMap API Key', - 'admin.weatherKeyHint': 'For weather data. Free at openweathermap.org', - 'admin.validateKey': 'Test', - 'admin.keyValid': 'Connected', - 'admin.keyInvalid': 'Invalid', - 'admin.keySaved': 'API keys saved', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Allow login via external providers like Google, Apple, Authentik or Keycloak.', - 'admin.oidcDisplayName': 'Display Name', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'The OpenID Connect Issuer URL of the provider. e.g. https://accounts.google.com', - 'admin.oidcSaved': 'OIDC configuration saved', - 'admin.oidcOnlyMode': 'Disable password authentication', - 'admin.oidcOnlyModeHint': 'When enabled, only SSO login is permitted. Password-based login and registration are blocked.', - - // File Types - 'admin.fileTypes': 'Allowed File Types', - 'admin.fileTypesHint': 'Configure which file types users can upload.', - 'admin.fileTypesFormat': 'Comma-separated extensions (e.g. jpg,png,pdf,doc). Use * to allow all types.', - 'admin.fileTypesSaved': 'File type settings saved', - - 'admin.placesPhotos.title': 'Place Photos', - 'admin.placesPhotos.subtitle': 'Fetch photos from the Google Places API. Disable to save API quota. Wikimedia photos are unaffected.', - 'admin.placesAutocomplete.title': 'Place Autocomplete', - 'admin.placesAutocomplete.subtitle': 'Use the Google Places API for search suggestions. Disable to save API quota.', - 'admin.placesDetails.title': 'Place Details', - 'admin.placesDetails.subtitle': 'Fetch detailed place information (hours, rating, website) from the Google Places API. Disable to save API quota.', - // Packing Templates & Bag Tracking - 'admin.bagTracking.title': 'Bag Tracking', - 'admin.bagTracking.subtitle': 'Enable weight and bag assignment for packing items', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Real-time messaging for trip collaboration', - 'admin.collab.notes.title': 'Notes', - 'admin.collab.notes.subtitle': 'Shared notes and documents', - 'admin.collab.polls.title': 'Polls', - 'admin.collab.polls.subtitle': 'Group polls and voting', - 'admin.collab.whatsnext.title': "What's Next", - 'admin.collab.whatsnext.subtitle': 'Activity suggestions and next steps', - 'admin.tabs.config': 'Personalization', - 'admin.tabs.defaults': 'User Defaults', - 'admin.defaultSettings.title': 'Default User Settings', - 'admin.defaultSettings.description': 'Set instance-wide defaults. Users who have not changed a setting will see these values. Their own changes always take priority.', - 'admin.defaultSettings.saved': 'Default saved', - 'admin.defaultSettings.reset': 'Reset to built-in default', - 'admin.defaultSettings.resetToBuiltIn': 'reset', - 'admin.tabs.templates': 'Packing Templates', - 'admin.packingTemplates.title': 'Packing Templates', - 'admin.packingTemplates.subtitle': 'Create reusable packing lists for your trips', - 'admin.packingTemplates.create': 'New Template', - 'admin.packingTemplates.namePlaceholder': 'Template name (e.g. Beach Holiday)', - 'admin.packingTemplates.empty': 'No templates created yet', - 'admin.packingTemplates.items': 'items', - 'admin.packingTemplates.categories': 'categories', - 'admin.packingTemplates.itemName': 'Item name', - 'admin.packingTemplates.itemCategory': 'Category', - 'admin.packingTemplates.categoryName': 'Category name (e.g. Clothing)', - 'admin.packingTemplates.addCategory': 'Add category', - 'admin.packingTemplates.created': 'Template created', - 'admin.packingTemplates.deleted': 'Template deleted', - 'admin.packingTemplates.loadError': 'Failed to load templates', - 'admin.packingTemplates.createError': 'Failed to create template', - 'admin.packingTemplates.deleteError': 'Failed to delete template', - 'admin.packingTemplates.saveError': 'Failed to save', - - // Addons - 'admin.tabs.addons': 'Addons', - 'admin.addons.title': 'Addons', - 'admin.addons.subtitle': 'Enable or disable features to customize your TREK experience.', - 'admin.addons.catalog.packing.name': 'Lists', - 'admin.addons.catalog.packing.description': 'Packing lists and to-do tasks for your trips', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Track expenses and plan your trip budget', - 'admin.addons.catalog.documents.name': 'Documents', - 'admin.addons.catalog.documents.description': 'Store and manage travel documents', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Personal vacation planner with calendar view', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'World map with visited countries and travel stats', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Real-time notes, polls, and chat for trip planning', - 'admin.addons.catalog.memories.name': 'Photos (Immich)', - 'admin.addons.catalog.memories.description': 'Share trip photos via your Immich instance', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol for AI assistant integration', - 'admin.addons.subtitleBefore': 'Enable or disable features to customize your ', - 'admin.addons.subtitleAfter': ' experience.', - 'admin.addons.enabled': 'Enabled', - 'admin.addons.disabled': 'Disabled', - 'admin.addons.type.trip': 'Trip', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integration', - 'admin.addons.tripHint': 'Available as a tab within each trip', - 'admin.addons.globalHint': 'Available as a standalone section in the main navigation', - 'admin.addons.integrationHint': 'Backend services and API integrations with no dedicated page', - 'admin.addons.toast.updated': 'Addon updated', - 'admin.addons.toast.error': 'Failed to update addon', - 'admin.addons.noAddons': 'No addons available', - // Weather info - 'admin.weather.title': 'Weather Data', - 'admin.weather.badge': 'Since March 24, 2026', - 'admin.weather.description': 'TREK uses Open-Meteo as its weather data source. Open-Meteo is a free, open-source weather service — no API key required.', - 'admin.weather.forecast': '16-day forecast', - 'admin.weather.forecastDesc': 'Previously 5 days (OpenWeatherMap)', - 'admin.weather.climate': 'Historical climate data', - 'admin.weather.climateDesc': 'Averages from the last 85 years for days beyond the 16-day forecast', - 'admin.weather.requests': '10,000 requests / day', - 'admin.weather.requestsDesc': 'Free, no API key required', - 'admin.weather.locationHint': 'Weather is based on the first place with coordinates in each day. If no place is assigned to a day, any place from the place list is used as a reference.', - - // GitHub - 'admin.tabs.mcpTokens': 'MCP Access', - 'admin.mcpTokens.title': 'MCP Access', - 'admin.mcpTokens.subtitle': 'Manage OAuth sessions and API tokens across all users', - 'admin.mcpTokens.sectionTitle': 'API Tokens', - 'admin.mcpTokens.owner': 'Owner', - 'admin.mcpTokens.tokenName': 'Token Name', - 'admin.mcpTokens.created': 'Created', - 'admin.mcpTokens.lastUsed': 'Last Used', - 'admin.mcpTokens.never': 'Never', - 'admin.mcpTokens.empty': 'No MCP tokens have been created yet', - 'admin.mcpTokens.deleteTitle': 'Delete Token', - 'admin.mcpTokens.deleteMessage': 'This will revoke the token immediately. The user will lose MCP access through this token.', - 'admin.mcpTokens.deleteSuccess': 'Token deleted', - 'admin.mcpTokens.deleteError': 'Failed to delete token', - 'admin.mcpTokens.loadError': 'Failed to load tokens', - 'admin.oauthSessions.sectionTitle': 'OAuth Sessions', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Owner', - 'admin.oauthSessions.scopes': 'Scopes', - 'admin.oauthSessions.created': 'Created', - 'admin.oauthSessions.empty': 'No active OAuth sessions', - 'admin.oauthSessions.revokeTitle': 'Revoke Session', - 'admin.oauthSessions.revokeMessage': 'This will revoke the OAuth session immediately. The client will lose MCP access.', - 'admin.oauthSessions.revokeSuccess': 'Session revoked', - 'admin.oauthSessions.revokeError': 'Failed to revoke session', - 'admin.oauthSessions.loadError': 'Failed to load OAuth sessions', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Security-sensitive and administration events (backups, users, MFA, settings).', - 'admin.audit.empty': 'No audit entries yet.', - 'admin.audit.refresh': 'Refresh', - 'admin.audit.loadMore': 'Load more', - 'admin.audit.showing': '{count} loaded · {total} total', - 'admin.audit.col.time': 'Time', - 'admin.audit.col.user': 'User', - 'admin.audit.col.action': 'Action', - 'admin.audit.col.resource': 'Resource', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Details', - 'admin.github.title': 'Release History', - 'admin.github.subtitle': 'Latest updates from {repo}', - 'admin.github.latest': 'Latest', - 'admin.github.prerelease': 'Pre-release', - 'admin.github.showDetails': 'Show details', - 'admin.github.hideDetails': 'Hide details', - 'admin.github.loadMore': 'Load more', - 'admin.github.loading': 'Loading...', - 'admin.github.error': 'Failed to load releases', - 'admin.github.by': 'by', - 'admin.github.support': 'Helps me keep building TREK', - - 'admin.update.available': 'Update available', - 'admin.update.text': 'TREK {version} is available. You are running {current}.', - 'admin.update.button': 'View on GitHub', - 'admin.update.install': 'Install Update', - 'admin.update.confirmTitle': 'Install Update?', - 'admin.update.confirmText': 'TREK will be updated from {current} to {version}. The server will restart automatically afterwards.', - 'admin.update.dataInfo': 'All your data (trips, users, API keys, uploads, Vacay, Atlas, budgets) will be preserved.', - 'admin.update.warning': 'The app will be briefly unavailable during the restart.', - 'admin.update.confirm': 'Update Now', - 'admin.update.installing': 'Updating…', - 'admin.update.success': 'Update installed! Server is restarting…', - 'admin.update.failed': 'Update failed', - 'admin.update.backupHint': 'We recommend creating a backup before updating.', - 'admin.update.backupLink': 'Go to Backup', - 'admin.update.howTo': 'How to Update', - 'admin.update.dockerText': 'Your TREK instance runs in Docker. To update to {version}, run the following commands on your server:', - 'admin.update.reloadHint': 'Please reload the page in a few seconds.', - - // Vacay addon - 'vacay.subtitle': 'Plan and manage vacation days', - 'vacay.settings': 'Settings', - 'vacay.year': 'Year', - 'vacay.addYear': 'Add next year', - 'vacay.addPrevYear': 'Add previous year', - 'vacay.removeYear': 'Remove year', - 'vacay.removeYearConfirm': 'Remove {year}?', - 'vacay.removeYearHint': 'All vacation entries and company holidays for this year will be permanently deleted.', - 'vacay.remove': 'Remove', - 'vacay.persons': 'Persons', - 'vacay.noPersons': 'No persons added', - 'vacay.addPerson': 'Add Person', - 'vacay.editPerson': 'Edit Person', - 'vacay.removePerson': 'Remove Person', - 'vacay.removePersonConfirm': 'Remove {name}?', - 'vacay.removePersonHint': 'All vacation entries for this person will be permanently deleted.', - 'vacay.personName': 'Name', - 'vacay.personNamePlaceholder': 'Enter name', - 'vacay.color': 'Color', - 'vacay.add': 'Add', - 'vacay.legend': 'Legend', - 'vacay.publicHoliday': 'Public Holiday', - 'vacay.companyHoliday': 'Company Holiday', - 'vacay.weekend': 'Weekend', - 'vacay.modeVacation': 'Vacation', - 'vacay.modeCompany': 'Company Holiday', - 'vacay.entitlement': 'Entitlement', - 'vacay.entitlementDays': 'Days', - 'vacay.used': 'Used', - 'vacay.remaining': 'Left', - 'vacay.carriedOver': 'from {year}', - 'vacay.blockWeekends': 'Block Weekends', - 'vacay.blockWeekendsHint': 'Prevent vacation entries on weekend days', - 'vacay.weekendDays': 'Weekend days', - 'vacay.mon': 'Mon', - 'vacay.tue': 'Tue', - 'vacay.wed': 'Wed', - 'vacay.thu': 'Thu', - 'vacay.fri': 'Fri', - 'vacay.sat': 'Sat', - 'vacay.sun': 'Sun', - 'vacay.publicHolidays': 'Public Holidays', - 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar', - 'vacay.selectCountry': 'Select country', - 'vacay.selectRegion': 'Select region (optional)', - 'vacay.addCalendar': 'Add calendar', - 'vacay.calendarLabel': 'Label (optional)', - 'vacay.calendarColor': 'Color', - 'vacay.noCalendars': 'No holiday calendars added yet', - 'vacay.companyHolidays': 'Company Holidays', - 'vacay.companyHolidaysHint': 'Allow marking company-wide holiday days', - 'vacay.companyHolidaysNoDeduct': 'Company holidays do not count towards vacation days.', - 'vacay.weekStart': 'Week starts on', - 'vacay.weekStartHint': 'Choose whether the calendar week starts on Monday or Sunday', - 'vacay.carryOver': 'Carry Over', - 'vacay.carryOverHint': 'Automatically carry remaining vacation days into the next year', - 'vacay.sharing': 'Sharing', - 'vacay.sharingHint': 'Share your vacation plan with other TREK users', - 'vacay.owner': 'Owner', - 'vacay.shareEmailPlaceholder': 'Email of TREK user', - 'vacay.shareSuccess': 'Plan shared successfully', - 'vacay.shareError': 'Could not share plan', - 'vacay.dissolve': 'Dissolve Fusion', - 'vacay.dissolveHint': 'Separate calendars again. Your entries will be kept.', - 'vacay.dissolveAction': 'Dissolve', - 'vacay.dissolved': 'Calendar separated', - 'vacay.fusedWith': 'Fused with', - 'vacay.you': 'you', - 'vacay.noData': 'No data', - 'vacay.changeColor': 'Change color', - 'vacay.inviteUser': 'Invite User', - 'vacay.inviteHint': 'Invite another TREK user to share a combined vacation calendar.', - 'vacay.selectUser': 'Select user', - 'vacay.sendInvite': 'Send Invite', - 'vacay.inviteSent': 'Invite sent', - 'vacay.inviteError': 'Could not send invite', - 'vacay.pending': 'pending', - 'vacay.noUsersAvailable': 'No users available', - 'vacay.accept': 'Accept', - 'vacay.decline': 'Decline', - 'vacay.acceptFusion': 'Accept & Fuse', - 'vacay.inviteTitle': 'Fusion Request', - 'vacay.inviteWantsToFuse': 'wants to share a vacation calendar with you.', - 'vacay.fuseInfo1': 'Both of you will see all vacation entries in one shared calendar.', - 'vacay.fuseInfo2': 'Both parties can create and edit entries for each other.', - 'vacay.fuseInfo3': 'Both parties can delete entries and change vacation entitlements.', - 'vacay.fuseInfo4': 'Settings like public holidays and company holidays are shared.', - 'vacay.fuseInfo5': 'The fusion can be dissolved at any time by either party. Your entries will be preserved.', - 'nav.myTrips': 'My Trips', - - // Atlas addon - 'atlas.subtitle': 'Your travel footprint around the world', - 'atlas.countries': 'Countries', - 'atlas.trips': 'Trips', - 'atlas.places': 'Places', - 'atlas.unmark': 'Remove', - 'atlas.confirmMark': 'Mark this country as visited?', - 'atlas.confirmUnmark': 'Remove this country from your visited list?', - 'atlas.confirmUnmarkRegion': 'Remove this region from your visited list?', - 'atlas.markVisited': 'Mark as visited', - 'atlas.markVisitedHint': 'Add this country to your visited list', - 'atlas.markRegionVisitedHint': 'Add this region to your visited list', - 'atlas.addToBucket': 'Add to bucket list', - 'atlas.addPoi': 'Add place', - 'atlas.searchCountry': 'Search a country...', - 'atlas.bucketNamePlaceholder': 'Name (country, city, place...)', - 'atlas.month': 'Month', - 'atlas.year': 'Year', - 'atlas.addToBucketHint': 'Save as a place you want to visit', - 'atlas.bucketWhen': 'When do you plan to visit?', - 'atlas.statsTab': 'Stats', - 'atlas.bucketTab': 'Bucket List', - 'atlas.addBucket': 'Add to bucket list', - 'atlas.bucketNotesPlaceholder': 'Notes (optional)', - 'atlas.bucketEmpty': 'Your bucket list is empty', - 'atlas.bucketEmptyHint': 'Add places you dream of visiting', - 'atlas.days': 'Days', - 'atlas.visitedCountries': 'Visited Countries', - 'atlas.cities': 'Cities', - 'atlas.noData': 'No travel data yet', - 'atlas.noDataHint': 'Create a trip and add places to see your world map', - 'atlas.lastTrip': 'Last trip', - 'atlas.nextTrip': 'Next trip', - 'atlas.daysLeft': 'days left', - 'atlas.streak': 'Streak', - 'atlas.years': 'years', - 'atlas.yearInRow': 'year in a row', - 'atlas.yearsInRow': 'years in a row', - 'atlas.tripIn': 'trip in', - 'atlas.tripsIn': 'trips in', - 'atlas.since': 'since', - 'atlas.europe': 'Europe', - 'atlas.asia': 'Asia', - 'atlas.northAmerica': 'N. America', - 'atlas.southAmerica': 'S. America', - 'atlas.africa': 'Africa', - 'atlas.oceania': 'Oceania', - 'atlas.other': 'Other', - 'atlas.firstVisit': 'First trip', - 'atlas.lastVisitLabel': 'Last trip', - 'atlas.tripSingular': 'Trip', - 'atlas.tripPlural': 'Trips', - 'atlas.placeVisited': 'Place visited', - 'atlas.placesVisited': 'Places visited', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transports', - 'trip.tabs.reservations': 'Bookings', - 'trip.tabs.reservationsShort': 'Book', - 'trip.tabs.packing': 'Packing List', - 'trip.tabs.packingShort': 'Packing', - 'trip.tabs.lists': 'Lists', - 'trip.tabs.listsShort': 'Lists', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'Files', - 'trip.loading': 'Loading trip...', - 'trip.loadingPhotos': 'Loading place photos...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Places', - 'trip.toast.placeUpdated': 'Place updated', - 'trip.toast.placeAdded': 'Place added', - 'trip.toast.placeDeleted': 'Place deleted', - 'trip.toast.selectDay': 'Please select a day first', - 'trip.toast.assignedToDay': 'Place assigned to day', - 'trip.toast.reorderError': 'Failed to reorder', - 'trip.toast.reservationUpdated': 'Reservation updated', - 'trip.toast.reservationAdded': 'Reservation added', - 'trip.toast.deleted': 'Deleted', - 'trip.confirm.deletePlace': 'Are you sure you want to delete this place?', - 'trip.confirm.deletePlaces': 'Delete {count} places?', - 'trip.toast.placesDeleted': '{count} places deleted', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'No places planned for this day', - 'dayplan.cannotReorderTransport': 'Bookings with a fixed time cannot be reordered', - 'dayplan.confirmRemoveTimeTitle': 'Remove time?', - 'dayplan.confirmRemoveTimeBody': 'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.', - 'dayplan.confirmRemoveTimeAction': 'Remove time & move', - 'dayplan.cannotDropOnTimed': 'Items cannot be placed between time-bound entries', - 'dayplan.cannotBreakChronology': 'This would break the chronological order of timed items and bookings', - 'dayplan.addNote': 'Add Note', - 'dayplan.expandAll': 'Expand all days', - 'dayplan.collapseAll': 'Collapse all days', - 'dayplan.editNote': 'Edit Note', - 'dayplan.noteAdd': 'Add Note', - 'dayplan.noteEdit': 'Edit Note', - 'dayplan.noteTitle': 'Note', - 'dayplan.noteSubtitle': 'Daily Note', - 'dayplan.totalCost': 'Total Cost', - 'dayplan.days': 'Days', - 'dayplan.dayN': 'Day {n}', - 'dayplan.calculating': 'Calculating...', - 'dayplan.route': 'Route', - 'dayplan.optimize': 'Optimize', - 'dayplan.optimized': 'Route optimized', - 'dayplan.routeError': 'Failed to calculate route', - 'dayplan.toast.needTwoPlaces': 'At least two places needed for route optimization', - 'dayplan.toast.routeOptimized': 'Route optimized', - 'dayplan.toast.noGeoPlaces': 'No places with coordinates found for route calculation', - 'dayplan.confirmed': 'Confirmed', - 'dayplan.pendingRes': 'Pending', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Export day plan as PDF', - 'dayplan.pdfError': 'Failed to export PDF', - - // Places Sidebar - 'places.addPlace': 'Add Place/Activity', - 'places.importFile': 'Import file', - 'places.sidebarDrop': 'Drop to import', - 'places.importFileHint': 'Import .gpx, .kml or .kmz files from tools like Google My Maps, Google Earth, or a GPS tracker.', - 'places.importFileDropHere': 'Click to select a file or drag and drop here', - 'places.importFileDropActive': 'Drop file to select', - 'places.importFileUnsupported': 'Unsupported file type. Use .gpx, .kml or .kmz.', - 'places.importFileTooLarge': 'File is too large. Maximum upload size is {maxMb} MB.', - 'places.importFileError': 'Import failed', - 'places.importAllSkipped': 'All places were already in the trip.', - 'places.gpxImported': '{count} places imported from GPX', - 'places.gpxImportTypes': 'What do you want to import?', - 'places.gpxImportWaypoints': 'Waypoints', - 'places.gpxImportRoutes': 'Routes', - 'places.gpxImportTracks': 'Tracks (with path geometry)', - 'places.gpxImportNoneSelected': 'Select at least one type to import.', - 'places.kmlImportTypes': 'What do you want to import?', - 'places.kmlImportPoints': 'Points (Placemarks)', - 'places.kmlImportPaths': 'Paths (LineStrings)', - 'places.kmlImportNoneSelected': 'Select at least one type to import.', - 'places.selectionCount': '{count} selected', - 'places.deleteSelected': 'Delete selected', - 'places.kmlKmzImported': '{count} places imported from KMZ/KML', - 'places.urlResolved': 'Place imported from URL', - 'places.importList': 'List Import', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Imported: {created} • Skipped: {skipped}', - 'places.importGoogleList': 'Google List', - 'places.importNaverList': 'Naver List', - 'places.googleListHint': 'Paste a shared Google Maps list link to import all places.', - 'places.googleListImported': '{count} places imported from "{list}"', - 'places.googleListError': 'Failed to import Google Maps list', - 'places.naverListHint': 'Paste a shared Naver Maps list link to import all places.', - 'places.naverListImported': '{count} places imported from "{list}"', - 'places.naverListError': 'Failed to import Naver Maps list', - 'places.viewDetails': 'View Details', - 'places.assignToDay': 'Add to which day?', - 'places.all': 'All', - 'places.unplanned': 'Unplanned', - 'places.filterTracks': 'Tracks', - 'places.search': 'Search places...', - 'places.allCategories': 'All Categories', - 'places.categoriesSelected': 'categories', - 'places.clearFilter': 'Clear filter', - 'places.count': '{count} places', - 'places.countSingular': '1 place', - 'places.allPlanned': 'All places are planned', - 'places.noneFound': 'No places found', - 'places.editPlace': 'Edit Place', - 'places.formName': 'Name', - 'places.formNamePlaceholder': 'e.g. Eiffel Tower', - 'places.formDescription': 'Description', - 'places.formDescriptionPlaceholder': 'Short description...', - 'places.formAddress': 'Address', - 'places.formAddressPlaceholder': 'Street, City, Country', - 'places.formLat': 'Latitude (e.g. 48.8566)', - 'places.formLng': 'Longitude (e.g. 2.3522)', - 'places.formCategory': 'Category', - 'places.noCategory': 'No Category', - 'places.categoryNamePlaceholder': 'Category name', - 'places.formTime': 'Time', - 'places.startTime': 'Start', - 'places.endTime': 'End', - 'places.endTimeBeforeStart': 'End time is before start time', - 'places.timeCollision': 'Time overlap with:', - 'places.formWebsite': 'Website', - 'places.formNotes': 'Notes', - 'places.formNotesPlaceholder': 'Personal notes...', - 'places.formReservation': 'Reservation', - 'places.reservationNotesPlaceholder': 'Reservation notes, confirmation number...', - 'places.mapsSearchPlaceholder': 'Search places...', - 'places.mapsSearchError': 'Place search failed.', - 'places.loadingDetails': 'Loading place details…', - 'places.osmHint': 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', - 'places.osmActive': 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.', - 'places.categoryCreateError': 'Failed to create category', - 'places.nameRequired': 'Please enter a name', - 'places.saveError': 'Failed to save', - // Place Inspector - 'inspector.opened': 'Open', - 'inspector.closed': 'Closed', - 'inspector.openingHours': 'Opening Hours', - 'inspector.showHours': 'Show opening hours', - 'inspector.files': 'Files', - 'inspector.filesCount': '{count} files', - 'inspector.remove': 'Remove', - 'inspector.removeFromDay': 'Remove from Day', - 'inspector.addToDay': 'Add to Day', - 'inspector.confirmedRes': 'Confirmed Reservation', - 'inspector.pendingRes': 'Pending Reservation', - 'inspector.google': 'Open in Google Maps', - 'inspector.website': 'Open Website', - 'inspector.addRes': 'Reservation', - 'inspector.editRes': 'Edit Reservation', - 'inspector.participants': 'Participants', - 'inspector.trackStats': 'Track Stats', - - // Reservations - 'reservations.title': 'Bookings', - 'reservations.empty': 'No reservations yet', - 'reservations.emptyHint': 'Add reservations for flights, hotels and more', - 'reservations.add': 'Add Reservation', - 'reservations.addManual': 'Manual Booking', - 'reservations.placeHint': 'Tip: Reservations are best created directly from a place to link them with your day plan.', - 'reservations.confirmed': 'Confirmed', - 'reservations.pending': 'Pending', - 'reservations.summary': '{confirmed} confirmed, {pending} pending', - 'reservations.fromPlan': 'From Plan', - 'reservations.showFiles': 'Show Files', - 'reservations.editTitle': 'Edit Reservation', - 'reservations.status': 'Status', - 'reservations.datetime': 'Date & Time', - 'reservations.startTime': 'Start time', - 'reservations.endTime': 'End time', - 'reservations.date': 'Date', - 'reservations.time': 'Time', - 'reservations.timeAlt': 'Time (alternative, e.g. 19:30)', - 'reservations.notes': 'Notes', - 'reservations.notesPlaceholder': 'Additional notes...', - 'reservations.meta.airline': 'Airline', - 'reservations.meta.flightNumber': 'Flight No.', - 'reservations.meta.from': 'From', - 'reservations.meta.to': 'To', - 'reservations.needsReview': 'Review', - 'reservations.needsReviewHint': 'Airport could not be matched automatically — please confirm the location.', - 'reservations.searchLocation': 'Search station, port, address…', - 'airport.searchPlaceholder': 'Airport code or city (e.g. FRA)', - 'map.connections': 'Connections', - 'map.showConnections': 'Show booking routes', - 'map.hideConnections': 'Hide booking routes', - 'reservations.meta.trainNumber': 'Train No.', - 'reservations.meta.platform': 'Platform', - 'reservations.meta.seat': 'Seat', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in until', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Accommodation', - 'reservations.meta.pickAccommodation': 'Link to accommodation', - 'reservations.meta.noAccommodation': 'None', - 'reservations.meta.hotelPlace': 'Accommodation', - 'reservations.meta.pickHotel': 'Select accommodation', - 'reservations.meta.fromDay': 'From', - 'reservations.meta.toDay': 'To', - 'reservations.meta.selectDay': 'Select day', - 'reservations.type.flight': 'Flight', - 'reservations.type.hotel': 'Accommodation', - 'reservations.type.restaurant': 'Restaurant', - 'reservations.type.train': 'Train', - 'reservations.type.car': 'Car', - 'reservations.type.cruise': 'Cruise', - 'reservations.type.event': 'Event', - 'reservations.type.tour': 'Tour', - 'reservations.type.other': 'Other', - 'reservations.confirm.delete': 'Are you sure you want to delete the reservation "{name}"?', - 'reservations.confirm.deleteTitle': 'Delete booking?', - 'reservations.confirm.deleteBody': '"{name}" will be permanently deleted.', - 'reservations.toast.updated': 'Reservation updated', - 'reservations.toast.removed': 'Reservation deleted', - 'reservations.toast.fileUploaded': 'File uploaded', - 'reservations.toast.uploadError': 'Failed to upload', - 'reservations.newTitle': 'New Reservation', - 'reservations.bookingType': 'Booking Type', - 'reservations.titleLabel': 'Title', - 'reservations.titlePlaceholder': 'e.g. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Location / Address', - 'reservations.locationPlaceholder': 'Address, Airport, Hotel...', - 'reservations.confirmationCode': 'Booking Code', - 'reservations.confirmationPlaceholder': 'e.g. ABC12345', - 'reservations.day': 'Day', - 'reservations.noDay': 'No Day', - 'reservations.place': 'Place', - 'reservations.noPlace': 'No Place', - 'reservations.pendingSave': 'will be saved…', - 'reservations.uploading': 'Uploading...', - 'reservations.attachFile': 'Attach file', - 'reservations.linkExisting': 'Link existing file', - 'reservations.toast.saveError': 'Failed to save', - 'reservations.toast.updateError': 'Failed to update', - 'reservations.toast.deleteError': 'Failed to delete', - 'reservations.confirm.remove': 'Remove reservation for "{name}"?', - 'reservations.linkAssignment': 'Link to day assignment', - 'reservations.pickAssignment': 'Select an assignment from your plan...', - 'reservations.noAssignment': 'No link (standalone)', - 'reservations.price': 'Price', - 'reservations.budgetCategory': 'Budget category', - 'reservations.budgetCategoryPlaceholder': 'e.g. Transport, Accommodation', - 'reservations.budgetCategoryAuto': 'Auto (from booking type)', - 'reservations.budgetHint': 'A budget entry will be created automatically when saving.', - 'reservations.departureDate': 'Departure', - 'reservations.arrivalDate': 'Arrival', - 'reservations.departureTime': 'Dep. time', - 'reservations.arrivalTime': 'Arr. time', - 'reservations.pickupDate': 'Pickup', - 'reservations.returnDate': 'Return', - 'reservations.pickupTime': 'Pickup time', - 'reservations.returnTime': 'Return time', - 'reservations.endDate': 'End date', - 'reservations.meta.departureTimezone': 'Dep. TZ', - 'reservations.meta.arrivalTimezone': 'Arr. TZ', - 'reservations.span.departure': 'Departure', - 'reservations.span.arrival': 'Arrival', - 'reservations.span.inTransit': 'In transit', - 'reservations.span.pickup': 'Pickup', - 'reservations.span.return': 'Return', - 'reservations.span.active': 'Active', - 'reservations.span.start': 'Start', - 'reservations.span.end': 'End', - 'reservations.span.ongoing': 'Ongoing', - 'reservations.validation.endBeforeStart': 'End date/time must be after start date/time', - 'reservations.addBooking': 'Add booking', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'Export CSV', - 'budget.emptyTitle': 'No budget created yet', - 'budget.emptyText': 'Create categories and entries to plan your travel budget', - 'budget.emptyPlaceholder': 'Enter category name...', - 'budget.createCategory': 'Create Category', - 'budget.category': 'Category', - 'budget.categoryName': 'Category Name', - 'budget.table.name': 'Name', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Persons', - 'budget.table.days': 'Days', - 'budget.table.perPerson': 'Per Person', - 'budget.table.perDay': 'Per Day', - 'budget.table.perPersonDay': 'P. p / Day', - 'budget.table.note': 'Note', - 'budget.table.date': 'Date', - 'budget.newEntry': 'New Entry', - 'budget.defaultEntry': 'New Entry', - 'budget.defaultCategory': 'New Category', - 'budget.total': 'Total', - 'budget.totalBudget': 'Total Budget', - 'budget.byCategory': 'By Category', - 'budget.editTooltip': 'Click to edit', - 'budget.linkedToReservation': 'Linked to a reservation — edit the name there', - 'budget.confirm.deleteCategory': 'Are you sure you want to delete the category "{name}" with {count} entries?', - 'budget.deleteCategory': 'Delete Category', - 'budget.perPerson': 'Per Person', - 'budget.paid': 'Paid', - 'budget.open': 'Open', - 'budget.noMembers': 'No members assigned', - 'budget.settlement': 'Settlement', - 'budget.settlementInfo': 'Click a member avatar on a budget item to mark them green — this means they paid. The settlement then shows who owes whom and how much.', - 'budget.netBalances': 'Net Balances', - - // Files - 'files.title': 'Files', - 'files.pageTitle': 'Files & Documents', - 'files.subtitle': '{count} files for {trip}', - 'files.download': 'Download', - 'files.openError': 'Could not open file', - 'files.downloadPdf': 'Download PDF', - 'files.count': '{count} files', - 'files.countSingular': '1 file', - 'files.uploaded': '{count} uploaded', - 'files.uploadError': 'Upload failed', - 'files.dropzone': 'Drop files here', - 'files.dropzoneHint': 'or click to browse', - 'files.allowedTypes': 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Uploading...', - 'files.filterAll': 'All', - 'files.filterPdf': 'PDFs', - 'files.filterImages': 'Images', - 'files.filterDocs': 'Documents', - 'files.filterCollab': 'Collab Notes', - 'files.sourceCollab': 'From Collab Notes', - 'files.empty': 'No files yet', - 'files.emptyHint': 'Upload files to attach them to your trip', - 'files.openTab': 'Open in new tab', - 'files.confirm.delete': 'Are you sure you want to delete this file?', - 'files.toast.deleted': 'File deleted', - 'files.toast.deleteError': 'Failed to delete file', - 'files.sourcePlan': 'Day Plan', - 'files.sourceBooking': 'Booking', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Attach', - 'files.pasteHint': 'You can also paste images from clipboard (Ctrl+V)', - 'files.trash': 'Trash', - 'files.trashEmpty': 'Trash is empty', - 'files.emptyTrash': 'Empty Trash', - 'files.restore': 'Restore', - 'files.star': 'Star', - 'files.unstar': 'Unstar', - 'files.assign': 'Assign', - 'files.assignTitle': 'Assign File', - 'files.assignPlace': 'Place', - 'files.assignBooking': 'Booking', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Unassigned', - 'files.unlink': 'Remove link', - 'files.toast.trashed': 'Moved to trash', - 'files.toast.restored': 'File restored', - 'files.toast.trashEmptied': 'Trash emptied', - 'files.toast.assigned': 'File assigned', - 'files.toast.assignError': 'Assignment failed', - 'files.toast.restoreError': 'Restore failed', - 'files.confirm.permanentDelete': 'Permanently delete this file? This cannot be undone.', - 'files.confirm.emptyTrash': 'Permanently delete all trashed files? This cannot be undone.', - 'files.noteLabel': 'Note', - 'files.notePlaceholder': 'Add a note...', - - // Packing - 'packing.title': 'Packing List', - 'packing.empty': 'Packing list is empty', - 'packing.import': 'Import', - 'packing.importTitle': 'Import Packing List', - 'packing.importHint': 'One item per line. Format: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional)', - 'packing.importPlaceholder': 'Hygiene, Toothbrush\nClothing, T-Shirts, 200\nDocuments, Passport, , Carry-on\nElectronics, Charger, 50, Suitcase, checked', - 'packing.importCsv': 'Load CSV/TXT', - 'packing.importAction': 'Import {count}', - 'packing.importSuccess': '{count} items imported', - 'packing.importError': 'Import failed', - 'packing.importEmpty': 'No items to import', - 'packing.progress': '{packed} of {total} packed ({percent}%)', - 'packing.clearChecked': 'Remove {count} checked', - 'packing.clearCheckedShort': 'Remove {count}', - 'packing.suggestions': 'Suggestions', - 'packing.suggestionsTitle': 'Add Suggestions', - 'packing.allSuggested': 'All suggestions added', - 'packing.allPacked': 'All packed!', - 'packing.addPlaceholder': 'Add new item...', - 'packing.categoryPlaceholder': 'Category...', - 'packing.filterAll': 'All', - 'packing.filterOpen': 'Open', - 'packing.filterDone': 'Done', - 'packing.emptyTitle': 'Packing list is empty', - 'packing.emptyHint': 'Add items or use the suggestions', - 'packing.emptyFiltered': 'No items match this filter', - 'packing.menuRename': 'Rename', - 'packing.menuCheckAll': 'Check All', - 'packing.menuUncheckAll': 'Uncheck All', - 'packing.menuDeleteCat': 'Delete Category', - 'packing.noMembers': 'No trip members', - 'packing.addItem': 'Add item', - 'packing.addItemPlaceholder': 'Item name...', - 'packing.addCategory': 'Add category', - 'packing.newCategoryPlaceholder': 'Category name (e.g. Clothing)', - 'packing.applyTemplate': 'Apply template', - 'packing.template': 'Template', - 'packing.templateApplied': '{count} items added from template', - 'packing.templateError': 'Failed to apply template', - 'packing.saveAsTemplate': 'Save as template', - 'packing.templateName': 'Template name', - 'packing.templateSaved': 'Packing list saved as template', - 'packing.bags': 'Bags', - 'packing.noBag': 'Unassigned', - 'packing.totalWeight': 'Total weight', - 'packing.bagName': 'Bag name...', - 'packing.addBag': 'Add bag', - 'packing.changeCategory': 'Change Category', - 'packing.confirm.clearChecked': 'Are you sure you want to remove {count} checked items?', - 'packing.confirm.deleteCat': 'Are you sure you want to delete the category "{name}" with {count} items?', - 'packing.defaultCategory': 'Other', - 'packing.toast.saveError': 'Failed to save', - 'packing.toast.deleteError': 'Failed to delete', - 'packing.toast.renameError': 'Failed to rename', - 'packing.toast.addError': 'Failed to add', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Passport', category: 'Documents' }, - { name: 'ID Card', category: 'Documents' }, - { name: 'Travel Insurance', category: 'Documents' }, - { name: 'Flight Tickets', category: 'Documents' }, - { name: 'Credit Card', category: 'Finances' }, - { name: 'Cash', category: 'Finances' }, - { name: 'Visa', category: 'Documents' }, - { name: 'T-Shirts', category: 'Clothing' }, - { name: 'Pants', category: 'Clothing' }, - { name: 'Underwear', category: 'Clothing' }, - { name: 'Socks', category: 'Clothing' }, - { name: 'Jacket', category: 'Clothing' }, - { name: 'Sleepwear', category: 'Clothing' }, - { name: 'Swimwear', category: 'Clothing' }, - { name: 'Rain Jacket', category: 'Clothing' }, - { name: 'Comfortable Shoes', category: 'Clothing' }, - { name: 'Toothbrush', category: 'Toiletries' }, - { name: 'Toothpaste', category: 'Toiletries' }, - { name: 'Shampoo', category: 'Toiletries' }, - { name: 'Deodorant', category: 'Toiletries' }, - { name: 'Sunscreen', category: 'Toiletries' }, - { name: 'Razor', category: 'Toiletries' }, - { name: 'Charger', category: 'Electronics' }, - { name: 'Power Bank', category: 'Electronics' }, - { name: 'Headphones', category: 'Electronics' }, - { name: 'Travel Adapter', category: 'Electronics' }, - { name: 'Camera', category: 'Electronics' }, - { name: 'Pain Medication', category: 'Health' }, - { name: 'Band-Aids', category: 'Health' }, - { name: 'Disinfectant', category: 'Health' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Share Trip', - 'members.inviteUser': 'Invite User', - 'members.selectUser': 'Select user…', - 'members.invite': 'Invite', - 'members.allHaveAccess': 'All users already have access.', - 'members.access': 'Access', - 'members.person': 'person', - 'members.persons': 'persons', - 'members.you': 'you', - 'members.owner': 'Owner', - 'members.leaveTrip': 'Leave trip', - 'members.removeAccess': 'Remove access', - 'members.confirmLeave': 'Leave trip? You will lose access.', - 'members.confirmRemove': 'Remove access for this user?', - 'members.loadError': 'Failed to load members', - 'members.added': 'added', - 'members.addError': 'Failed to add', - 'members.removed': 'Member removed', - 'members.removeError': 'Failed to remove', - - // Categories (Admin) - 'categories.title': 'Categories', - 'categories.subtitle': 'Manage categories for places', - 'categories.new': 'New Category', - 'categories.empty': 'No categories yet', - 'categories.namePlaceholder': 'Category name', - 'categories.icon': 'Icon', - 'categories.color': 'Color', - 'categories.customColor': 'Choose custom color', - 'categories.preview': 'Preview', - 'categories.defaultName': 'Category', - 'categories.update': 'Update', - 'categories.create': 'Create', - 'categories.confirm.delete': 'Delete category? Places in this category will not be deleted.', - 'categories.toast.loadError': 'Failed to load categories', - 'categories.toast.nameRequired': 'Please enter a name', - 'categories.toast.updated': 'Category updated', - 'categories.toast.created': 'Category created', - 'categories.toast.saveError': 'Failed to save', - 'categories.toast.deleted': 'Category deleted', - 'categories.toast.deleteError': 'Failed to delete', - - // Backup (Admin) - 'backup.title': 'Data Backup', - 'backup.subtitle': 'Database and all uploaded files', - 'backup.refresh': 'Refresh', - 'backup.upload': 'Upload Backup', - 'backup.uploading': 'Uploading…', - 'backup.create': 'Create Backup', - 'backup.creating': 'Creating…', - 'backup.empty': 'No backups yet', - 'backup.createFirst': 'Create first backup', - 'backup.download': 'Download', - 'backup.restore': 'Restore', - 'backup.confirm.restore': 'Restore backup "{name}"?\n\nAll current data will be replaced with the backup.', - 'backup.confirm.uploadRestore': 'Upload and restore backup file "{name}"?\n\nAll current data will be overwritten.', - 'backup.confirm.delete': 'Delete backup "{name}"?', - 'backup.toast.loadError': 'Failed to load backups', - 'backup.toast.created': 'Backup created successfully', - 'backup.toast.createError': 'Failed to create backup', - 'backup.toast.restored': 'Backup restored. Page will reload…', - 'backup.toast.restoreError': 'Failed to restore', - 'backup.toast.uploadError': 'Failed to upload', - 'backup.toast.deleted': 'Backup deleted', - 'backup.toast.deleteError': 'Failed to delete', - 'backup.toast.downloadError': 'Download failed', - 'backup.toast.settingsSaved': 'Auto-backup settings saved', - 'backup.toast.settingsError': 'Failed to save settings', - 'backup.auto.title': 'Auto-Backup', - 'backup.auto.subtitle': 'Automatic backup on a schedule', - 'backup.auto.enable': 'Enable auto-backup', - 'backup.auto.enableHint': 'Backups will be created automatically on the chosen schedule', - 'backup.auto.interval': 'Interval', - 'backup.auto.hour': 'Run at hour', - 'backup.auto.hourHint': 'Server local time ({format} format)', - 'backup.auto.dayOfWeek': 'Day of week', - 'backup.auto.dayOfMonth': 'Day of month', - 'backup.auto.dayOfMonthHint': 'Limited to 1–28 for compatibility with all months', - 'backup.auto.scheduleSummary': 'Schedule', - 'backup.auto.summaryDaily': 'Every day at {hour}:00', - 'backup.auto.summaryWeekly': 'Every {day} at {hour}:00', - 'backup.auto.summaryMonthly': 'Day {day} of every month at {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Auto-backup is configured via Docker environment variables. To change these settings, update your docker-compose.yml and restart the container.', - 'backup.auto.copyEnv': 'Copy Docker env vars', - 'backup.auto.envCopied': 'Docker env vars copied to clipboard', - 'backup.auto.keepLabel': 'Delete old backups after', - 'backup.dow.sunday': 'Sun', - 'backup.dow.monday': 'Mon', - 'backup.dow.tuesday': 'Tue', - 'backup.dow.wednesday': 'Wed', - 'backup.dow.thursday': 'Thu', - 'backup.dow.friday': 'Fri', - 'backup.dow.saturday': 'Sat', - 'backup.interval.hourly': 'Hourly', - 'backup.interval.daily': 'Daily', - 'backup.interval.weekly': 'Weekly', - 'backup.interval.monthly': 'Monthly', - 'backup.keep.1day': '1 day', - 'backup.keep.3days': '3 days', - 'backup.keep.7days': '7 days', - 'backup.keep.14days': '14 days', - 'backup.keep.30days': '30 days', - 'backup.keep.forever': 'Keep forever', - - // Photos - 'photos.title': 'Photos', - 'photos.subtitle': '{count} photos for {trip}', - 'photos.dropHere': 'Drop photos here...', - 'photos.dropHereActive': 'Drop photos here', - 'photos.captionForAll': 'Caption (for all)', - 'photos.captionPlaceholder': 'Optional caption...', - 'photos.addCaption': 'Add caption...', - 'photos.allDays': 'All Days', - 'photos.noPhotos': 'No photos yet', - 'photos.uploadHint': 'Upload your travel photos', - 'photos.clickToSelect': 'or click to select', - 'photos.linkPlace': 'Link Place', - 'photos.noPlace': 'No Place', - 'photos.uploadN': '{n} photo(s) upload', - 'photos.linkDay': 'Link Day', - 'photos.noDay': 'No Day', - 'photos.dayLabel': 'Day {number}', - 'photos.photoSelected': 'Photo selected', - 'photos.photosSelected': 'Photos selected', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · up to 30 photos', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Restore Backup?', - 'backup.restoreWarning': 'All current data (trips, places, users, uploads) will be permanently replaced by the backup. This action cannot be undone.', - 'backup.restoreTip': 'Tip: Create a backup of the current state before restoring.', - 'backup.restoreConfirm': 'Yes, restore', - - // PDF - 'pdf.travelPlan': 'Travel Plan', - 'pdf.planned': 'Planned', - 'pdf.costLabel': 'Cost EUR', - 'pdf.preview': 'PDF Preview', - 'pdf.saveAsPdf': 'Save as PDF', - - // Planner - 'planner.places': 'Places', - 'planner.bookings': 'Bookings', - 'planner.packingList': 'Packing List', - 'planner.documents': 'Documents', - 'planner.dayPlan': 'Day Plan', - 'planner.reservations': 'Reservations', - 'planner.minTwoPlaces': 'At least 2 places with coordinates needed', - 'planner.noGeoPlaces': 'No places with coordinates available', - 'planner.routeCalculated': 'Route calculated', - 'planner.routeCalcFailed': 'Route could not be calculated', - 'planner.routeError': 'Error calculating route', - 'planner.icsExportFailed': 'ICS export failed', - 'planner.routeOptimized': 'Route optimized', - 'planner.reservationUpdated': 'Reservation updated', - 'planner.reservationAdded': 'Reservation added', - 'planner.confirmDeleteReservation': 'Delete reservation?', - 'planner.reservationDeleted': 'Reservation deleted', - 'planner.days': 'Days', - 'planner.allPlaces': 'All Places', - 'planner.totalPlaces': '{n} places total', - 'planner.noDaysPlanned': 'No days planned yet', - 'planner.editTrip': 'Edit trip \u2192', - 'planner.placeOne': '1 place', - 'planner.placeN': '{n} places', - 'planner.addNote': 'Add note', - 'planner.noEntries': 'No entries for this day', - 'planner.addPlace': 'Add place/activity', - 'planner.addPlaceShort': '+ Add place/activity', - 'planner.resPending': 'Reservation pending · ', - 'planner.resConfirmed': 'Reservation confirmed · ', - 'planner.notePlaceholder': 'Note\u2026', - 'planner.noteTimePlaceholder': 'Time (optional)', - 'planner.noteExamplePlaceholder': 'e.g. S3 at 14:30 from central station, ferry from pier 7, lunch break\u2026', - 'planner.totalCost': 'Total cost', - 'planner.searchPlaces': 'Search places\u2026', - 'planner.allCategories': 'All Categories', - 'planner.noPlacesFound': 'No places found', - 'planner.addFirstPlace': 'Add first place', - 'planner.noReservations': 'No reservations', - 'planner.addFirstReservation': 'Add first reservation', - 'planner.new': 'New', - 'planner.addToDay': '+ Day', - 'planner.calculating': 'Calculating\u2026', - 'planner.route': 'Route', - 'planner.optimize': 'Optimize', - 'planner.openGoogleMaps': 'Open in Google Maps', - 'planner.selectDayHint': 'Select a day from the left list to see the day plan', - 'planner.noPlacesForDay': 'No places for this day yet', - 'planner.addPlacesLink': 'Add places \u2192', - 'planner.minTotal': 'min. total', - 'planner.noReservation': 'No reservation', - 'planner.removeFromDay': 'Remove from day', - 'planner.addToThisDay': 'Add to day', - 'planner.overview': 'Overview', - 'planner.noDays': 'No days yet', - 'planner.editTripToAddDays': 'Edit trip to add days', - 'planner.dayCount': '{n} Days', - 'planner.clickToUnlock': 'Click to unlock', - 'planner.keepPosition': 'Keep position during route optimization', - 'planner.dayDetails': 'Day details', - 'planner.dayN': 'Day {n}', - - // Dashboard Stats - 'stats.countries': 'Countries', - 'stats.cities': 'Cities', - 'stats.trips': 'Trips', - 'stats.places': 'Places', - 'stats.worldProgress': 'World Progress', - 'stats.visited': 'visited', - 'stats.remaining': 'remaining', - 'stats.visitedCountries': 'Visited Countries', - - // Day Detail Panel - 'day.precipProb': 'Rain probability', - 'day.precipitation': 'Precipitation', - 'day.wind': 'Wind', - 'day.sunrise': 'Sunrise', - 'day.sunset': 'Sunset', - 'day.hourlyForecast': 'Hourly Forecast', - 'day.climateHint': 'Historical averages — real forecast available within 16 days of this date.', - 'day.noWeather': 'No weather data available. Add a place with coordinates.', - 'day.overview': 'Daily Overview', - 'day.accommodation': 'Accommodation', - 'day.addAccommodation': 'Add accommodation', - 'day.hotelDayRange': 'Apply to days', - 'day.noPlacesForHotel': 'Add places to your trip first', - 'day.allDays': 'All', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Until', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Confirmation', - 'day.editAccommodation': 'Edit accommodation', - 'day.reservations': 'Reservations', - - // Photos / Immich - 'memories.title': 'Photos', - 'memories.notConnected': '{provider_name} not connected', - 'memories.notConnectedHint': 'Connect your {provider_name} instance in Settings to be able add photos to this trip.', - 'memories.notConnectedMultipleHint': 'Connect any of these photo providers: {provider_names} in Settings to be able add photos to this trip.', - 'memories.noDates': 'Add dates to your trip to load photos.', - 'memories.noPhotos': 'No photos found', - 'memories.noPhotosHint': 'No photos found in {provider_name} for this trip\'s date range.', - 'memories.photosFound': 'photos', - 'memories.fromOthers': 'from others', - 'memories.sharePhotos': 'Share photos', - 'memories.sharing': 'Sharing', - 'memories.reviewTitle': 'Review your photos', - 'memories.reviewHint': 'Click photos to exclude them from sharing.', - 'memories.shareCount': 'Share {count} photos', - //------------------------- - //todo section - 'memories.providerUrl': 'Server URL', - 'memories.providerApiKey': 'API Key', - 'memories.providerUsername': 'Username', - 'memories.providerPassword': 'Password', - 'memories.providerOTP': 'MFA code (if enabled)', - 'memories.skipSSLVerification': 'Skip SSL certificate verification', - 'memories.immichAutoUpload': 'Mirror journey photos to Immich on upload', - 'memories.providerUrlHintSynology': 'Include the Photos app path in the URL, e.g. https://nas:5001/photo', - 'memories.testConnection': 'Test connection', - 'memories.testShort': 'Test', - 'memories.testFirst': 'Test connection first', - 'memories.connected': 'Connected', - 'memories.disconnected': 'Not connected', - 'memories.connectionSuccess': 'Connected to {provider_name}', - 'memories.connectionError': 'Could not connect to {provider_name}', - 'memories.saved': '{provider_name} settings saved', - 'memories.providerDisconnectedBanner': 'Your {provider_name} connection is lost. Reconnect in Settings to view photos.', - 'memories.saveError': 'Could not save {provider_name} settings', - //------------------------ - 'memories.addPhotos': 'Add photos', - 'memories.linkAlbum': 'Link Album', - 'memories.selectAlbum': 'Select {provider_name} Album', - 'memories.selectAlbumMultiple': 'Select Album', - 'memories.noAlbums': 'No albums found', - 'memories.syncAlbum': 'Sync album', - 'memories.unlinkAlbum': 'Unlink album', - 'memories.photos': 'photos', - 'memories.selectPhotos': 'Select photos from {provider_name}', - 'memories.selectPhotosMultiple': 'Select Photos', - 'memories.selectHint': 'Tap photos to select them.', - 'memories.selected': 'selected', - 'memories.addSelected': 'Add {count} photos', - 'memories.alreadyAdded': 'Added', - 'memories.private': 'Private', - 'memories.stopSharing': 'Stop sharing', - 'memories.oldest': 'Oldest first', - 'memories.newest': 'Newest first', - 'memories.allLocations': 'All locations', - 'memories.tripDates': 'Trip dates', - 'memories.allPhotos': 'All photos', - 'memories.confirmShareTitle': 'Share with trip members?', - 'memories.confirmShareHint': '{count} photos will be visible to all members of this trip. You can make individual photos private later.', - 'memories.confirmShareButton': 'Share photos', - 'memories.error.loadAlbums': 'Failed to load albums', - 'memories.error.linkAlbum': 'Failed to link album', - 'memories.error.unlinkAlbum': 'Failed to unlink album', - 'memories.error.syncAlbum': 'Failed to sync album', - 'memories.error.loadPhotos': 'Failed to load photos', - 'memories.error.addPhotos': 'Failed to add photos', - 'memories.error.removePhoto': 'Failed to remove photo', - 'memories.error.toggleSharing': 'Failed to update sharing', - 'memories.saveRouteNotConfigured': 'Save route is not configured for this provider', - 'memories.testRouteNotConfigured': 'Test route is not configured for this provider', - 'memories.fillRequiredFields': 'Please fill all required fields', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Notes', - 'collab.tabs.polls': 'Polls', - 'collab.whatsNext.title': "What's Next", - 'collab.whatsNext.today': 'Today', - 'collab.whatsNext.tomorrow': 'Tomorrow', - 'collab.whatsNext.empty': 'No upcoming activities', - 'collab.whatsNext.until': 'to', - 'collab.whatsNext.emptyHint': 'Activities with times will appear here', - 'collab.chat.send': 'Send', - 'collab.chat.placeholder': 'Type a message...', - 'collab.chat.empty': 'Start the conversation', - 'collab.chat.emptyHint': 'Messages are shared with all trip members', - 'collab.chat.emptyDesc': 'Share ideas, plans, and updates with your travel group', - 'collab.chat.today': 'Today', - 'collab.chat.yesterday': 'Yesterday', - 'collab.chat.deletedMessage': 'deleted a message', - 'collab.chat.reply': 'Reply', - 'collab.chat.loadMore': 'Load older messages', - 'collab.chat.justNow': 'just now', - 'collab.chat.minutesAgo': '{n}m ago', - 'collab.chat.hoursAgo': '{n}h ago', - 'collab.notes.title': 'Notes', - 'collab.notes.new': 'New Note', - 'collab.notes.empty': 'No notes yet', - 'collab.notes.emptyHint': 'Start capturing ideas and plans', - 'collab.notes.all': 'All', - 'collab.notes.titlePlaceholder': 'Note title', - 'collab.notes.contentPlaceholder': 'Write something...', - 'collab.notes.categoryPlaceholder': 'Category', - 'collab.notes.newCategory': 'New category...', - 'collab.notes.category': 'Category', - 'collab.notes.noCategory': 'No category', - 'collab.notes.color': 'Color', - 'collab.notes.save': 'Save', - 'collab.notes.cancel': 'Cancel', - 'collab.notes.edit': 'Edit', - 'collab.notes.delete': 'Delete', - 'collab.notes.pin': 'Pin', - 'collab.notes.unpin': 'Unpin', - 'collab.notes.daysAgo': '{n}d ago', - 'collab.notes.categorySettings': 'Manage Categories', - 'collab.notes.create': 'Create', - 'collab.notes.website': 'Website', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Attach files', - 'collab.notes.noCategoriesYet': 'No categories yet', - 'collab.notes.emptyDesc': 'Create a note to get started', - 'collab.polls.title': 'Polls', - 'collab.polls.new': 'New Poll', - 'collab.polls.empty': 'No polls yet', - 'collab.polls.emptyHint': 'Ask the group and vote together', - 'collab.polls.question': 'Question', - 'collab.polls.questionPlaceholder': 'What should we do?', - 'collab.polls.addOption': '+ Add option', - 'collab.polls.optionPlaceholder': 'Option {n}', - 'collab.polls.create': 'Create Poll', - 'collab.polls.close': 'Close', - 'collab.polls.closed': 'Closed', - 'collab.polls.votes': '{n} votes', - 'collab.polls.vote': '{n} vote', - 'collab.polls.multipleChoice': 'Multiple choice', - 'collab.polls.multiChoice': 'Multiple choice', - 'collab.polls.deadline': 'Deadline', - 'collab.polls.option': 'Option', - 'collab.polls.options': 'Options', - 'collab.polls.delete': 'Delete', - 'collab.polls.closedSection': 'Closed', - - // Permissions - 'admin.tabs.permissions': 'Permissions', - 'perm.title': 'Permission Settings', - 'perm.subtitle': 'Control who can perform actions across the application', - 'perm.saved': 'Permission settings saved', - 'perm.resetDefaults': 'Reset to defaults', - 'perm.customized': 'customized', - 'perm.level.admin': 'Admin only', - 'perm.level.tripOwner': 'Trip owner', - 'perm.level.tripMember': 'Trip members', - 'perm.level.everybody': 'Everyone', - 'perm.cat.trip': 'Trip Management', - 'perm.cat.members': 'Member Management', - 'perm.cat.files': 'Files', - 'perm.cat.content': 'Content & Schedule', - 'perm.cat.extras': 'Budget, Packing & Collaboration', - 'perm.action.trip_create': 'Create trips', - 'perm.action.trip_edit': 'Edit trip details', - 'perm.action.trip_delete': 'Delete trips', - 'perm.action.trip_archive': 'Archive / unarchive trips', - 'perm.action.trip_cover_upload': 'Upload cover image', - 'perm.action.member_manage': 'Add / remove members', - 'perm.action.file_upload': 'Upload files', - 'perm.action.file_edit': 'Edit file metadata', - 'perm.action.file_delete': 'Delete files', - 'perm.action.place_edit': 'Add / edit / delete places', - 'perm.action.day_edit': 'Edit days, notes & assignments', - 'perm.action.reservation_edit': 'Manage reservations', - 'perm.action.budget_edit': 'Manage budget', - 'perm.action.packing_edit': 'Manage packing lists', - 'perm.action.collab_edit': 'Collaboration (notes, polls, chat)', - 'perm.action.share_manage': 'Manage share links', - 'perm.actionHint.trip_create': 'Who can create new trips', - 'perm.actionHint.trip_edit': 'Who can change trip name, dates, description and currency', - 'perm.actionHint.trip_delete': 'Who can permanently delete a trip', - 'perm.actionHint.trip_archive': 'Who can archive or unarchive a trip', - 'perm.actionHint.trip_cover_upload': 'Who can upload or change the cover image', - 'perm.actionHint.member_manage': 'Who can invite or remove trip members', - 'perm.actionHint.file_upload': 'Who can upload files to a trip', - 'perm.actionHint.file_edit': 'Who can edit file descriptions and links', - 'perm.actionHint.file_delete': 'Who can move files to trash or permanently delete them', - 'perm.actionHint.place_edit': 'Who can add, edit or delete places', - 'perm.actionHint.day_edit': 'Who can edit days, day notes and place assignments', - 'perm.actionHint.reservation_edit': 'Who can create, edit or delete reservations', - 'perm.actionHint.budget_edit': 'Who can create, edit or delete budget items', - 'perm.actionHint.packing_edit': 'Who can manage packing items and bags', - 'perm.actionHint.collab_edit': 'Who can create notes, polls and send messages', - 'perm.actionHint.share_manage': 'Who can create or delete public share links', - - // Undo - 'undo.button': 'Undo', - 'undo.tooltip': 'Undo: {action}', - 'undo.assignPlace': 'Place assigned to day', - 'undo.removeAssignment': 'Place removed from day', - 'undo.reorder': 'Places reordered', - 'undo.optimize': 'Route optimized', - 'undo.deletePlace': 'Place deleted', - 'undo.deletePlaces': 'Places deleted', - 'undo.moveDay': 'Place moved to another day', - 'undo.lock': 'Place lock toggled', - 'undo.importGpx': 'GPX import', - 'undo.importKeyholeMarkup': 'KMZ/KML import', - 'undo.importGoogleList': 'Google Maps import', - 'undo.importNaverList': 'Naver Maps import', - 'undo.addPlace': 'Place added', - 'undo.done': 'Undone: {action}', - - // Notifications - 'notifications.title': 'Notifications', - 'notifications.markAllRead': 'Mark all read', - 'notifications.deleteAll': 'Delete all', - 'notifications.showAll': 'Show all notifications', - 'notifications.empty': 'No notifications', - 'notifications.emptyDescription': "You're all caught up!", - 'notifications.all': 'All', - 'notifications.unreadOnly': 'Unread', - 'notifications.markRead': 'Mark as read', - 'notifications.markUnread': 'Mark as unread', - 'notifications.delete': 'Delete', - 'notifications.system': 'System', - 'notifications.synologySessionCleared.title': 'Synology Photos disconnected', - 'notifications.synologySessionCleared.text': 'Your server or account changed — go to Settings to test your connection again.', - - // Notification test keys (dev only) - 'notifications.versionAvailable.title': 'Update Available', - 'notifications.versionAvailable.text': 'TREK {version} is now available.', - 'notifications.versionAvailable.button': 'View Details', - 'notifications.test.title': 'Test notification from {actor}', - 'notifications.test.text': 'This is a simple test notification.', - 'notifications.test.booleanTitle': '{actor} asks for your approval', - 'notifications.test.booleanText': 'This is a test boolean notification. Choose an action below.', - 'notifications.test.accept': 'Approve', - 'notifications.test.decline': 'Decline', - 'notifications.test.navigateTitle': 'Check something out', - 'notifications.test.navigateText': 'This is a test navigate notification.', - 'notifications.test.goThere': 'Go there', - 'notifications.test.adminTitle': 'Admin broadcast', - 'notifications.test.adminText': '{actor} sent a test notification to all admins.', - 'notifications.test.tripTitle': '{actor} posted in your trip', - 'notifications.test.tripText': 'Test notification for trip "{trip}".', - - // Todo - 'todo.subtab.packing': 'Packing List', - 'todo.subtab.todo': 'To-Do', - 'todo.completed': 'completed', - 'todo.filter.all': 'All', - 'todo.filter.open': 'Open', - 'todo.filter.done': 'Done', - 'todo.uncategorized': 'Uncategorized', - 'todo.namePlaceholder': 'Task name', - 'todo.descriptionPlaceholder': 'Description (optional)', - 'todo.unassigned': 'Unassigned', - 'todo.noCategory': 'No category', - 'todo.hasDescription': 'Has description', - 'todo.addItem': 'Add new task', - 'todo.sidebar.sortBy': 'Sort by', - 'todo.priority': 'Priority', - 'todo.newCategoryLabel': 'new', - 'budget.categoriesLabel': 'categories', - 'todo.newCategory': 'Category name', - 'todo.addCategory': 'Add category', - 'todo.newItem': 'New task', - 'todo.empty': 'No tasks yet. Add a task to get started!', - 'todo.filter.my': 'My Tasks', - 'todo.filter.overdue': 'Overdue', - 'todo.sidebar.tasks': 'Tasks', - 'todo.sidebar.categories': 'Categories', - 'todo.detail.title': 'Task', - 'todo.detail.description': 'Description', - 'todo.detail.category': 'Category', - 'todo.detail.dueDate': 'Due date', - 'todo.detail.assignedTo': 'Assigned to', - 'todo.detail.delete': 'Delete', - 'todo.detail.save': 'Save changes', - 'todo.sortByPrio': 'Priority', - 'todo.detail.priority': 'Priority', - 'todo.detail.noPriority': 'None', - 'todo.detail.create': 'Create task', - - // Notifications — dev test events - 'notif.test.title': '[Test] Notification', - 'notif.test.simple.text': 'This is a simple test notification.', - 'notif.test.boolean.text': 'Do you accept this test notification?', - 'notif.test.navigate.text': 'Click below to navigate to the dashboard.', - - // Notifications - 'notif.trip_invite.title': 'Trip Invitation', - 'notif.trip_invite.text': '{actor} invited you to {trip}', - 'notif.booking_change.title': 'Booking Updated', - 'notif.booking_change.text': '{actor} updated a booking in {trip}', - 'notif.trip_reminder.title': 'Trip Reminder', - 'notif.trip_reminder.text': 'Your trip {trip} is coming up soon!', - 'notif.todo_due.title': 'To-do due', - 'notif.todo_due.text': '{todo} in {trip} is due on {due}', - 'notif.vacay_invite.title': 'Vacay Fusion Invite', - 'notif.vacay_invite.text': '{actor} invited you to fuse vacation plans', - 'notif.photos_shared.title': 'Photos Shared', - 'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}', - 'notif.collab_message.title': 'New Message', - 'notif.collab_message.text': '{actor} sent a message in {trip}', - 'notif.packing_tagged.title': 'Packing Assignment', - 'notif.packing_tagged.text': '{actor} assigned you to {category} in {trip}', - 'notif.version_available.title': 'New Version Available', - 'notif.version_available.text': 'TREK {version} is now available', - 'notif.action.view_trip': 'View Trip', - 'notif.action.view_collab': 'View Messages', - 'notif.action.view_packing': 'View Packing', - 'notif.action.view_photos': 'View Photos', - 'notif.action.view_vacay': 'View Vacay', - 'notif.action.view_admin': 'Go to Admin', - 'notif.action.view': 'View', - 'notif.action.accept': 'Accept', - 'notif.action.decline': 'Decline', - 'notif.generic.title': 'Notification', - 'notif.generic.text': 'You have a new notification', - 'notif.dev.unknown_event.title': '[DEV] Unknown Event', - 'notif.dev.unknown_event.text': 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG', - - // Journey addon - 'journey.search.placeholder': 'Search journeys…', - 'journey.search.noResults': 'No journeys match "{query}"', - 'journey.title': 'Journey', - 'journey.subtitle': 'Track your travels as they happen', - 'journey.new': 'New Journey', - 'journey.create': 'Create', - 'journey.titlePlaceholder': 'Where are you going?', - 'journey.empty': 'No journeys yet', - 'journey.emptyHint': 'Start documenting your next trip', - 'journey.deleted': 'Journey deleted', - 'journey.createError': 'Could not create journey', - 'journey.deleteError': 'Could not delete journey', - 'journey.deleteConfirmTitle': 'Delete', - 'journey.deleteConfirmMessage': 'Delete "{title}"? This cannot be undone.', - 'journey.deleteConfirmGeneric': 'Are you sure you want to delete this?', - 'journey.notFound': 'Journey not found', - 'journey.photos': 'Photos', - 'journey.timelineEmpty': 'No stops yet', - 'journey.timelineEmptyHint': 'Add a check-in or write a journal entry to get started', - 'journey.status.draft': 'Draft', - 'journey.status.active': 'Active', - 'journey.status.completed': 'Completed', - 'journey.status.upcoming': 'Upcoming', - 'journey.status.archived': 'Archived', - 'journey.checkin.add': 'Check in', - 'journey.checkin.namePlaceholder': 'Location name', - 'journey.checkin.notesPlaceholder': 'Notes (optional)', - 'journey.checkin.save': 'Save', - 'journey.checkin.error': 'Could not save check-in', - 'journey.entry.add': 'Journal', - 'journey.entry.edit': 'Edit entry', - 'journey.entry.titlePlaceholder': 'Title (optional)', - 'journey.entry.bodyPlaceholder': 'What happened today?', - 'journey.entry.save': 'Save', - 'journey.entry.error': 'Could not save entry', - 'journey.photo.add': 'Photo', - 'journey.photo.uploadError': 'Upload failed', - 'journey.share.share': 'Share', - 'journey.share.public': 'Public', - 'journey.share.linkCopied': 'Public link copied', - 'journey.share.disabled': 'Public sharing disabled', - 'journey.editor.titlePlaceholder': 'Give this moment a name...', - 'journey.editor.bodyPlaceholder': 'Tell the story of this day...', - 'journey.editor.placePlaceholder': 'Location (optional)', - 'journey.editor.tagsPlaceholder': 'Tags: hidden gem, best meal, must revisit...', - 'journey.visibility.private': 'Private', - 'journey.visibility.shared': 'Shared', - 'journey.visibility.public': 'Public', - 'journey.emptyState.title': 'Your story starts here', - 'journey.emptyState.subtitle': 'Check in at a place or write your first journal entry', - - // Journey Frontpage - 'journey.frontpage.subtitle': 'Turn your trips into stories you\'ll never forget', - 'journey.frontpage.createJourney': 'Create Journey', - 'journey.frontpage.activeJourney': 'Active Journey', - 'journey.frontpage.allJourneys': 'All Journeys', - 'journey.frontpage.journeys': 'journeys', - 'journey.frontpage.createNew': 'Create a new Journey', - 'journey.frontpage.createNewSub': 'Pick trips, write stories, share your adventures', - 'journey.frontpage.live': 'Live', - 'journey.frontpage.synced': 'Synced', - 'journey.frontpage.continueWriting': 'Continue writing', - 'journey.frontpage.updated': 'Updated {time}', - 'journey.frontpage.suggestionLabel': 'Trip just ended', - 'journey.frontpage.suggestionText': 'Turn {title} into a Journey', - 'journey.frontpage.dismiss': 'Dismiss', - 'journey.frontpage.journeyName': 'Journey Name', - 'journey.frontpage.namePlaceholder': 'e.g. Southeast Asia 2026', - 'journey.frontpage.selectTrips': 'Select Trips', - 'journey.frontpage.tripsSelected': 'trips selected', - 'journey.frontpage.trips': 'trips', - 'journey.frontpage.placesImported': 'places will be imported', - 'journey.frontpage.places': 'places', - - // Journey Detail - 'journey.detail.backToJourney': 'Back to Journey', - 'journey.detail.syncedWithTrips': 'Synced with Trips', - 'journey.detail.addEntry': 'Add Entry', - 'journey.detail.newEntry': 'New Entry', - 'journey.detail.editEntry': 'Edit Entry', - 'journey.detail.noEntries': 'No entries yet', - 'journey.detail.noEntriesHint': 'Add a trip to get started with skeleton entries', - 'journey.detail.noPhotos': 'No photos yet', - 'journey.detail.noPhotosHint': 'Upload photos to entries or browse your Immich/Synology library', - 'journey.detail.journeyTab': 'Journey', - 'journey.detail.journeyStats': 'Journey Stats', - 'journey.detail.syncedTrips': 'Synced Trips', - 'journey.detail.noTripsLinked': 'No trips linked yet', - 'journey.detail.contributors': 'Contributors', - 'journey.detail.readMore': 'Read more', - 'journey.detail.prosCons': 'Pros & Cons', - 'journey.detail.photos': 'photos', - 'journey.detail.day': 'Day {number}', - 'journey.detail.places': 'places', - - // Journey Detail — Stats - 'journey.stats.days': 'Days', - 'journey.stats.cities': 'Cities', - 'journey.stats.entries': 'Entries', - 'journey.stats.photos': 'Photos', - 'journey.stats.places': 'Places', - 'journey.skeletons.show': 'Show suggestions', - 'journey.skeletons.hide': 'Hide suggestions', - - // Journey Detail — Verdict - 'journey.verdict.lovedIt': 'Loved it', - 'journey.verdict.couldBeBetter': 'Could be better', - - // Journey Detail — Synced badge - 'journey.synced.places': 'places', - 'journey.synced.synced': 'synced', - - // Journey Entry Editor - 'journey.editor.discardChangesConfirm': 'You have unsaved changes. Discard them?', - 'journey.editor.uploadFailed': 'Photo upload failed', - 'journey.editor.uploadPhotos': 'Upload photos', - 'journey.editor.uploading': 'Uploading...', - 'journey.editor.uploadingProgress': 'Uploading {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} of {total} photos failed — save again to retry', - 'journey.editor.fromGallery': 'From Gallery', - 'journey.editor.allPhotosAdded': 'All photos already added', - 'journey.editor.writeStory': 'Write your story...', - 'journey.editor.prosCons': 'Pros & Cons', - 'journey.editor.pros': 'Pros', - 'journey.editor.cons': 'Cons', - 'journey.editor.proPlaceholder': 'Something great...', - 'journey.editor.conPlaceholder': 'Not so great...', - 'journey.editor.addAnother': 'Add another', - 'journey.editor.date': 'Date', - 'journey.editor.location': 'Location', - 'journey.editor.searchLocation': 'Search location...', - 'journey.editor.mood': 'Mood', - 'journey.editor.weather': 'Weather', - 'journey.editor.photoFirst': '1st', - 'journey.editor.makeFirst': 'Make 1st', - 'journey.editor.searching': 'Searching...', - - // Journey Entry — Moods - 'journey.mood.amazing': 'Amazing', - 'journey.mood.good': 'Good', - 'journey.mood.neutral': 'Neutral', - 'journey.mood.rough': 'Rough', - - // Journey Entry — Weather - 'journey.weather.sunny': 'Sunny', - 'journey.weather.partly': 'Partly cloudy', - 'journey.weather.cloudy': 'Cloudy', - 'journey.weather.rainy': 'Rainy', - 'journey.weather.stormy': 'Stormy', - 'journey.weather.cold': 'Snowy', - - // Journey — Trip Linking - 'journey.trips.linkTrip': 'Link Trip', - 'journey.trips.searchTrip': 'Search Trip', - 'journey.trips.searchPlaceholder': 'Trip name or destination...', - 'journey.trips.noTripsAvailable': 'No trips available', - 'journey.trips.link': 'Link', - 'journey.trips.tripLinked': 'Trip linked', - 'journey.trips.linkFailed': 'Failed to link trip', - 'journey.trips.addTrip': 'Add Trip', - 'journey.trips.unlinkTrip': 'Unlink Trip', - 'journey.trips.unlinkMessage': 'Unlink "{title}"? All synced entries and photos from this trip will be permanently deleted. This cannot be undone.', - 'journey.trips.unlink': 'Unlink', - 'journey.trips.tripUnlinked': 'Trip unlinked', - 'journey.trips.unlinkFailed': 'Failed to unlink trip', - 'journey.trips.noTripsLinkedSettings': 'No trips linked', - - // Journey — Contributors - 'journey.contributors.invite': 'Invite Contributor', - 'journey.contributors.searchUser': 'Search User', - 'journey.contributors.searchPlaceholder': 'Username or email...', - 'journey.contributors.noUsers': 'No users found', - 'journey.contributors.role': 'Role', - 'journey.contributors.added': 'Contributor added', - 'journey.contributors.addFailed': 'Failed to add contributor', - 'journey.contributors.remove': 'Remove contributor', - 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', - 'journey.contributors.removed': 'Contributor removed', - 'journey.contributors.removeFailed': 'Failed to remove contributor', - - // Journey — Share - 'journey.share.publicShare': 'Public Share', - 'journey.share.createLink': 'Create share link', - 'journey.share.linkCreated': 'Share link created', - 'journey.share.createFailed': 'Failed to create link', - 'journey.share.copy': 'Copy', - 'journey.share.copied': 'Copied!', - 'journey.share.timeline': 'Timeline', - 'journey.share.gallery': 'Gallery', - 'journey.share.map': 'Map', - 'journey.share.removeLink': 'Remove share link', - 'journey.share.linkDeleted': 'Share link deleted', - 'journey.share.deleteFailed': 'Failed to delete', - 'journey.share.updateFailed': 'Failed to update', - - // Journey — Invite - 'journey.invite.role': 'Role', - 'journey.invite.viewer': 'Viewer', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Invite', - 'journey.invite.inviting': 'Inviting...', - - // Journey — Settings Dialog - 'journey.settings.title': 'Journey Settings', - 'journey.settings.coverImage': 'Cover Image', - 'journey.settings.changeCover': 'Change cover', - 'journey.settings.addCover': 'Add cover image', - 'journey.settings.name': 'Name', - 'journey.settings.subtitle': 'Subtitle', - 'journey.settings.subtitlePlaceholder': 'e.g. Thailand, Vietnam & Cambodia', - 'journey.settings.endJourney': 'Archive Journey', - 'journey.settings.reopenJourney': 'Restore Journey', - 'journey.settings.archived': 'Journey archived', - 'journey.settings.reopened': 'Journey reopened', - 'journey.settings.endDescription': 'Hides the Live badge. You can reopen anytime.', - 'journey.settings.delete': 'Delete', - 'journey.settings.deleteJourney': 'Delete Journey', - 'journey.settings.deleteMessage': 'Delete "{title}"? All entries and photos will be lost.', - 'journey.settings.saved': 'Settings saved', - 'journey.settings.saveFailed': 'Failed to save', - 'journey.settings.coverUpdated': 'Cover updated', - 'journey.settings.coverFailed': 'Upload failed', - 'journey.settings.failedToDelete': 'Failed to delete', - 'journey.entries.deleteTitle': 'Delete Entry', - 'journey.photosUploaded': '{count} photos uploaded', - 'journey.photosUploadFailed': 'Some photos failed to upload', - 'journey.photosAdded': '{count} photos added', - - // Journey — Public Page - 'journey.public.notFound': 'Not Found', - 'journey.public.notFoundMessage': 'This journey doesn\'t exist or the link has expired.', - 'journey.public.readOnly': 'Read-only · Public Journey', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Shared via', - 'journey.public.madeWith': 'Made with', - - // Journey — PDF Export - 'journey.pdf.journeyBook': 'Journey Book', - 'journey.pdf.madeWith': 'Made with TREK', - 'journey.pdf.day': 'Day', - 'journey.pdf.theEnd': 'The End', - 'journey.pdf.saveAsPdf': 'Save as PDF', - 'journey.pdf.pages': 'pages', - 'journey.picker.tripPeriod': 'Trip Period', - 'journey.picker.dateRange': 'Date Range', - 'journey.picker.allPhotos': 'All Photos', - 'journey.picker.albums': 'Albums', - 'journey.picker.selected': 'selected', - 'journey.picker.addTo': 'Add to', - 'journey.picker.newGallery': 'New Gallery', - 'journey.picker.selectAll': 'Select all', - 'journey.picker.deselectAll': 'Deselect all', - 'journey.picker.noAlbums': 'No albums found', - 'journey.picker.selectDate': 'Select date', - 'journey.picker.search': 'Search', - - // Dashboard Mobile - 'dashboard.greeting.morning': 'Good morning,', - 'dashboard.greeting.afternoon': 'Good afternoon,', - 'dashboard.greeting.evening': 'Good evening,', - 'dashboard.mobile.liveNow': 'Live Now', - 'dashboard.mobile.tripProgress': 'Trip progress', - 'dashboard.mobile.daysLeft': '{count} days left', - 'dashboard.mobile.places': 'Places', - 'dashboard.mobile.buddies': 'Buddies', - 'dashboard.mobile.newTrip': 'New Trip', - 'dashboard.mobile.currency': 'Currency', - 'dashboard.mobile.timezone': 'Timezone', - 'dashboard.mobile.upcomingTrips': 'Upcoming Trips', - 'dashboard.mobile.yourTrips': 'Your Trips', - 'dashboard.mobile.trips': 'trips', - 'dashboard.mobile.starts': 'Starts', - 'dashboard.mobile.duration': 'Duration', - 'dashboard.mobile.day': 'day', - 'dashboard.mobile.days': 'days', - 'dashboard.mobile.ongoing': 'Ongoing', - 'dashboard.mobile.startsToday': 'Starts today', - 'dashboard.mobile.tomorrow': 'Tomorrow', - 'dashboard.mobile.inDays': 'In {count} days', - 'dashboard.mobile.inMonths': 'In {count} months', - 'dashboard.mobile.completed': 'Completed', - 'dashboard.mobile.currencyConverter': 'Currency Converter', - - // BottomNav & Profile - 'nav.profile': 'Profile', - 'nav.bottomSettings': 'Settings', - 'nav.bottomAdmin': 'Admin Settings', - 'nav.bottomLogout': 'Logout', - 'nav.bottomAdminBadge': 'Admin', - - // DayPlan Mobile - 'dayplan.mobile.addPlace': 'Add Place', - 'dayplan.mobile.searchPlaces': 'Search places...', - 'dayplan.mobile.allAssigned': 'All places assigned', - 'dayplan.mobile.noMatch': 'No match', - 'dayplan.mobile.createNew': 'Create new place', - - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': 'Trip tracking & travel journal with check-ins, photos, and daily stories', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Trips', - 'oauth.scope.group.places': 'Places', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Packing', - 'oauth.scope.group.todos': 'To-dos', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Reservations', - 'oauth.scope.group.collab': 'Collaboration', - 'oauth.scope.group.notifications': 'Notifications', - 'oauth.scope.group.vacay': 'Vacation', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Weather', - 'oauth.scope.group.journey': 'Journey', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'View trips & itineraries', - 'oauth.scope.trips:read.description': 'Read trips, days, day notes, and members', - 'oauth.scope.trips:write.label': 'Edit trips & itineraries', - 'oauth.scope.trips:write.description': 'Create and update trips, days, notes, and manage members', - 'oauth.scope.trips:delete.label': 'Delete trips', - 'oauth.scope.trips:delete.description': 'Permanently delete entire trips — this action is irreversible', - 'oauth.scope.trips:share.label': 'Manage share links', - 'oauth.scope.trips:share.description': 'Create, update, and revoke public share links for trips', - 'oauth.scope.places:read.label': 'View places & map data', - 'oauth.scope.places:read.description': 'Read places, day assignments, tags, and categories', - 'oauth.scope.places:write.label': 'Manage places', - 'oauth.scope.places:write.description': 'Create, update, and delete places, assignments, and tags', - 'oauth.scope.atlas:read.label': 'View Atlas', - 'oauth.scope.atlas:read.description': 'Read visited countries, regions, and bucket list', - 'oauth.scope.atlas:write.label': 'Manage Atlas', - 'oauth.scope.atlas:write.description': 'Mark countries and regions visited, manage bucket list', - 'oauth.scope.packing:read.label': 'View packing lists', - 'oauth.scope.packing:read.description': 'Read packing items, bags, and category assignees', - 'oauth.scope.packing:write.label': 'Manage packing lists', - 'oauth.scope.packing:write.description': 'Add, update, delete, toggle, and reorder packing items and bags', - 'oauth.scope.todos:read.label': 'View to-do lists', - 'oauth.scope.todos:read.description': 'Read trip to-do items and category assignees', - 'oauth.scope.todos:write.label': 'Manage to-do lists', - 'oauth.scope.todos:write.description': 'Create, update, toggle, delete, and reorder to-do items', - 'oauth.scope.budget:read.label': 'View budget', - 'oauth.scope.budget:read.description': 'Read budget items and expense breakdown', - 'oauth.scope.budget:write.label': 'Manage budget', - 'oauth.scope.budget:write.description': 'Create, update, and delete budget items', - 'oauth.scope.reservations:read.label': 'View reservations', - 'oauth.scope.reservations:read.description': 'Read reservations and accommodation details', - 'oauth.scope.reservations:write.label': 'Manage reservations', - 'oauth.scope.reservations:write.description': 'Create, update, delete, and reorder reservations', - 'oauth.scope.collab:read.label': 'View collaboration', - 'oauth.scope.collab:read.description': 'Read collab notes, polls, and messages', - 'oauth.scope.collab:write.label': 'Manage collaboration', - 'oauth.scope.collab:write.description': 'Create, update, and delete collab notes, polls, and messages', - 'oauth.scope.notifications:read.label': 'View notifications', - 'oauth.scope.notifications:read.description': 'Read in-app notifications and unread counts', - 'oauth.scope.notifications:write.label': 'Manage notifications', - 'oauth.scope.notifications:write.description': 'Mark notifications as read and respond to them', - 'oauth.scope.vacay:read.label': 'View vacation plans', - 'oauth.scope.vacay:read.description': 'Read vacation planning data, entries, and stats', - 'oauth.scope.vacay:write.label': 'Manage vacation plans', - 'oauth.scope.vacay:write.description': 'Create and manage vacation entries, holidays, and team plans', - 'oauth.scope.geo:read.label': 'Maps & geocoding', - 'oauth.scope.geo:read.description': 'Search locations, resolve map URLs, and reverse geocode coordinates', - 'oauth.scope.weather:read.label': 'Weather forecasts', - 'oauth.scope.weather:read.description': 'Fetch weather forecasts for trip locations and dates', - 'oauth.scope.journey:read.label': 'View journeys', - 'oauth.scope.journey:read.description': 'Read journeys, entries, and contributor list', - 'oauth.scope.journey:write.label': 'Manage journeys', - 'oauth.scope.journey:write.description': 'Create, update, and delete journeys and their entries', - 'oauth.scope.journey:share.label': 'Manage journey links', - 'oauth.scope.journey:share.description': 'Create, update, and revoke public share links for journeys', - - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Photos have moved in 3.0', - 'system_notice.v3_photos.body': '**Photos** in the Trip Planner have been removed. Your photos are safe — TREK never modified your Immich or Synology library.\n\nPhotos now live in the **Journey** addon. Journey is optional — if it is not yet available, ask your admin to enable it under Admin → Addons.', - 'system_notice.v3_journey.title': 'Meet Journey — travel journal', - 'system_notice.v3_journey.body': 'Document your trips as rich travel stories with timelines, photo galleries, and interactive maps.', - 'system_notice.v3_journey.cta_label': 'Open Journey', - 'system_notice.v3_journey.highlight_timeline': 'Day-by-day timeline & gallery', - 'system_notice.v3_journey.highlight_photos': 'Import from Immich or Synology', - 'system_notice.v3_journey.highlight_share': 'Share publicly — no login needed', - 'system_notice.v3_journey.highlight_export': 'Export as a PDF photo book', - 'system_notice.v3_features.title': 'More highlights in 3.0', - 'system_notice.v3_features.body': 'A few more things worth knowing about this release.', - 'system_notice.v3_features.highlight_dashboard': 'Mobile-first dashboard redesign', - 'system_notice.v3_features.highlight_offline': 'Full offline mode as a PWA', - 'system_notice.v3_features.highlight_search': 'Real-time place search autocomplete', - 'system_notice.v3_features.highlight_import': 'Import places from KMZ/KML files', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 upgrade', - 'system_notice.v3_mcp.body': 'The MCP integration has been fully overhauled. OAuth 2.1 is now the recommended auth method. Legacy static tokens (trek_\u2026) are deprecated and will be removed in a future release.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommended (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 fine-grained permission scopes', - 'system_notice.v3_mcp.highlight_deprecated': 'Static trek_ tokens deprecated', - 'system_notice.v3_mcp.highlight_tools': 'Expanded toolset & prompts', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'A personal note from me', - 'system_notice.v3_thankyou.body': 'Before you go — I want to take a moment.\n\nTREK started as a side project I built for my own trips. I never imagined it would grow into something that 4,000 of you now trust to plan your adventures. Every star, every issue, every feature request — I read them all, and they keep me going through late nights between a full-time job and university.\n\nI want you to know: TREK will always be open source, always self-hosted, always yours. No tracking, no subscriptions, no strings attached. Just a tool built by someone who loves traveling as much as you do.\n\nSpecial thanks to [jubnl](https://github.com/jubnl) — you have become an incredible collaborator. So much of what makes 3.0 great carries your fingerprints. Thank you for believing in this project when it was still rough around the edges.\n\nAnd to every single one of you who filed a bug, translated a string, shared TREK with a friend, or simply used it to plan a trip — **thank you**. You are the reason this exists.\n\nHere\'s to many more adventures together.\n\n— Maurice\n\n---\n\n[Join the community on Discord](https://discord.gg/7Q6M6jDwzf)\n\nIf TREK makes your travels better, a [small coffee](https://ko-fi.com/mauriceboe) always keeps the lights on.', - - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Action required: user account conflict', - 'system_notice.v3014_whitespace_collision.body': 'The 3.0.14 upgrade detected one or more username or email collisions caused by leading/trailing whitespace in stored accounts. Affected accounts were renamed automatically. Check the server logs for lines starting with **[migration] WHITESPACE COLLISION** to identify which accounts need review.', - - // System notices — onboarding - 'system_notice.welcome_v1.title': 'Welcome to TREK', - 'system_notice.welcome_v1.body': 'Your all-in-one travel planner. Build itineraries, share trips with friends, and stay organized — online or offline.', - 'system_notice.welcome_v1.cta_label': 'Plan a trip', - 'system_notice.welcome_v1.hero_alt': 'A scenic travel destination with TREK planning UI overlay', - 'system_notice.welcome_v1.highlight_plan': 'Day-by-day itineraries for any trip', - 'system_notice.welcome_v1.highlight_share': 'Collaborate with travel partners', - 'system_notice.welcome_v1.highlight_offline': 'Works offline on mobile', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Previous notice', - 'system_notice.pager.next': 'Next notice', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Go to notice {n}', - 'system_notice.pager.position': 'Notice {current} of {total}', - 'transport.addTransport': 'Add transport', - 'transport.modalTitle.create': 'Add transport', - 'transport.modalTitle.edit': 'Edit transport', - 'transport.title': 'Transports', - 'transport.addManual': 'Manual Transport', -} - -export default en diff --git a/client/src/i18n/translations/es.ts b/client/src/i18n/translations/es.ts deleted file mode 100644 index 70aec75f..00000000 --- a/client/src/i18n/translations/es.ts +++ /dev/null @@ -1,2373 +0,0 @@ -const es: Record = { - // Common - 'common.save': 'Guardar', - 'common.showMore': 'Ver más', - 'common.showLess': 'Ver menos', - 'common.cancel': 'Cancelar', - 'common.clear': 'Borrar', - 'common.delete': 'Eliminar', - 'common.edit': 'Editar', - 'common.add': 'Añadir', - 'common.loading': 'Cargando...', - 'common.import': 'Importar', - 'common.select': 'Seleccionar', - 'common.selectAll': 'Seleccionar todo', - 'common.deselectAll': 'Deseleccionar todo', - 'common.error': 'Error', - 'common.unknownError': 'Error desconocido', - 'common.tooManyAttempts': 'Demasiados intentos. Inténtelo de nuevo más tarde.', - 'common.back': 'Atrás', - 'common.all': 'Todo', - 'common.close': 'Cerrar', - 'common.open': 'Abrir', - 'common.upload': 'Subir', - 'common.search': 'Buscar', - 'common.confirm': 'Confirmar', - 'common.ok': 'Aceptar', - 'common.yes': 'Sí', - 'common.no': 'No', - 'common.or': 'o', - 'common.none': 'Ninguno', - 'common.date': 'Fecha', - 'common.rename': 'Renombrar', - 'common.discardChanges': 'Descartar cambios', - 'common.discard': 'Descartar', - 'common.name': 'Nombre', - 'common.email': 'Correo', - 'common.password': 'Contraseña', - 'common.saving': 'Guardando...', - 'common.saved': 'Guardado', - 'common.expand': 'Expandir', - 'common.collapse': 'Contraer', - 'trips.reminder': 'Recordatorio', - 'trips.reminderNone': 'Ninguno', - 'trips.reminderDay': 'día', - 'trips.reminderDays': 'días', - 'trips.reminderCustom': 'Personalizado', - 'trips.memberRemoved': '{username} eliminado', - 'trips.memberRemoveError': 'Error al eliminar', - 'trips.memberAdded': '{username} añadido', - 'trips.memberAddError': 'Error al añadir', - 'trips.reminderDaysBefore': 'días antes de la salida', - 'trips.reminderDisabledHint': 'Los recordatorios de viaje están desactivados. Actívalos en Admin > Configuración > Notificaciones.', - 'common.update': 'Actualizar', - 'common.change': 'Cambiar', - 'common.uploading': 'Subiendo…', - 'common.backToPlanning': 'Volver a la planificación', - 'common.reset': 'Restablecer', - - // Navbar - 'nav.trip': 'Viaje', - 'nav.share': 'Compartir', - 'nav.settings': 'Ajustes', - 'nav.admin': 'Administración', - 'nav.logout': 'Cerrar sesión', - 'nav.lightMode': 'Modo claro', - 'nav.darkMode': 'Modo oscuro', - 'nav.autoMode': 'Modo automático', - 'nav.administrator': 'Administrador', - 'nav.myTrips': 'Mis viajes', - - // Dashboard - 'dashboard.title': 'Mis viajes', - 'dashboard.subtitle.loading': 'Cargando viajes...', - 'dashboard.subtitle.trips': '{count} viajes ({archived} archivados)', - 'dashboard.subtitle.empty': 'Empieza tu primer viaje', - 'dashboard.subtitle.activeOne': '{count} viaje activo', - 'dashboard.subtitle.activeMany': '{count} viajes activos', - 'dashboard.subtitle.archivedSuffix': ' · {count} archivados', - 'dashboard.newTrip': 'Nuevo viaje', - 'dashboard.gridView': 'Vista de cuadrícula', - 'dashboard.listView': 'Vista de lista', - 'dashboard.currency': 'Divisa', - 'dashboard.timezone': 'Zonas horarias', - 'dashboard.localTime': 'Hora local', - 'dashboard.timezoneCustomTitle': 'Zona horaria personalizada', - 'dashboard.timezoneCustomLabelPlaceholder': 'Nombre (opcional)', - 'dashboard.timezoneCustomTzPlaceholder': 'ej. America/New_York', - 'dashboard.timezoneCustomAdd': 'Añadir', - 'dashboard.timezoneCustomErrorEmpty': 'Introduce una zona horaria', - 'dashboard.timezoneCustomErrorInvalid': 'Zona horaria no válida. Usa formato como Europe/Madrid', - 'dashboard.timezoneCustomErrorDuplicate': 'Ya añadida', - 'dashboard.emptyTitle': 'Aún no hay viajes', - 'dashboard.emptyText': 'Crea tu primer viaje y empieza a planificar', - 'dashboard.emptyButton': 'Crear primer viaje', - 'dashboard.nextTrip': 'Próximo viaje', - 'dashboard.shared': 'Compartido', - 'dashboard.sharedBy': 'Compartido por {name}', - 'dashboard.days': 'Días', - 'dashboard.places': 'Lugares', - 'dashboard.members': 'Compañeros de viaje', - 'dashboard.archive': 'Archivar', - 'dashboard.copyTrip': 'Copiar', - 'dashboard.copySuffix': 'copia', - 'dashboard.restore': 'Restaurar', - 'dashboard.archived': 'Archivado', - 'dashboard.status.ongoing': 'En curso', - 'dashboard.status.today': 'Hoy', - 'dashboard.status.tomorrow': 'Mañana', - 'dashboard.status.past': 'Pasado', - 'dashboard.status.daysLeft': 'Quedan {count} días', - 'dashboard.toast.loadError': 'No se pudieron cargar los viajes', - 'dashboard.toast.created': '¡Viaje creado correctamente!', - 'dashboard.toast.createError': 'No se pudo crear el viaje', - 'dashboard.toast.updated': '¡Viaje actualizado!', - 'dashboard.toast.updateError': 'No se pudo actualizar el viaje', - 'dashboard.toast.deleted': 'Viaje eliminado', - 'dashboard.toast.deleteError': 'No se pudo eliminar el viaje', - 'dashboard.toast.archived': 'Viaje archivado', - 'dashboard.toast.archiveError': 'No se pudo archivar el viaje', - 'dashboard.toast.restored': 'Viaje restaurado', - 'dashboard.toast.restoreError': 'No se pudo restaurar el viaje', - 'dashboard.toast.copied': '¡Viaje copiado!', - 'dashboard.toast.copyError': 'No se pudo copiar el viaje', - 'dashboard.confirm.delete': '¿Eliminar el viaje "{title}"? Todos los lugares y planes se borrarán permanentemente.', - 'dashboard.editTrip': 'Editar viaje', - 'dashboard.createTrip': 'Crear nuevo viaje', - 'dashboard.tripTitle': 'Título', - 'dashboard.tripTitlePlaceholder': 'p. ej. Verano en Japón', - 'dashboard.tripDescription': 'Descripción', - 'dashboard.tripDescriptionPlaceholder': '¿De qué trata este viaje?', - 'dashboard.startDate': 'Fecha de inicio', - 'dashboard.endDate': 'Fecha de fin', - 'dashboard.dayCount': 'Número de días', - 'dashboard.dayCountHint': 'Cuántos días planificar cuando no se han establecido fechas de viaje.', - 'dashboard.noDateHint': 'Sin fecha definida: se crearán 7 días por defecto. Puedes cambiarlo cuando quieras.', - 'dashboard.coverImage': 'Imagen de portada', - 'dashboard.addCoverImage': 'Añadir imagen de portada', - 'dashboard.addMembers': 'Compañeros de viaje', - 'dashboard.addMember': 'Añadir miembro', - 'dashboard.coverSaved': 'Imagen de portada guardada', - 'dashboard.coverUploadError': 'Error al subir la imagen', - 'dashboard.coverRemoveError': 'Error al eliminar la imagen', - 'dashboard.titleRequired': 'El título es obligatorio', - 'dashboard.endDateError': 'La fecha de fin debe ser posterior a la de inicio', - - // Settings - 'settings.title': 'Ajustes', - 'settings.subtitle': 'Configura tus ajustes personales', - 'settings.tabs.display': 'Pantalla', - 'settings.tabs.map': 'Mapa', - 'settings.tabs.notifications': 'Notificaciones', - 'settings.tabs.integrations': 'Integraciones', - 'settings.tabs.account': 'Cuenta', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Acerca de', - 'settings.map': 'Mapa', - 'settings.mapTemplate': 'Plantilla del mapa', - 'settings.mapTemplatePlaceholder.select': 'Seleccionar plantilla...', - 'settings.mapDefaultHint': 'Déjalo vacío para OpenStreetMap (por defecto)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'Plantilla de URL para los mosaicos del mapa', - 'settings.mapProvider': 'Proveedor de mapa', - 'settings.mapProviderHint': 'Afecta a los mapas de Trip Planner y Journey. Atlas siempre usa Leaflet.', - 'settings.mapLeafletSubtitle': 'Clásico 2D, cualquier mosaico raster', - 'settings.mapMapboxSubtitle': 'Mosaicos vectoriales, edificios 3D y terreno', - 'settings.mapExperimental': 'Experimental', - 'settings.mapMapboxToken': 'Token de acceso de Mapbox', - 'settings.mapMapboxTokenHint': 'Token público (pk.*) de', - 'settings.mapMapboxTokenLink': 'mapbox.com → Tokens de acceso', - 'settings.mapStyle': 'Estilo de mapa', - 'settings.mapStylePlaceholder': 'Seleccionar un estilo de Mapbox', - 'settings.mapStyleHint': 'Preset o tu propia URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': 'Edificios 3D y terreno', - 'settings.map3dHint': 'Inclinación + extrusiones 3D reales de edificios — funciona con todos los estilos, incluyendo satélite.', - 'settings.mapHighQuality': 'Modo de alta calidad', - 'settings.mapHighQualityHint': 'Antialiasing + proyección global para bordes más nítidos y una vista realista del mundo.', - 'settings.mapHighQualityWarning': 'Puede afectar el rendimiento en dispositivos menos potentes.', - 'settings.mapTipLabel': 'Consejo:', - 'settings.mapTip': 'Clic derecho y arrastrar para rotar/inclinar el mapa. Clic central para añadir un lugar (el clic derecho está reservado para la rotación).', - 'settings.latitude': 'Latitud', - 'settings.longitude': 'Longitud', - 'settings.saveMap': 'Guardar mapa', - 'settings.apiKeys': 'Claves API', - 'settings.mapsKey': 'Clave API de Google Maps', - 'settings.mapsKeyHint': 'Necesaria para buscar lugares. Consíguela en console.cloud.google.com', - 'settings.weatherKey': 'Clave API de OpenWeatherMap', - 'settings.weatherKeyHint': 'Para datos meteorológicos. Gratis en openweathermap.org/api', - 'settings.keyPlaceholder': 'Introduce la clave...', - 'settings.configured': 'Configurado', - 'settings.saveKeys': 'Guardar claves', - 'settings.display': 'Visualización', - 'settings.colorMode': 'Modo de color', - 'settings.light': 'Claro', - 'settings.dark': 'Oscuro', - 'settings.auto': 'Automático', - 'settings.language': 'Idioma', - 'settings.temperature': 'Unidad de temperatura', - 'settings.timeFormat': 'Formato de hora', - 'settings.blurBookingCodes': 'Difuminar códigos de reserva', - 'settings.notifications': 'Notificaciones', - 'settings.notifyTripInvite': 'Invitaciones de viaje', - 'settings.notifyBookingChange': 'Cambios en reservas', - 'settings.notifyTripReminder': 'Recordatorios de viaje', - 'settings.notifyTodoDue': 'Tarea próxima', - 'settings.notifyVacayInvite': 'Invitaciones de fusión Vacay', - 'settings.notifyPhotosShared': 'Fotos compartidas (Immich)', - 'settings.notifyCollabMessage': 'Mensajes de chat (Collab)', - 'settings.notifyPackingTagged': 'Lista de equipaje: asignaciones', - 'settings.notifyWebhook': 'Notificaciones webhook', - 'settings.notificationsDisabled': 'Las notificaciones no están configuradas. Pida a un administrador que active las notificaciones por correo o webhook.', - 'settings.notificationsActive': 'Canal activo', - 'settings.notificationsManagedByAdmin': 'Los eventos de notificación son configurados por el administrador.', - 'admin.notifications.title': 'Notificaciones', - 'admin.notifications.hint': 'Elija un canal de notificación. Solo uno puede estar activo a la vez.', - 'admin.notifications.none': 'Desactivado', - 'admin.notifications.email': 'Correo (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Guardar configuración de notificaciones', - 'admin.notifications.saved': 'Configuración de notificaciones guardada', - 'admin.notifications.testWebhook': 'Enviar webhook de prueba', - 'admin.notifications.testWebhookSuccess': 'Webhook de prueba enviado correctamente', - 'admin.notifications.testWebhookFailed': 'Error al enviar webhook de prueba', - 'admin.smtp.title': 'Correo y notificaciones', - 'admin.smtp.hint': 'Configuración SMTP para el envío de notificaciones por correo.', - 'admin.smtp.testButton': 'Enviar correo de prueba', - 'admin.webhook.hint': 'Enviar notificaciones a un webhook externo (Discord, Slack, etc.).', - 'admin.smtp.testSuccess': 'Correo de prueba enviado correctamente', - 'admin.smtp.testFailed': 'Error al enviar correo de prueba', - 'dayplan.icsTooltip': 'Exportar calendario (ICS)', - 'share.linkTitle': 'Enlace público', - 'share.linkHint': 'Crea un enlace que cualquiera puede usar para ver este viaje sin iniciar sesión. Solo lectura — no se puede editar.', - 'share.createLink': 'Crear enlace', - 'share.deleteLink': 'Eliminar enlace', - 'share.createError': 'No se pudo crear el enlace', - 'common.copy': 'Copiar', - 'common.copied': 'Copiado', - 'share.permMap': 'Mapa y plan', - 'share.permBookings': 'Reservas', - 'share.permPacking': 'Equipaje', - 'shared.expired': 'Enlace expirado o inválido', - 'shared.expiredHint': 'Este enlace de viaje compartido ya no está activo.', - 'shared.readOnly': 'Vista de solo lectura', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Reservas', - 'shared.tabPacking': 'Equipaje', - 'shared.tabBudget': 'Presupuesto', - 'shared.tabChat': 'Chat', - 'shared.days': 'días', - 'shared.places': 'lugares', - 'shared.other': 'Otro', - 'shared.totalBudget': 'Presupuesto total', - 'shared.messages': 'mensajes', - 'shared.sharedVia': 'Compartido vía', - 'shared.confirmed': 'Confirmado', - 'shared.pending': 'Pendiente', - 'share.permBudget': 'Presupuesto', - 'share.permCollab': 'Chat', - 'settings.on': 'Activado', - 'settings.off': 'Desactivado', - 'settings.mcp.title': 'Configuración MCP', - 'settings.mcp.endpoint': 'Endpoint MCP', - 'settings.mcp.clientConfig': 'Configuración del cliente', - 'settings.mcp.clientConfigHint': 'Reemplaza con un token de la lista de abajo. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Reemplaza y con las credenciales del cliente OAuth 2.1 que creaste arriba. mcp-remote abrirá el navegador para completar la autorización la primera vez que te conectes. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).', - 'settings.mcp.copy': 'Copiar', - 'settings.mcp.copied': '¡Copiado!', - 'settings.mcp.apiTokens': 'Tokens de API', - 'settings.mcp.createToken': 'Crear nuevo token', - 'settings.mcp.noTokens': 'Sin tokens aún. Crea uno para conectar clientes MCP.', - 'settings.mcp.tokenCreatedAt': 'Creado', - 'settings.mcp.tokenUsedAt': 'Usado', - 'settings.mcp.deleteTokenTitle': 'Eliminar token', - 'settings.mcp.deleteTokenMessage': 'Este token dejará de funcionar de inmediato. Cualquier cliente MCP que lo use perderá el acceso.', - 'settings.mcp.modal.createTitle': 'Crear token de API', - 'settings.mcp.modal.tokenName': 'Nombre del token', - 'settings.mcp.modal.tokenNamePlaceholder': 'p. ej. Claude Desktop, Portátil de trabajo', - 'settings.mcp.modal.creating': 'Creando…', - 'settings.mcp.modal.create': 'Crear token', - 'settings.mcp.modal.createdTitle': 'Token creado', - 'settings.mcp.modal.createdWarning': 'Este token solo se mostrará una vez. Cópialo y guárdalo ahora — no se podrá recuperar.', - 'settings.mcp.modal.done': 'Listo', - 'settings.mcp.toast.created': 'Token creado', - 'settings.mcp.toast.createError': 'Error al crear el token', - 'settings.mcp.toast.deleted': 'Token eliminado', - 'settings.mcp.toast.deleteError': 'Error al eliminar el token', - 'settings.mcp.apiTokensDeprecated': 'Los tokens de API están obsoletos y se eliminarán en una versión futura. Utilice los clientes OAuth 2.1 en su lugar.', - 'settings.oauth.clients': 'Clientes OAuth 2.1', - 'settings.oauth.clientsHint': 'Registre clientes OAuth 2.1 para que las aplicaciones MCP de terceros (Claude Web, Cursor, etc.) puedan conectarse sin tokens estáticos.', - 'settings.oauth.createClient': 'Nuevo cliente', - 'settings.oauth.noClients': 'No hay clientes OAuth registrados.', - 'settings.oauth.clientId': 'ID de cliente', - 'settings.oauth.clientSecret': 'Secreto de cliente', - 'settings.oauth.deleteClient': 'Eliminar cliente', - 'settings.oauth.deleteClientMessage': 'Este cliente y todas las sesiones activas se eliminarán permanentemente. Cualquier aplicación que lo use perderá el acceso inmediatamente.', - 'settings.oauth.rotateSecret': 'Renovar secreto', - 'settings.oauth.rotateSecretMessage': 'Se generará un nuevo secreto de cliente y todas las sesiones existentes se invalidarán de inmediato. Actualice su aplicación antes de cerrar este diálogo.', - 'settings.oauth.rotateSecretConfirm': 'Renovar', - 'settings.oauth.rotateSecretConfirming': 'Renovando…', - 'settings.oauth.rotateSecretDoneTitle': 'Nuevo secreto generado', - 'settings.oauth.rotateSecretDoneWarning': 'Este secreto solo se muestra una vez. Cópielo ahora y actualice su aplicación — todas las sesiones anteriores han sido invalidadas.', - 'settings.oauth.activeSessions': 'Sesiones OAuth activas', - 'settings.oauth.sessionScopes': 'Ámbitos', - 'settings.oauth.sessionExpires': 'Expira', - 'settings.oauth.revoke': 'Revocar', - 'settings.oauth.revokeSession': 'Revocar sesión', - 'settings.oauth.revokeSessionMessage': 'Esto revocará inmediatamente el acceso de esta sesión OAuth.', - 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth', - 'settings.oauth.modal.presets': 'Ajustes rápidos', - 'settings.oauth.modal.clientName': 'Nombre de la aplicación', - 'settings.oauth.modal.clientNamePlaceholder': 'ej. Claude Web, Mi app MCP', - 'settings.oauth.modal.redirectUris': 'URIs de redirección', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Un URI por línea. HTTPS obligatorio (localhost exento). Coincidencia exacta.', - 'settings.oauth.modal.scopes': 'Ámbitos permitidos', - 'settings.oauth.modal.scopesHint': 'list_trips y get_trip_summary siempre están disponibles — sin ámbito requerido. Permiten a la IA descubrir los IDs de viaje necesarios.', - 'settings.oauth.modal.selectAll': 'Seleccionar todo', - 'settings.oauth.modal.deselectAll': 'Deseleccionar todo', - 'settings.oauth.modal.creating': 'Registrando…', - 'settings.oauth.modal.create': 'Registrar cliente', - 'settings.oauth.modal.createdTitle': 'Cliente registrado', - 'settings.oauth.modal.createdWarning': 'El secreto del cliente solo se muestra una vez. Cópielo ahora — no se puede recuperar.', - 'settings.oauth.toast.createError': 'Error al registrar el cliente OAuth', - 'settings.oauth.toast.deleted': 'Cliente OAuth eliminado', - 'settings.oauth.toast.deleteError': 'Error al eliminar el cliente OAuth', - 'settings.oauth.toast.revoked': 'Sesión revocada', - 'settings.oauth.toast.revokeError': 'Error al revocar la sesión', - 'settings.oauth.toast.rotateError': 'Error al renovar el secreto del cliente', - 'settings.oauth.modal.machineClient': 'Cliente de máquina (sin inicio de sesión en el navegador)', - 'settings.oauth.modal.machineClientHint': 'Usa el grant client_credentials — sin URIs de redirección. El token se emite directamente vía client_id + client_secret y actúa como tú dentro de los alcances seleccionados.', - 'settings.oauth.modal.machineClientUsage': 'Obtener token: POST /oauth/token con grant_type=client_credentials, client_id y client_secret. Sin navegador, sin token de actualización.', - 'settings.oauth.badge.machine': 'máquina', - 'settings.account': 'Cuenta', - 'settings.about': 'Acerca de', - 'settings.about.reportBug': 'Reportar un error', - 'settings.about.reportBugHint': 'Encontraste un problema? Avísanos', - 'settings.about.featureRequest': 'Solicitar función', - 'settings.about.featureRequestHint': 'Sugiere una nueva función', - 'settings.about.wikiHint': 'Documentación y guías', - 'settings.about.supporters.badge': 'Patrocinadores Mensuales', - 'settings.about.supporters.title': 'Compañía de viaje para TREK', - 'settings.about.supporters.subtitle': 'Mientras planeas tu próxima ruta, estas personas ayudan a planear el futuro de TREK. Su aporte mensual va directo al desarrollo y a las horas reales invertidas — para que TREK siga siendo Open Source.', - 'settings.about.supporters.since': 'patrocinador desde {date}', - 'settings.about.supporters.tierEmpty': 'Sé el primero', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK es un planificador de viajes autoalojado que te ayuda a organizar tus viajes desde la primera idea hasta el último recuerdo. Planificación diaria, presupuesto, listas de equipaje, fotos y mucho más — todo en un solo lugar, en tu propio servidor.', - 'settings.about.madeWith': 'Hecho con', - 'settings.about.madeBy': 'por Maurice y una creciente comunidad de código abierto.', - 'settings.username': 'Usuario', - 'settings.email': 'Correo', - 'settings.role': 'Rol', - 'settings.roleAdmin': 'Administrador', - 'settings.oidcLinked': 'Vinculado con', - 'settings.changePassword': 'Cambiar contraseña', - 'settings.mustChangePassword': 'Debe cambiar su contraseña antes de continuar. Establezca una nueva contraseña a continuación.', - 'settings.currentPassword': 'Contraseña actual', - 'settings.newPassword': 'Nueva contraseña', - 'settings.confirmPassword': 'Confirmar nueva contraseña', - 'settings.updatePassword': 'Actualizar contraseña', - 'settings.passwordRequired': 'Introduce la contraseña actual y la nueva', - 'settings.passwordTooShort': 'La contraseña debe tener al menos 8 caracteres', - 'settings.passwordMismatch': 'Las contraseñas no coinciden', - 'settings.passwordChanged': 'Contraseña cambiada correctamente', - 'settings.deleteAccount': 'Eliminar cuenta', - 'settings.deleteAccountTitle': '¿Eliminar tu cuenta?', - 'settings.deleteAccountWarning': 'Tu cuenta y todos tus viajes, lugares y archivos se eliminarán permanentemente. Esta acción no se puede deshacer.', - 'settings.deleteAccountConfirm': 'Eliminar permanentemente', - 'settings.deleteBlockedTitle': 'No es posible eliminarla', - 'settings.deleteBlockedMessage': 'Eres el único administrador. Asciende a otro usuario a administrador antes de eliminar tu cuenta.', - 'settings.roleUser': 'Usuario', - 'settings.saveProfile': 'Guardar perfil', - 'settings.mfa.title': 'Autenticación de dos factores (2FA)', - 'settings.mfa.description': 'Añade un segundo paso al iniciar sesión. Usa una app de autenticación (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'Tu administrador exige autenticación en dos factores. Configura una app de autenticación abajo antes de continuar.', - 'settings.mfa.backupTitle': 'Códigos de respaldo', - 'settings.mfa.backupDescription': 'Usa estos códigos de un solo uso si pierdes acceso a tu app autenticadora.', - 'settings.mfa.backupWarning': 'Guárdalos ahora. Cada código solo se puede usar una vez.', - 'settings.mfa.backupCopy': 'Copiar códigos', - 'settings.mfa.backupDownload': 'Descargar TXT', - 'settings.mfa.backupPrint': 'Imprimir / PDF', - 'settings.mfa.backupCopied': 'Códigos de respaldo copiados', - 'settings.mfa.enabled': '2FA está activado en tu cuenta.', - 'settings.mfa.disabled': '2FA no está activado.', - 'settings.mfa.setup': 'Configurar autenticador', - 'settings.mfa.scanQr': 'Escanea este código QR con tu app o introduce la clave manualmente.', - 'settings.mfa.secretLabel': 'Clave secreta (entrada manual)', - 'settings.mfa.codePlaceholder': 'Código de 6 dígitos', - 'settings.mfa.enable': 'Activar 2FA', - 'settings.mfa.cancelSetup': 'Cancelar', - 'settings.mfa.disableTitle': 'Desactivar 2FA', - 'settings.mfa.disableHint': 'Introduce tu contraseña y un código actual de tu autenticador.', - 'settings.mfa.disable': 'Desactivar 2FA', - 'settings.mfa.toastEnabled': 'Autenticación de dos factores activada', - 'settings.mfa.toastDisabled': 'Autenticación de dos factores desactivada', - 'settings.mfa.demoBlocked': 'No disponible en modo demo', - 'settings.toast.mapSaved': 'Ajustes del mapa guardados', - 'settings.toast.keysSaved': 'Claves API guardadas', - 'settings.toast.displaySaved': 'Ajustes de visualización guardados', - 'settings.toast.profileSaved': 'Perfil guardado', - 'settings.uploadAvatar': 'Subir foto de perfil', - 'settings.removeAvatar': 'Eliminar foto de perfil', - 'settings.avatarUploaded': 'Foto de perfil actualizada', - 'settings.avatarRemoved': 'Foto de perfil eliminada', - 'settings.avatarError': 'Falló la subida', - - // Login - 'login.error': 'Inicio de sesión fallido. Revisa tus credenciales.', - 'login.tagline': 'Tus viajes.\nTu plan.', - 'login.description': 'Planifica viajes en colaboración con mapas interactivos, presupuestos y sincronización en tiempo real.', - 'login.features.maps': 'Mapas interactivos', - 'login.features.mapsDesc': 'Google Places, rutas y agrupación', - 'login.features.realtime': 'Sincronización en tiempo real', - 'login.features.realtimeDesc': 'Planificad juntos mediante WebSocket', - 'login.features.budget': 'Control de presupuesto', - 'login.features.budgetDesc': 'Categorías, gráficos y costes por persona', - 'login.features.collab': 'Colaboración', - 'login.features.collabDesc': 'Multiusuario con viajes compartidos', - 'login.features.packing': 'Listas de equipaje', - 'login.features.packingDesc': 'Categorías, progreso y sugerencias', - 'login.features.bookings': 'Reservas', - 'login.features.bookingsDesc': 'Vuelos, hoteles, restaurantes y más', - 'login.features.files': 'Documentos', - 'login.features.filesDesc': 'Sube y gestiona documentos', - 'login.features.routes': 'Rutas inteligentes', - 'login.features.routesDesc': 'Optimización automática y exportación a Google Maps', - 'login.selfHosted': 'Autoalojado · Código abierto · Tus datos siguen siendo tuyos', - 'login.title': 'Iniciar sesión', - 'login.subtitle': 'Bienvenido de nuevo', - 'login.signingIn': 'Iniciando sesión…', - 'login.signIn': 'Entrar', - 'login.createAdmin': 'Crear cuenta de administrador', - 'login.createAdminHint': 'Configura la primera cuenta administradora de TREK.', - 'login.setNewPassword': 'Establecer nueva contraseña', - 'login.setNewPasswordHint': 'Debe cambiar su contraseña antes de continuar.', - 'login.createAccount': 'Crear cuenta', - 'login.createAccountHint': 'Crea una cuenta nueva.', - 'login.creating': 'Creando…', - 'login.noAccount': '¿No tienes cuenta?', - 'login.hasAccount': '¿Ya tienes cuenta?', - 'login.register': 'Registrarse', - 'login.emailPlaceholder': 'tu@correo.com', - 'login.username': 'Usuario', - 'login.oidc.registrationDisabled': 'El registro está desactivado. Contacta con tu administrador.', - 'login.oidc.noEmail': 'No se recibió ningún correo del proveedor.', - 'login.mfaTitle': 'Autenticación de dos factores', - 'login.mfaSubtitle': 'Introduce el código de 6 dígitos de tu app de autenticación.', - 'login.mfaCodeLabel': 'Código de verificación', - 'login.mfaCodeRequired': 'Introduce el código de tu app de autenticación.', - 'login.mfaHint': 'Abre Google Authenticator, Authy u otra app TOTP.', - 'login.mfaBack': '← Volver al inicio de sesión', - 'login.mfaVerify': 'Verificar', - 'login.invalidInviteLink': 'Enlace de invitación inválido o expirado', - 'login.oidcFailed': 'Error de inicio de sesión OIDC', - 'login.usernameRequired': 'El nombre de usuario es obligatorio', - 'login.passwordMinLength': 'La contraseña debe tener al menos 8 caracteres', - 'login.forgotPassword': '¿Olvidaste tu contraseña?', - 'login.forgotPasswordTitle': 'Restablecer tu contraseña', - 'login.forgotPasswordBody': 'Introduce la dirección de correo con la que te registraste. Si existe una cuenta, enviaremos un enlace.', - 'login.forgotPasswordSubmit': 'Enviar enlace', - 'login.forgotPasswordSentTitle': 'Revisa tu correo', - 'login.forgotPasswordSentBody': 'Si existe una cuenta con ese correo, el enlace de restablecimiento está en camino. Caduca en 60 minutos.', - 'login.forgotPasswordSmtpHintOff': 'Nota: tu administrador no ha configurado SMTP, así que el enlace de restablecimiento se escribirá en la consola del servidor en lugar de enviarse por correo.', - 'login.backToLogin': 'Volver al inicio de sesión', - 'login.newPassword': 'Nueva contraseña', - 'login.confirmPassword': 'Confirmar nueva contraseña', - 'login.passwordsDontMatch': 'Las contraseñas no coinciden', - 'login.mfaCode': 'Código 2FA', - 'login.resetPasswordTitle': 'Establecer una nueva contraseña', - 'login.resetPasswordBody': 'Elige una contraseña segura que no hayas usado aquí antes. Mínimo 8 caracteres.', - 'login.resetPasswordMfaBody': 'Introduce tu código 2FA o un código de respaldo para completar el restablecimiento.', - 'login.resetPasswordSubmit': 'Restablecer contraseña', - 'login.resetPasswordVerify': 'Verificar y restablecer', - 'login.resetPasswordSuccessTitle': 'Contraseña actualizada', - 'login.resetPasswordSuccessBody': 'Ya puedes iniciar sesión con tu nueva contraseña.', - 'login.resetPasswordInvalidLink': 'Enlace de restablecimiento no válido', - 'login.resetPasswordInvalidLinkBody': 'Este enlace falta o está roto. Solicita uno nuevo para continuar.', - 'login.resetPasswordFailed': 'Restablecimiento fallido. El enlace puede haber caducado.', - 'login.oidc.tokenFailed': 'La autenticación falló.', - 'login.oidc.invalidState': 'Sesión no válida. Inténtalo de nuevo.', - 'login.demoFailed': 'Falló el acceso a la demo', - 'login.oidcSignIn': 'Entrar con {name}', - 'login.demoHint': 'Prueba la demo: no necesitas registrarte', - - // Register - 'register.passwordMismatch': 'Las contraseñas no coinciden', - 'register.passwordTooShort': 'La contraseña debe tener al menos 8 caracteres', - 'register.failed': 'Falló el registro', - 'register.getStarted': 'Empezar', - 'register.subtitle': 'Crea una cuenta y empieza a planificar tus viajes.', - 'register.feature1': 'Planes de viaje ilimitados', - 'register.feature2': 'Vista de mapa interactiva', - 'register.feature3': 'Gestiona lugares y categorías', - 'register.feature4': 'Haz seguimiento de las reservas', - 'register.feature5': 'Crea listas de equipaje', - 'register.feature6': 'Guarda fotos y archivos', - 'register.createAccount': 'Crear cuenta', - 'register.startPlanning': 'Empieza a planificar tu viaje', - 'register.minChars': 'Mín. 6 caracteres', - 'register.confirmPassword': 'Confirmar contraseña', - 'register.repeatPassword': 'Repetir contraseña', - 'register.registering': 'Registrando...', - 'register.register': 'Registrarse', - 'register.hasAccount': '¿Ya tienes cuenta?', - 'register.signIn': 'Iniciar sesión', - - // Admin - 'admin.title': 'Administración', - 'admin.subtitle': 'Gestión de usuarios y ajustes del sistema', - 'admin.tabs.users': 'Usuarios', - 'admin.tabs.categories': 'Categorías', - 'admin.tabs.backup': 'Copia de seguridad', - 'admin.tabs.audit': 'Auditoría', - 'admin.stats.users': 'Usuarios', - 'admin.stats.trips': 'Viajes', - 'admin.stats.places': 'Lugares', - 'admin.stats.photos': 'Fotos', - 'admin.stats.files': 'Archivos', - 'admin.table.user': 'Usuario', - 'admin.table.email': 'Correo', - 'admin.table.role': 'Rol', - 'admin.table.created': 'Creado', - 'admin.table.lastLogin': 'Último acceso', - 'admin.table.actions': 'Acciones', - 'admin.you': '(Tú)', - 'admin.editUser': 'Editar usuario', - 'admin.newPassword': 'Nueva contraseña', - 'admin.newPasswordHint': 'Déjalo vacío para mantener la contraseña actual', - 'admin.deleteUser': '¿Eliminar al usuario "{name}"? Todos sus viajes se borrarán permanentemente.', - 'admin.deleteUserTitle': 'Eliminar usuario', - 'admin.newPasswordPlaceholder': 'Introduce una nueva contraseña…', - 'admin.toast.loadError': 'No se pudieron cargar los datos de administración', - 'admin.toast.userUpdated': 'Usuario actualizado', - 'admin.toast.updateError': 'No se pudo actualizar', - 'admin.toast.userDeleted': 'Usuario eliminado', - 'admin.toast.deleteError': 'No se pudo eliminar', - 'admin.toast.cannotDeleteSelf': 'No puedes eliminar tu propia cuenta', - 'admin.toast.userCreated': 'Usuario creado', - 'admin.toast.createError': 'No se pudo crear el usuario', - 'admin.toast.fieldsRequired': 'Usuario, correo y contraseña son obligatorios', - 'admin.createUser': 'Crear usuario', - 'admin.invite.title': 'Enlaces de invitación', - 'admin.invite.subtitle': 'Crear enlaces de registro de un solo uso', - 'admin.invite.create': 'Crear enlace', - 'admin.invite.createAndCopy': 'Crear y copiar', - 'admin.invite.empty': 'No se han creado enlaces de invitación', - 'admin.invite.maxUses': 'Usos máx.', - 'admin.invite.expiry': 'Expira después de', - 'admin.invite.uses': 'usado(s)', - 'admin.invite.expiresAt': 'expira el', - 'admin.invite.createdBy': 'por', - 'admin.invite.active': 'Activo', - 'admin.invite.expired': 'Expirado', - 'admin.invite.usedUp': 'Agotado', - 'admin.invite.copied': 'Enlace de invitación copiado', - 'admin.invite.copyLink': 'Copiar enlace', - 'admin.invite.deleted': 'Enlace de invitación eliminado', - 'admin.invite.createError': 'Error al crear el enlace', - 'admin.invite.deleteError': 'Error al eliminar el enlace', - 'admin.tabs.settings': 'Ajustes', - 'admin.allowRegistration': 'Permitir el registro', - 'admin.allowRegistrationHint': 'Los nuevos usuarios pueden registrarse por sí mismos', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Exigir autenticación en dos factores (2FA)', - 'admin.requireMfaHint': 'Los usuarios sin 2FA deben completar la configuración en Ajustes antes de usar la aplicación.', - 'admin.apiKeys': 'Claves API', - 'admin.apiKeysHint': 'Opcional. Activa datos ampliados de lugares, como fotos y previsión del tiempo.', - 'admin.mapsKey': 'Clave API de Google Maps', - 'admin.mapsKeyHint': 'Obligatoria para buscar lugares. Consíguela en console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Sin una clave API, la búsqueda de lugares usa OpenStreetMap. Con una clave de Google también se pueden cargar fotos, valoraciones y horarios de apertura. Consíguela en console.cloud.google.com.', - 'admin.recommended': 'Recomendado', - 'admin.weatherKey': 'Clave API de OpenWeatherMap', - 'admin.weatherKeyHint': 'Para datos meteorológicos. Gratis en openweathermap.org', - 'admin.validateKey': 'Probar', - 'admin.keyValid': 'Conectado', - 'admin.keyInvalid': 'No válida', - 'admin.keySaved': 'Claves API guardadas', - 'admin.oidcTitle': 'Inicio de sesión único (OIDC)', - 'admin.oidcSubtitle': 'Permite iniciar sesión mediante proveedores externos como Google, Apple, Authentik o Keycloak.', - 'admin.oidcDisplayName': 'Nombre visible', - 'admin.oidcIssuer': 'URL del emisor', - 'admin.oidcIssuerHint': 'La URL Issuer de OpenID Connect del proveedor. Ej.: https://accounts.google.com', - 'admin.oidcSaved': 'Configuración OIDC guardada', - - // File Types - 'admin.fileTypes': 'Tipos de archivo permitidos', - 'admin.fileTypesHint': 'Configura qué tipos de archivo pueden subir los usuarios.', - 'admin.fileTypesFormat': 'Extensiones separadas por comas (p. ej. jpg,png,pdf,doc). Usa * para permitir todos los tipos.', - 'admin.fileTypesSaved': 'Ajustes de tipos de archivo guardados', - - 'admin.placesPhotos.title': 'Fotos de Lugares', - 'admin.placesPhotos.subtitle': 'Obtiene fotos de la Google Places API. Desactiva para ahorrar cuota de API. Las fotos de Wikimedia no se ven afectadas.', - 'admin.placesAutocomplete.title': 'Autocompletado de Lugares', - 'admin.placesAutocomplete.subtitle': 'Usa la Google Places API para sugerencias de búsqueda. Desactiva para ahorrar cuota de API.', - 'admin.placesDetails.title': 'Detalles del Lugar', - 'admin.placesDetails.subtitle': 'Obtiene información detallada del lugar (horarios, valoración, web) de la Google Places API. Desactiva para ahorrar cuota de API.', - 'admin.bagTracking.title': 'Seguimiento de equipaje', - 'admin.bagTracking.subtitle': 'Activar peso y asignación de equipaje para artículos de la lista', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Mensajería en tiempo real para la colaboración', - 'admin.collab.notes.title': 'Notas', - 'admin.collab.notes.subtitle': 'Notas y documentos compartidos', - 'admin.collab.polls.title': 'Encuestas', - 'admin.collab.polls.subtitle': 'Encuestas y votaciones grupales', - 'admin.collab.whatsnext.title': 'Qué sigue', - 'admin.collab.whatsnext.subtitle': 'Sugerencias de actividades y próximos pasos', - 'admin.tabs.config': 'Personalización', - 'admin.tabs.defaults': 'Valores predeterminados', - 'admin.defaultSettings.title': 'Configuración predeterminada de usuarios', - 'admin.defaultSettings.description': 'Establece valores predeterminados para toda la instancia. Los usuarios que no hayan cambiado una opción verán estos valores. Sus propios cambios siempre tienen prioridad.', - 'admin.defaultSettings.saved': 'Predeterminado guardado', - 'admin.defaultSettings.reset': 'Restaurar al valor predeterminado integrado', - 'admin.defaultSettings.resetToBuiltIn': 'restaurar', - 'admin.tabs.templates': 'Plantillas de equipaje', - 'admin.packingTemplates.title': 'Plantillas de equipaje', - 'admin.packingTemplates.subtitle': 'Crear listas de equipaje reutilizables para tus viajes', - 'admin.packingTemplates.create': 'Nueva plantilla', - 'admin.packingTemplates.namePlaceholder': 'Nombre de la plantilla (ej. Vacaciones en la playa)', - 'admin.packingTemplates.empty': 'No se han creado plantillas aún', - 'admin.packingTemplates.items': 'artículos', - 'admin.packingTemplates.categories': 'categorías', - 'admin.packingTemplates.itemName': 'Nombre del artículo', - 'admin.packingTemplates.itemCategory': 'Categoría', - 'admin.packingTemplates.categoryName': 'Nombre de categoría (ej. Ropa)', - 'admin.packingTemplates.addCategory': 'Añadir categoría', - 'admin.packingTemplates.created': 'Plantilla creada', - 'admin.packingTemplates.deleted': 'Plantilla eliminada', - 'admin.packingTemplates.loadError': 'Error al cargar plantillas', - 'admin.packingTemplates.createError': 'Error al crear plantilla', - 'admin.packingTemplates.deleteError': 'Error al eliminar plantilla', - 'admin.packingTemplates.saveError': 'Error al guardar', - - // Addons - 'admin.tabs.addons': 'Complementos', - 'admin.addons.title': 'Complementos', - 'admin.addons.subtitle': 'Activa o desactiva funciones para personalizar tu experiencia en TREK.', - 'admin.addons.subtitleBefore': 'Activa o desactiva funciones para personalizar tu experiencia en ', - 'admin.addons.subtitleAfter': '.', - 'admin.addons.enabled': 'Activo', - 'admin.addons.disabled': 'Desactivado', - 'admin.addons.type.trip': 'Viaje', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integración', - 'admin.addons.tripHint': 'Disponible como pestaña dentro de cada viaje', - 'admin.addons.globalHint': 'Disponible como sección independiente en la navegación principal', - 'admin.addons.integrationHint': 'Servicios backend e integraciones de API sin página dedicada', - 'admin.addons.toast.updated': 'Complemento actualizado', - 'admin.addons.toast.error': 'No se pudo actualizar el complemento', - 'admin.addons.noAddons': 'No hay complementos disponibles', - 'admin.weather.title': 'Datos meteorológicos', - 'admin.weather.badge': 'Desde el 24 de marzo de 2026', - 'admin.weather.description': 'TREK utiliza Open-Meteo como fuente de datos meteorológicos. Open-Meteo es un servicio meteorológico gratuito y de código abierto: no requiere clave API.', - 'admin.weather.forecast': 'Pronóstico de 16 días', - 'admin.weather.forecastDesc': 'Antes eran 5 días (OpenWeatherMap)', - 'admin.weather.climate': 'Datos climáticos históricos', - 'admin.weather.climateDesc': 'Promedios de los últimos 85 años para fechas posteriores al pronóstico de 16 días', - 'admin.weather.requests': '10.000 solicitudes / día', - 'admin.weather.requestsDesc': 'Gratis, sin necesidad de clave API', - 'admin.weather.locationHint': 'El tiempo se basa en el primer lugar con coordenadas de cada día. Si no hay ningún lugar asignado a un día, se usa como referencia cualquier lugar de la lista.', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'Acceso MCP', - 'admin.mcpTokens.title': 'Acceso MCP', - 'admin.mcpTokens.subtitle': 'Gestionar sesiones OAuth y tokens de API de todos los usuarios', - 'admin.mcpTokens.sectionTitle': 'Tokens de API', - 'admin.mcpTokens.owner': 'Propietario', - 'admin.mcpTokens.tokenName': 'Nombre del token', - 'admin.mcpTokens.created': 'Creado', - 'admin.mcpTokens.lastUsed': 'Último uso', - 'admin.mcpTokens.never': 'Nunca', - 'admin.mcpTokens.empty': 'Aún no se han creado tokens MCP', - 'admin.mcpTokens.deleteTitle': 'Eliminar token', - 'admin.mcpTokens.deleteMessage': 'Este token se revocará inmediatamente. El usuario perderá el acceso MCP a través de este token.', - 'admin.mcpTokens.deleteSuccess': 'Token eliminado', - 'admin.mcpTokens.deleteError': 'No se pudo eliminar el token', - 'admin.mcpTokens.loadError': 'No se pudieron cargar los tokens', - 'admin.oauthSessions.sectionTitle': 'Sesiones OAuth', - 'admin.oauthSessions.clientName': 'Cliente', - 'admin.oauthSessions.owner': 'Propietario', - 'admin.oauthSessions.scopes': 'Permisos', - 'admin.oauthSessions.created': 'Creado', - 'admin.oauthSessions.empty': 'No hay sesiones OAuth activas', - 'admin.oauthSessions.revokeTitle': 'Revocar sesión', - 'admin.oauthSessions.revokeMessage': 'Esto revocará la sesión OAuth inmediatamente. El cliente perderá el acceso MCP.', - 'admin.oauthSessions.revokeSuccess': 'Sesión revocada', - 'admin.oauthSessions.revokeError': 'No se pudo revocar la sesión', - 'admin.oauthSessions.loadError': 'No se pudieron cargar las sesiones OAuth', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Eventos sensibles de seguridad y administración (copias de seguridad, usuarios, MFA, ajustes).', - 'admin.audit.empty': 'Aún no hay entradas de auditoría.', - 'admin.audit.refresh': 'Actualizar', - 'admin.audit.loadMore': 'Cargar más', - 'admin.audit.showing': '{count} cargados · {total} en total', - 'admin.audit.col.time': 'Fecha y hora', - 'admin.audit.col.user': 'Usuario', - 'admin.audit.col.action': 'Acción', - 'admin.audit.col.resource': 'Recurso', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Detalles', - - 'admin.github.title': 'Historial de versiones', - 'admin.github.subtitle': 'Últimas novedades de {repo}', - 'admin.github.latest': 'Última', - 'admin.github.prerelease': 'Prelanzamiento', - 'admin.github.showDetails': 'Mostrar detalles', - 'admin.github.hideDetails': 'Ocultar detalles', - 'admin.github.loadMore': 'Cargar más', - 'admin.github.loading': 'Cargando...', - 'admin.github.support': 'Ayuda a seguir desarrollando TREK', - 'admin.github.error': 'No se pudieron cargar las versiones', - 'admin.github.by': 'por', - 'admin.update.available': 'Actualización disponible', - 'admin.update.text': 'TREK {version} está disponible. Estás usando {current}.', - 'admin.update.button': 'Ver en GitHub', - 'admin.update.install': 'Instalar actualización', - 'admin.update.confirmTitle': '¿Instalar actualización?', - 'admin.update.confirmText': 'TREK se actualizará de {current} a {version}. Después, el servidor se reiniciará automáticamente.', - 'admin.update.dataInfo': 'Todos tus datos (viajes, usuarios, claves API, subidas, Vacay, Atlas, presupuestos) se conservarán.', - 'admin.update.warning': 'La app estará brevemente no disponible durante el reinicio.', - 'admin.update.confirm': 'Actualizar ahora', - 'admin.update.installing': 'Actualizando…', - 'admin.update.success': '¡Actualización instalada! El servidor se está reiniciando…', - 'admin.update.failed': 'La actualización falló', - 'admin.update.backupHint': 'Recomendamos crear una copia de seguridad antes de actualizar.', - 'admin.update.backupLink': 'Ir a Copia de seguridad', - 'admin.update.howTo': 'Cómo actualizar', - 'admin.update.dockerText': 'Tu instancia de TREK se ejecuta en Docker. Para actualizar a {version}, ejecuta los siguientes comandos en tu servidor:', - 'admin.update.reloadHint': 'Recarga la página en unos segundos.', - - // Vacay addon - 'vacay.subtitle': 'Planifica y gestiona días de vacaciones', - 'vacay.settings': 'Ajustes', - 'vacay.year': 'Año', - 'vacay.addYear': 'Añadir año siguiente', - 'vacay.addPrevYear': 'Añadir año anterior', - 'vacay.removeYear': 'Eliminar año', - 'vacay.removeYearConfirm': '¿Eliminar {year}?', - 'vacay.removeYearHint': 'Todas las vacaciones y festivos de empresa de este año se borrarán permanentemente.', - 'vacay.remove': 'Eliminar', - 'vacay.persons': 'Personas', - 'vacay.noPersons': 'No se han añadido personas', - 'vacay.addPerson': 'Añadir persona', - 'vacay.editPerson': 'Editar persona', - 'vacay.removePerson': 'Eliminar persona', - 'vacay.removePersonConfirm': '¿Eliminar a {name}?', - 'vacay.removePersonHint': 'Todas las vacaciones de esta persona se borrarán permanentemente.', - 'vacay.personName': 'Nombre', - 'vacay.personNamePlaceholder': 'Introduce un nombre', - 'vacay.color': 'Color', - 'vacay.add': 'Añadir', - 'vacay.legend': 'Leyenda', - 'vacay.publicHoliday': 'Festivo', - 'vacay.companyHoliday': 'Festivo de empresa', - 'vacay.weekend': 'Fin de semana', - 'vacay.modeVacation': 'Vacaciones', - 'vacay.modeCompany': 'Festivo de empresa', - 'vacay.entitlement': 'Derecho', - 'vacay.entitlementDays': 'Días', - 'vacay.used': 'Usados', - 'vacay.remaining': 'Restantes', - 'vacay.carriedOver': 'de {year}', - 'vacay.blockWeekends': 'Bloquear fines de semana', - 'vacay.blockWeekendsHint': 'Impide marcar vacaciones en sábados y domingos', - 'vacay.weekendDays': 'Días de fin de semana', - 'vacay.mon': 'Lun', - 'vacay.tue': 'Mar', - 'vacay.wed': 'Mié', - 'vacay.thu': 'Jue', - 'vacay.fri': 'Vie', - 'vacay.sat': 'Sáb', - 'vacay.sun': 'Dom', - 'vacay.publicHolidays': 'Festivos', - 'vacay.publicHolidaysHint': 'Marcar festivos en el calendario', - 'vacay.selectCountry': 'Seleccionar país', - 'vacay.selectRegion': 'Seleccionar región (opcional)', - 'vacay.companyHolidays': 'Festivos de empresa', - 'vacay.companyHolidaysHint': 'Permitir marcar días festivos comunes de la empresa', - 'vacay.companyHolidaysNoDeduct': 'Los festivos de empresa no descuentan días de vacaciones.', - 'vacay.weekStart': 'La semana comienza el', - 'vacay.weekStartHint': 'Elige si la semana comienza el lunes o el domingo', - 'vacay.carryOver': 'Arrastrar saldo', - 'vacay.carryOverHint': 'Trasladar automáticamente los días restantes al año siguiente', - 'vacay.sharing': 'Compartir', - 'vacay.sharingHint': 'Comparte tu calendario de vacaciones con otros usuarios de TREK', - 'vacay.owner': 'Propietario', - 'vacay.shareEmailPlaceholder': 'Correo electrónico del usuario de TREK', - 'vacay.shareSuccess': 'Plan compartido correctamente', - 'vacay.shareError': 'No se pudo compartir el plan', - 'vacay.dissolve': 'Deshacer fusión', - 'vacay.dissolveHint': 'Separar de nuevo los calendarios. Tus entradas se conservarán.', - 'vacay.dissolveAction': 'Disolver', - 'vacay.dissolved': 'Calendario separado', - 'vacay.fusedWith': 'Fusionado con', - 'vacay.you': 'tú', - 'vacay.noData': 'Sin datos', - 'vacay.changeColor': 'Cambiar color', - 'vacay.inviteUser': 'Invitar usuario', - 'vacay.inviteHint': 'Invita a otro usuario de TREK a compartir un calendario combinado de vacaciones.', - 'vacay.selectUser': 'Seleccionar usuario', - 'vacay.sendInvite': 'Enviar invitación', - 'vacay.inviteSent': 'Invitación enviada', - 'vacay.inviteError': 'No se pudo enviar la invitación', - 'vacay.pending': 'pendiente', - 'vacay.noUsersAvailable': 'No hay usuarios disponibles', - 'vacay.accept': 'Aceptar', - 'vacay.decline': 'Rechazar', - 'vacay.acceptFusion': 'Aceptar y fusionar', - 'vacay.inviteTitle': 'Solicitud de fusión', - 'vacay.inviteWantsToFuse': 'quiere compartir un calendario de vacaciones contigo.', - 'vacay.fuseInfo1': 'Ambos veréis todas las entradas de vacaciones en un único calendario compartido.', - 'vacay.fuseInfo2': 'Ambas partes pueden crear y editar entradas mutuamente.', - 'vacay.fuseInfo3': 'Ambas partes pueden borrar entradas y cambiar el número de días de vacaciones disponibles.', - 'vacay.fuseInfo4': 'Ajustes como festivos y festivos de empresa se comparten.', - 'vacay.fuseInfo5': 'La fusión puede disolverse en cualquier momento por cualquiera de las partes. Tus entradas se conservarán.', - 'vacay.addCalendar': 'Añadir calendario', - 'vacay.calendarColor': 'Color del calendario', - 'vacay.calendarLabel': 'Etiqueta', - 'vacay.noCalendars': 'Sin calendarios', - - // Atlas addon - 'atlas.subtitle': 'Tu huella viajera por el mundo', - 'atlas.countries': 'Países', - 'atlas.trips': 'Viajes', - 'atlas.places': 'Lugares', - 'atlas.days': 'Días', - 'atlas.visitedCountries': 'Países visitados', - 'atlas.cities': 'Ciudades', - 'atlas.noData': 'Aún no hay datos de viaje', - 'atlas.noDataHint': 'Crea un viaje y añade lugares para ver tu mapa del mundo', - 'atlas.lastTrip': 'Último viaje', - 'atlas.nextTrip': 'Próximo viaje', - 'atlas.daysLeft': 'días restantes', - 'atlas.streak': 'Racha', - 'atlas.year': 'año', - 'atlas.years': 'años', - 'atlas.yearInRow': 'año seguido', - 'atlas.yearsInRow': 'años seguidos', - 'atlas.tripIn': 'viaje en', - 'atlas.tripsIn': 'viajes en', - 'atlas.since': 'desde', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Asia', - 'atlas.northAmerica': 'América del Norte', - 'atlas.southAmerica': 'América del Sur', - 'atlas.africa': 'África', - 'atlas.oceania': 'Oceanía', - 'atlas.other': 'Otros', - 'atlas.firstVisit': 'Primer viaje', - 'atlas.lastVisitLabel': 'Último viaje', - 'atlas.tripSingular': 'Viaje', - 'atlas.tripPlural': 'Viajes', - 'atlas.placeVisited': 'Lugar visitado', - 'atlas.placesVisited': 'Lugares visitados', - 'atlas.statsTab': 'Estadísticas', - 'atlas.bucketTab': 'Lista de deseos', - 'atlas.addBucket': 'Añadir a lista de deseos', - 'atlas.bucketNamePlaceholder': 'Lugar o destino...', - 'atlas.bucketNotesPlaceholder': 'Notas (opcional)', - 'atlas.bucketEmpty': 'Tu lista de deseos está vacía', - 'atlas.bucketEmptyHint': 'Añade lugares que sueñas con visitar', - 'atlas.unmark': 'Eliminar', - 'atlas.confirmMark': '¿Marcar este país como visitado?', - 'atlas.confirmUnmark': '¿Eliminar este país de tu lista de visitados?', - 'atlas.confirmUnmarkRegion': '¿Eliminar esta región de tu lista de visitados?', - 'atlas.markVisited': 'Marcar como visitado', - 'atlas.markVisitedHint': 'Añadir este país a tu lista de visitados', - 'atlas.markRegionVisitedHint': 'Añadir esta región a tu lista de visitados', - 'atlas.addToBucket': 'Añadir a lista de deseos', - 'atlas.addPoi': 'Añadir lugar', - 'atlas.searchCountry': 'Buscar un país...', - 'atlas.month': 'Mes', - 'atlas.addToBucketHint': 'Guardar como lugar que quieres visitar', - 'atlas.bucketWhen': '¿Cuándo planeas visitarlo?', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transportes', - 'trip.tabs.reservations': 'Reservas', - 'trip.tabs.reservationsShort': 'Reservas', - 'trip.tabs.packing': 'Lista de equipaje', - 'trip.tabs.packingShort': 'Equipaje', - 'trip.tabs.lists': 'Listas', - 'trip.tabs.listsShort': 'Listas', - 'trip.tabs.budget': 'Presupuesto', - 'trip.tabs.files': 'Archivos', - 'trip.loading': 'Cargando viaje...', - 'trip.loadingPhotos': 'Cargando fotos de los lugares...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Lugares', - 'trip.toast.placeUpdated': 'Lugar actualizado', - 'trip.toast.placeAdded': 'Lugar añadido', - 'trip.toast.placeDeleted': 'Lugar eliminado', - 'trip.toast.selectDay': 'Selecciona primero un día', - 'trip.toast.assignedToDay': 'Lugar asignado al día', - 'trip.toast.reorderError': 'No se pudo reordenar', - 'trip.toast.reservationUpdated': 'Reserva actualizada', - 'trip.toast.reservationAdded': 'Reserva añadida', - 'trip.toast.deleted': 'Eliminado', - 'trip.confirm.deletePlace': '¿Seguro que quieres eliminar este lugar?', - 'trip.confirm.deletePlaces': '¿Eliminar {count} lugares?', - 'trip.toast.placesDeleted': '{count} lugares eliminados', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'No hay lugares planificados para este día', - 'dayplan.addNote': 'Añadir nota', - 'dayplan.editNote': 'Editar nota', - 'dayplan.noteAdd': 'Añadir nota', - 'dayplan.noteEdit': 'Editar nota', - 'dayplan.noteTitle': 'Nota', - 'dayplan.noteSubtitle': 'Nota diaria', - 'dayplan.totalCost': 'Coste total', - 'dayplan.days': 'Días', - 'dayplan.dayN': 'Día {n}', - 'dayplan.calculating': 'Calculando...', - 'dayplan.route': 'Ruta', - 'dayplan.optimize': 'Optimizar', - 'dayplan.optimized': 'Ruta optimizada', - 'dayplan.routeError': 'No se pudo calcular la ruta', - 'dayplan.toast.needTwoPlaces': 'Se necesitan al menos dos lugares para optimizar la ruta', - 'dayplan.toast.routeOptimized': 'Ruta optimizada', - 'dayplan.toast.noGeoPlaces': 'No se encontraron lugares con coordenadas para calcular la ruta', - 'dayplan.confirmed': 'Confirmado', - 'dayplan.pendingRes': 'Pendiente', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Exportar plan diario como PDF', - 'dayplan.pdfError': 'No se pudo exportar el PDF', - 'dayplan.cannotReorderTransport': 'Las reservas con hora fija no se pueden reordenar', - 'dayplan.confirmRemoveTimeTitle': '¿Eliminar hora?', - 'dayplan.confirmRemoveTimeBody': 'Este lugar tiene una hora fija ({time}). Al moverlo se eliminará la hora y se permitirá el orden libre.', - 'dayplan.confirmRemoveTimeAction': 'Eliminar hora y mover', - 'dayplan.cannotDropOnTimed': 'No se pueden colocar elementos entre entradas con hora fija', - 'dayplan.cannotBreakChronology': 'Esto rompería el orden cronológico de los elementos y reservas programados', - - // Places Sidebar - 'places.addPlace': 'Añadir lugar/actividad', - 'places.importFile': 'Importar archivo', - 'places.sidebarDrop': 'Soltar para importar', - 'places.importFileHint': 'Importa archivos .gpx, .kml o .kmz de herramientas como Google My Maps, Google Earth o un rastreador GPS.', - 'places.importFileDropHere': 'Haz clic para seleccionar un archivo o arrástralo aquí', - 'places.importFileDropActive': 'Suelta el archivo para seleccionarlo', - 'places.importFileUnsupported': 'Tipo de archivo no compatible. Usa .gpx, .kml o .kmz.', - 'places.importFileTooLarge': 'El archivo es demasiado grande. El tamaño máximo de carga es {maxMb} MB.', - 'places.importFileError': 'Importación fallida', - 'places.importAllSkipped': 'Todos los lugares ya estaban en el viaje.', - 'places.gpxImported': '{count} lugares importados desde GPX', - 'places.gpxImportTypes': '¿Qué deseas importar?', - 'places.gpxImportWaypoints': 'Puntos de ruta', - 'places.gpxImportRoutes': 'Rutas', - 'places.gpxImportTracks': 'Tracks (con geometría de ruta)', - 'places.gpxImportNoneSelected': 'Selecciona al menos un tipo para importar.', - 'places.kmlImportTypes': '¿Qué deseas importar?', - 'places.kmlImportPoints': 'Puntos (Placemarks)', - 'places.kmlImportPaths': 'Rutas (LineStrings)', - 'places.kmlImportNoneSelected': 'Selecciona al menos un tipo.', - 'places.selectionCount': '{count} seleccionado(s)', - 'places.deleteSelected': 'Eliminar selección', - 'places.kmlKmzImported': '{count} lugares importados desde KMZ/KML', - 'places.urlResolved': 'Lugar importado desde URL', - 'places.importList': 'Importar lista', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importados: {created} • Omitidos: {skipped}', - 'places.importGoogleList': 'Lista Google', - 'places.importNaverList': 'Lista Naver', - 'places.googleListHint': 'Pega un enlace compartido de una lista de Google Maps para importar todos los lugares.', - 'places.googleListImported': '{count} lugares importados de "{list}"', - 'places.googleListError': 'Error al importar la lista de Google Maps', - 'places.naverListHint': 'Pega un enlace compartido de una lista de Naver Maps para importar todos los lugares.', - 'places.naverListImported': '{count} lugares importados de "{list}"', - 'places.naverListError': 'Error al importar la lista de Naver Maps', - 'places.viewDetails': 'Ver detalles', - 'places.assignToDay': '¿A qué día añadirlo?', - 'places.all': 'Todo', - 'places.unplanned': 'Sin planificar', - 'places.filterTracks': 'Rutas', - 'places.search': 'Buscar lugares...', - 'places.allCategories': 'Todas las categorías', - 'places.categoriesSelected': 'categorías', - 'places.clearFilter': 'Borrar filtro', - 'places.count': '{count} lugares', - 'places.countSingular': '1 lugar', - 'places.allPlanned': 'Todos los lugares están planificados', - 'places.noneFound': 'No se encontraron lugares', - 'places.editPlace': 'Editar lugar', - 'places.formName': 'Nombre', - 'places.formNamePlaceholder': 'p. ej. Torre Eiffel', - 'places.formDescription': 'Descripción', - 'places.formDescriptionPlaceholder': 'Descripción breve...', - 'places.formAddress': 'Dirección', - 'places.formAddressPlaceholder': 'Calle, ciudad, país', - 'places.formLat': 'Latitud (p. ej. 48.8566)', - 'places.formLng': 'Longitud (p. ej. 2.3522)', - 'places.formCategory': 'Categoría', - 'places.noCategory': 'Sin categoría', - 'places.categoryNamePlaceholder': 'Nombre de la categoría', - 'places.formTime': 'Hora', - 'places.startTime': 'Inicio', - 'places.endTime': 'Fin', - 'places.endTimeBeforeStart': 'La hora de fin es anterior a la de inicio', - 'places.timeCollision': 'Solapamiento horario con:', - 'places.formWebsite': 'Página web', - 'places.formNotes': 'Notas', - 'places.formNotesPlaceholder': 'Notas personales...', - 'places.formReservation': 'Reserva', - 'places.reservationNotesPlaceholder': 'Notas de reserva, número de confirmación...', - 'places.mapsSearchPlaceholder': 'Buscar lugares...', - 'places.mapsSearchError': 'La búsqueda de lugares falló.', - 'places.loadingDetails': 'Cargando detalles del lugar…', - 'places.osmHint': 'Usando búsqueda con OpenStreetMap (sin fotos, horarios ni valoraciones). Añade una clave API de Google en Ajustes para obtener todos los detalles.', - 'places.osmActive': 'Búsqueda mediante OpenStreetMap (sin fotos, valoraciones ni horarios). Añade una clave API de Google en Ajustes para datos ampliados.', - 'places.categoryCreateError': 'No se pudo crear la categoría', - 'places.nameRequired': 'Introduce un nombre', - 'places.saveError': 'No se pudo guardar', - - // Place Inspector - 'inspector.opened': 'Abierto', - 'inspector.closed': 'Cerrado', - 'inspector.openingHours': 'Horario de apertura', - 'inspector.showHours': 'Mostrar horario', - 'inspector.files': 'Archivos', - 'inspector.filesCount': '{count} archivos', - 'inspector.removeFromDay': 'Quitar del día', - 'inspector.remove': 'Eliminar', - 'inspector.addToDay': 'Añadir al día', - 'inspector.confirmedRes': 'Reserva confirmada', - 'inspector.pendingRes': 'Reserva pendiente', - 'inspector.google': 'Abrir en Google Maps', - 'inspector.website': 'Abrir la web', - 'inspector.addRes': 'Reserva', - 'inspector.editRes': 'Editar reserva', - 'inspector.participants': 'Participantes', - 'inspector.trackStats': 'Datos de la ruta', - - // Reservations - 'reservations.title': 'Reservas', - 'reservations.empty': 'Aún no hay reservas', - 'reservations.emptyHint': 'Añade reservas de vuelos, hoteles y más', - 'reservations.add': 'Añadir reserva', - 'reservations.addManual': 'Reserva manual', - 'reservations.placeHint': 'Consejo: es mejor crear las reservas directamente desde un lugar para vincularlas con el plan del día.', - 'reservations.confirmed': 'Confirmada', - 'reservations.pending': 'Pendiente', - 'reservations.summary': '{confirmed} confirmadas, {pending} pendientes', - 'reservations.fromPlan': 'Del plan', - 'reservations.showFiles': 'Mostrar archivos', - 'reservations.editTitle': 'Editar reserva', - 'reservations.status': 'Estado', - 'reservations.datetime': 'Fecha y hora', - 'reservations.startTime': 'Hora de inicio', - 'reservations.endTime': 'Hora de fin', - 'reservations.date': 'Fecha', - 'reservations.time': 'Hora', - 'reservations.timeAlt': 'Hora (alternativa, p. ej. 19:30)', - 'reservations.notes': 'Notas', - 'reservations.notesPlaceholder': 'Notas adicionales...', - 'reservations.type.flight': 'Vuelo', - 'reservations.type.hotel': 'Alojamiento', - 'reservations.type.restaurant': 'Restaurante', - 'reservations.type.train': 'Tren', - 'reservations.type.car': 'Coche', - 'reservations.type.cruise': 'Crucero', - 'reservations.type.event': 'Evento', - 'reservations.type.tour': 'Excursión', - 'reservations.type.other': 'Otro', - 'reservations.confirm.delete': '¿Seguro que quieres eliminar la reserva "{name}"?', - 'reservations.confirm.deleteTitle': '¿Eliminar reserva?', - 'reservations.confirm.deleteBody': '« {name} » se eliminará permanentemente.', - 'reservations.toast.updated': 'Reserva actualizada', - 'reservations.toast.removed': 'Reserva eliminada', - 'reservations.toast.fileUploaded': 'Archivo subido', - 'reservations.toast.uploadError': 'No se pudo subir', - 'reservations.newTitle': 'Nueva reserva', - 'reservations.bookingType': 'Tipo de reserva', - 'reservations.titleLabel': 'Título', - 'reservations.titlePlaceholder': 'p. ej. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Ubicación / dirección', - 'reservations.locationPlaceholder': 'Dirección, aeropuerto, hotel...', - 'reservations.confirmationCode': 'Código de reserva', - 'reservations.confirmationPlaceholder': 'p. ej. ABC12345', - 'reservations.day': 'Día', - 'reservations.noDay': 'Sin día', - 'reservations.place': 'Lugar', - 'reservations.noPlace': 'Sin lugar', - 'reservations.pendingSave': 'se guardará…', - 'reservations.uploading': 'Subiendo...', - 'reservations.attachFile': 'Adjuntar archivo', - 'reservations.linkExisting': 'Vincular archivo existente', - 'reservations.toast.saveError': 'No se pudo guardar', - 'reservations.toast.updateError': 'No se pudo actualizar', - 'reservations.toast.deleteError': 'No se pudo eliminar', - 'reservations.confirm.remove': '¿Eliminar la reserva de "{name}"?', - 'reservations.linkAssignment': 'Vincular a una asignación del día', - 'reservations.pickAssignment': 'Selecciona una asignación de tu plan...', - 'reservations.noAssignment': 'Sin vínculo (independiente)', - 'reservations.price': 'Precio', - 'reservations.budgetCategory': 'Categoría de presupuesto', - 'reservations.budgetCategoryPlaceholder': 'ej. Transporte, Alojamiento', - 'reservations.budgetCategoryAuto': 'Automático (según tipo de reserva)', - 'reservations.budgetHint': 'Se creará automáticamente una entrada presupuestaria al guardar.', - 'reservations.departureDate': 'Salida', - 'reservations.arrivalDate': 'Llegada', - 'reservations.departureTime': 'Hora salida', - 'reservations.arrivalTime': 'Hora llegada', - 'reservations.pickupDate': 'Recogida', - 'reservations.returnDate': 'Devolución', - 'reservations.pickupTime': 'Hora recogida', - 'reservations.returnTime': 'Hora devolución', - 'reservations.endDate': 'Fecha fin', - 'reservations.meta.departureTimezone': 'TZ salida', - 'reservations.meta.arrivalTimezone': 'TZ llegada', - 'reservations.span.departure': 'Salida', - 'reservations.span.arrival': 'Llegada', - 'reservations.span.inTransit': 'En tránsito', - 'reservations.span.pickup': 'Recogida', - 'reservations.span.return': 'Devolución', - 'reservations.span.active': 'Activo', - 'reservations.span.start': 'Inicio', - 'reservations.span.end': 'Fin', - 'reservations.span.ongoing': 'En curso', - 'reservations.validation.endBeforeStart': 'La fecha/hora de fin debe ser posterior a la de inicio', - 'reservations.addBooking': 'Añadir reserva', - - // Budget - 'budget.title': 'Presupuesto', - 'budget.exportCsv': 'Exportar CSV', - 'budget.emptyTitle': 'Aún no se ha creado ningún presupuesto', - 'budget.emptyText': 'Crea categorías y entradas para planificar el presupuesto de tu viaje', - 'budget.emptyPlaceholder': 'Introduce el nombre de la categoría...', - 'budget.createCategory': 'Crear categoría', - 'budget.category': 'Categoría', - 'budget.categoryName': 'Nombre de la categoría', - 'budget.table.name': 'Nombre', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Personas', - 'budget.table.days': 'Días', - 'budget.table.perPerson': 'Por persona', - 'budget.table.perDay': 'Por día', - 'budget.table.perPersonDay': 'Por pers. / día', - 'budget.table.note': 'Nota', - 'budget.table.date': 'Fecha', - 'budget.newEntry': 'Nueva entrada', - 'budget.defaultEntry': 'Nueva entrada', - 'budget.defaultCategory': 'Nueva categoría', - 'budget.total': 'Total', - 'budget.totalBudget': 'Presupuesto total', - 'budget.byCategory': 'Por categoría', - 'budget.editTooltip': 'Haz clic para editar', - 'budget.linkedToReservation': 'Vinculado a una reserva — edite el nombre allí', - 'budget.confirm.deleteCategory': '¿Seguro que quieres eliminar la categoría "{name}" con {count} entradas?', - 'budget.deleteCategory': 'Eliminar categoría', - 'budget.perPerson': 'Por persona', - 'budget.paid': 'Pagado', - 'budget.open': 'Abrir', - 'budget.noMembers': 'No hay miembros asignados', - 'budget.settlement': 'Liquidación', - 'budget.settlementInfo': 'Haz clic en el avatar de un miembro en una partida del presupuesto para marcarlo en verde — esto significa que ha pagado. La liquidación muestra quién debe cuánto a quién.', - 'budget.netBalances': 'Saldos netos', - - // Files - 'files.title': 'Archivos', - 'files.pageTitle': 'Archivos y documentos', - 'files.subtitle': '{count} archivos para {trip}', - 'files.download': 'Descargar', - 'files.openError': 'No se pudo abrir el archivo', - 'files.downloadPdf': 'Descargar PDF', - 'files.count': '{count} archivos', - 'files.countSingular': '1 archivo', - 'files.uploaded': '{count} archivos subidos', - 'files.uploadError': 'La subida falló', - 'files.dropzone': 'Arrastra aquí los archivos', - 'files.dropzoneHint': 'o haz clic para explorar', - 'files.allowedTypes': 'Imágenes, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Máx. 50 MB', - 'files.uploading': 'Subiendo...', - 'files.filterAll': 'Todo', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Imágenes', - 'files.filterDocs': 'Documentos', - 'files.filterCollab': 'Notas de colaboración', - 'files.sourceCollab': 'Desde notas de colaboración', - 'files.empty': 'Aún no hay archivos', - 'files.emptyHint': 'Sube archivos para adjuntarlos a tu viaje', - 'files.openTab': 'Abrir en una pestaña nueva', - 'files.confirm.delete': '¿Seguro que quieres eliminar este archivo?', - 'files.toast.deleted': 'Archivo eliminado', - 'files.toast.deleteError': 'No se pudo eliminar el archivo', - 'files.sourcePlan': 'Plan diario', - 'files.sourceBooking': 'Reserva', - 'files.sourceTransport': 'Transporte', - 'files.attach': 'Adjuntar', - 'files.pasteHint': 'También puedes pegar imágenes desde el portapapeles (Ctrl+V)', - - // Packing - 'packing.title': 'Lista de equipaje', - 'packing.empty': 'La lista de equipaje está vacía', - 'packing.import': 'Importar', - 'packing.importTitle': 'Importar lista de equipaje', - 'packing.importHint': 'Un elemento por línea. Categoría y cantidad opcionales separadas por coma, punto y coma o tabulación: Nombre, Categoría, Cantidad', - 'packing.importPlaceholder': 'Cepillo de dientes\nProtector solar, Higiene\nCamisetas, Ropa, 5\nPasaporte, Documentos', - 'packing.importCsv': 'Cargar CSV/TXT', - 'packing.importAction': 'Importar {count}', - 'packing.importSuccess': '{count} elementos importados', - 'packing.importError': 'Error al importar', - 'packing.importEmpty': 'Sin elementos para importar', - 'packing.progress': '{packed} de {total} preparados ({percent}%)', - 'packing.clearChecked': 'Eliminar {count} marcados', - 'packing.clearCheckedShort': 'Eliminar {count}', - 'packing.suggestions': 'Sugerencias', - 'packing.suggestionsTitle': 'Añadir sugerencias', - 'packing.allSuggested': 'Todas las sugerencias añadidas', - 'packing.allPacked': '¡Todo preparado!', - 'packing.addPlaceholder': 'Añadir nuevo elemento...', - 'packing.categoryPlaceholder': 'Categoría...', - 'packing.filterAll': 'Todo', - 'packing.filterOpen': 'Pendientes', - 'packing.filterDone': 'Hecho', - 'packing.emptyTitle': 'La lista de equipaje está vacía', - 'packing.emptyHint': 'Añade elementos o usa las sugerencias', - 'packing.emptyFiltered': 'Ningún elemento coincide con este filtro', - 'packing.menuRename': 'Renombrar', - 'packing.menuCheckAll': 'Marcar todo', - 'packing.menuUncheckAll': 'Desmarcar todo', - 'packing.menuDeleteCat': 'Eliminar categoría', - 'packing.addItem': 'Añadir artículo', - 'packing.addItemPlaceholder': 'Nombre del artículo...', - 'packing.addCategory': 'Añadir categoría', - 'packing.newCategoryPlaceholder': 'Nombre de categoría (ej. Ropa)', - 'packing.applyTemplate': 'Aplicar plantilla', - 'packing.template': 'Plantilla', - 'packing.templateApplied': '{count} artículos añadidos desde plantilla', - 'packing.templateError': 'Error al aplicar plantilla', - 'packing.saveAsTemplate': 'Guardar como plantilla', - 'packing.templateName': 'Nombre de la plantilla', - 'packing.templateSaved': 'Lista de equipaje guardada como plantilla', - 'packing.noMembers': 'Sin miembros', - 'packing.bags': 'Equipaje', - 'packing.noBag': 'Sin asignar', - 'packing.totalWeight': 'Peso total', - 'packing.bagName': 'Nombre...', - 'packing.addBag': 'Añadir equipaje', - 'packing.changeCategory': 'Cambiar categoría', - 'packing.confirm.clearChecked': '¿Seguro que quieres eliminar {count} elementos marcados?', - 'packing.confirm.deleteCat': '¿Seguro que quieres eliminar la categoría "{name}" con {count} elementos?', - 'packing.defaultCategory': 'Otros', - 'packing.toast.saveError': 'No se pudo guardar', - 'packing.toast.deleteError': 'No se pudo eliminar', - 'packing.toast.renameError': 'No se pudo renombrar', - 'packing.toast.addError': 'No se pudo añadir', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Pasaporte', category: 'Documentos' }, - { name: 'Documento de identidad', category: 'Documentos' }, - { name: 'Seguro de viaje', category: 'Documentos' }, - { name: 'Billetes de vuelo', category: 'Documentos' }, - { name: 'Tarjeta de crédito', category: 'Finanzas' }, - { name: 'Efectivo', category: 'Finanzas' }, - { name: 'Visado', category: 'Documentos' }, - { name: 'Camisetas', category: 'Ropa' }, - { name: 'Pantalones', category: 'Ropa' }, - { name: 'Ropa interior', category: 'Ropa' }, - { name: 'Calcetines', category: 'Ropa' }, - { name: 'Chaqueta', category: 'Ropa' }, - { name: 'Pijama', category: 'Ropa' }, - { name: 'Ropa de baño', category: 'Ropa' }, - { name: 'Impermeable', category: 'Ropa' }, - { name: 'Zapatos cómodos', category: 'Ropa' }, - { name: 'Cepillo de dientes', category: 'Aseo' }, - { name: 'Pasta de dientes', category: 'Aseo' }, - { name: 'Champú', category: 'Aseo' }, - { name: 'Desodorante', category: 'Aseo' }, - { name: 'Protector solar', category: 'Aseo' }, - { name: 'Maquinilla de afeitar', category: 'Aseo' }, - { name: 'Cargador', category: 'Electrónica' }, - { name: 'Batería externa', category: 'Electrónica' }, - { name: 'Auriculares', category: 'Electrónica' }, - { name: 'Adaptador de viaje', category: 'Electrónica' }, - { name: 'Cámara', category: 'Electrónica' }, - { name: 'Analgésicos', category: 'Salud' }, - { name: 'Tiritas', category: 'Salud' }, - { name: 'Desinfectante', category: 'Salud' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Compartir viaje', - 'members.inviteUser': 'Invitar usuario', - 'members.selectUser': 'Seleccionar usuario…', - 'members.invite': 'Invitar', - 'members.allHaveAccess': 'Todos los usuarios ya tienen acceso.', - 'members.access': 'Acceso', - 'members.person': 'persona', - 'members.persons': 'personas', - 'members.you': 'tú', - 'members.owner': 'Propietario', - 'members.leaveTrip': 'Abandonar viaje', - 'members.removeAccess': 'Quitar acceso', - 'members.confirmLeave': '¿Abandonar el viaje? Perderás el acceso.', - 'members.confirmRemove': '¿Quitar el acceso de este usuario?', - 'members.loadError': 'No se pudieron cargar los miembros', - 'members.added': 'añadido', - 'members.addError': 'No se pudo añadir', - 'members.removed': 'Miembro eliminado', - 'members.removeError': 'No se pudo eliminar', - - // Categories (Admin) - 'categories.title': 'Categorías', - 'categories.subtitle': 'Gestiona categorías para lugares', - 'categories.new': 'Nueva categoría', - 'categories.empty': 'Aún no hay categorías', - 'categories.namePlaceholder': 'Nombre de la categoría', - 'categories.icon': 'Icono', - 'categories.color': 'Color', - 'categories.customColor': 'Elegir color personalizado', - 'categories.preview': 'Vista previa', - 'categories.defaultName': 'Categoría', - 'categories.update': 'Actualizar', - 'categories.create': 'Crear', - 'categories.confirm.delete': '¿Eliminar la categoría? Los lugares de esta categoría no se eliminarán.', - 'categories.toast.loadError': 'No se pudieron cargar las categorías', - 'categories.toast.nameRequired': 'Introduce un nombre', - 'categories.toast.updated': 'Categoría actualizada', - 'categories.toast.created': 'Categoría creada', - 'categories.toast.saveError': 'No se pudo guardar', - 'categories.toast.deleted': 'Categoría eliminada', - 'categories.toast.deleteError': 'No se pudo eliminar', - - // Backup (Admin) - 'backup.title': 'Copia de seguridad de datos', - 'backup.subtitle': 'Base de datos y todos los archivos subidos', - 'backup.refresh': 'Actualizar', - 'backup.upload': 'Subir copia de seguridad', - 'backup.uploading': 'Subiendo…', - 'backup.create': 'Crear copia', - 'backup.creating': 'Creando…', - 'backup.empty': 'Aún no hay copias', - 'backup.createFirst': 'Crear la primera copia', - 'backup.download': 'Descargar', - 'backup.restore': 'Restaurar', - 'backup.confirm.restore': '¿Restaurar la copia "{name}"?\n\nTodos los datos actuales serán reemplazados por la copia.', - 'backup.confirm.uploadRestore': '¿Subir y restaurar el archivo de copia "{name}"?\n\nTodos los datos actuales se sobrescribirán.', - 'backup.confirm.delete': '¿Eliminar la copia "{name}"?', - 'backup.toast.loadError': 'No se pudieron cargar las copias', - 'backup.toast.created': 'Copia de seguridad creada correctamente', - 'backup.toast.createError': 'No se pudo crear la copia', - 'backup.toast.restored': 'Copia restaurada. La página se recargará…', - 'backup.toast.restoreError': 'No se pudo restaurar', - 'backup.toast.uploadError': 'No se pudo subir', - 'backup.toast.deleted': 'Copia eliminada', - 'backup.toast.deleteError': 'No se pudo eliminar', - 'backup.toast.downloadError': 'La descarga falló', - 'backup.toast.settingsSaved': 'Ajustes de copia automática guardados', - 'backup.toast.settingsError': 'No se pudieron guardar los ajustes', - 'backup.auto.title': 'Copia automática', - 'backup.auto.subtitle': 'Copia de seguridad automática según una programación', - 'backup.auto.enable': 'Activar copia automática', - 'backup.auto.enableHint': 'Se crearán copias automáticamente según la frecuencia elegida', - 'backup.auto.interval': 'Intervalo', - 'backup.auto.hour': 'Ejecutar a la hora', - 'backup.auto.hourHint': 'Hora local del servidor (formato {format})', - 'backup.auto.dayOfWeek': 'Día de la semana', - 'backup.auto.dayOfMonth': 'Día del mes', - 'backup.auto.dayOfMonthHint': 'Limitado a 1–28 para compatibilidad con todos los meses', - 'backup.auto.scheduleSummary': 'Programación', - 'backup.auto.summaryDaily': 'Todos los días a las {hour}:00', - 'backup.auto.summaryWeekly': 'Cada {day} a las {hour}:00', - 'backup.auto.summaryMonthly': 'El día {day} de cada mes a las {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'La copia automática está configurada mediante variables de entorno Docker. Para cambiar estos ajustes, actualiza tu docker-compose.yml y reinicia el contenedor.', - 'backup.auto.copyEnv': 'Copiar variables de entorno Docker', - 'backup.auto.envCopied': 'Variables de entorno Docker copiadas al portapapeles', - 'backup.auto.keepLabel': 'Eliminar copias antiguas después de', - 'backup.dow.sunday': 'Dom', - 'backup.dow.monday': 'Lun', - 'backup.dow.tuesday': 'Mar', - 'backup.dow.wednesday': 'Mié', - 'backup.dow.thursday': 'Jue', - 'backup.dow.friday': 'Vie', - 'backup.dow.saturday': 'Sáb', - 'backup.interval.hourly': 'Cada hora', - 'backup.interval.daily': 'Diaria', - 'backup.interval.weekly': 'Semanal', - 'backup.interval.monthly': 'Mensual', - 'backup.keep.1day': '1 día', - 'backup.keep.3days': '3 días', - 'backup.keep.7days': '7 días', - 'backup.keep.14days': '14 días', - 'backup.keep.30days': '30 días', - 'backup.keep.forever': 'Conservar para siempre', - - // Photos - 'photos.title': 'Fotos', - 'photos.subtitle': '{count} fotos para {trip}', - 'photos.dropHere': 'Suelta fotos aquí...', - 'photos.dropHereActive': 'Suelta fotos aquí', - 'photos.captionForAll': 'Leyenda (para todos)', - 'photos.captionPlaceholder': 'Leyenda opcional...', - 'photos.addCaption': 'Añadir leyenda...', - 'photos.allDays': 'Todos los días', - 'photos.noPhotos': 'Aún no hay fotos', - 'photos.uploadHint': 'Sube y organiza las fotos compartidas de este viaje', - 'photos.clickToSelect': 'o haz clic para seleccionar', - 'photos.linkPlace': 'Vincular lugar', - 'photos.noPlace': 'Sin lugar', - 'photos.uploadN': 'Subida de {n} foto(s)', - 'photos.linkDay': 'Vincular día', - 'photos.noDay': 'Ningún día', - 'photos.dayLabel': 'Día {number}', - 'photos.photoSelected': 'Foto seleccionada', - 'photos.photosSelected': 'Fotos seleccionadas', - 'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · hasta 30 fotos', - 'admin.addons.catalog.memories.name': 'Fotos (Immich)', - 'admin.addons.catalog.memories.description': 'Comparte fotos de viaje a través de tu instancia de Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Protocolo de contexto de modelo para integración con asistentes de IA', - 'admin.addons.catalog.packing.name': 'Listas', - 'admin.addons.catalog.packing.description': 'Listas de equipaje y tareas pendientes para tus viajes', - 'admin.addons.catalog.budget.name': 'Presupuesto', - 'admin.addons.catalog.budget.description': 'Controla los gastos y planifica el presupuesto del viaje', - 'admin.addons.catalog.documents.name': 'Documentos', - 'admin.addons.catalog.documents.description': 'Guarda y gestiona la documentación del viaje', - 'admin.addons.catalog.vacay.name': 'Vacaciones', - 'admin.addons.catalog.vacay.description': 'Planificador personal de vacaciones con vista de calendario', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Mapa del mundo con los países visitados y estadísticas de viaje', - 'admin.addons.catalog.collab.name': 'Colaboración', - 'admin.addons.catalog.collab.description': 'Notas, encuestas y chat en tiempo real para organizar el viaje', - - // Backup restore modal - 'backup.restoreConfirmTitle': '¿Restaurar copia?', - 'backup.restoreWarning': 'Todos los datos actuales (viajes, lugares, usuarios, subidas) serán reemplazados permanentemente por la copia. Esta acción no se puede deshacer.', - 'backup.restoreTip': 'Consejo: crea una copia del estado actual antes de restaurar.', - 'backup.restoreConfirm': 'Sí, restaurar', - - // PDF - 'pdf.travelPlan': 'Plan de viaje', - 'pdf.planned': 'Planificado', - 'pdf.costLabel': 'Coste EUR', - 'pdf.preview': 'Vista previa PDF', - 'pdf.saveAsPdf': 'Guardar como PDF', - - // Planner - 'planner.places': 'Lugares', - 'planner.bookings': 'Reservas', - 'planner.packingList': 'Lista de equipaje', - 'planner.documents': 'Documentos', - 'planner.dayPlan': 'Plan por días', - 'planner.reservations': 'Reservas', - 'planner.minTwoPlaces': 'Se necesitan al menos 2 lugares con coordenadas', - 'planner.noGeoPlaces': 'No hay lugares con coordenadas disponibles', - 'planner.routeCalculated': 'Ruta calculada', - 'planner.routeCalcFailed': 'No se pudo calcular la ruta', - 'planner.routeError': 'Error al calcular la ruta', - 'planner.icsExportFailed': 'Error al exportar ICS', - 'planner.routeOptimized': 'Ruta optimizada', - 'planner.reservationUpdated': 'Reserva actualizada', - 'planner.reservationAdded': 'Reserva añadida', - 'planner.confirmDeleteReservation': '¿Eliminar reserva?', - 'planner.reservationDeleted': 'Reserva eliminada', - 'planner.days': 'Días', - 'planner.allPlaces': 'Todos los lugares', - 'planner.totalPlaces': '{n} lugares en total', - 'planner.noDaysPlanned': 'Aún no hay días planificados', - 'planner.editTrip': 'Editar viaje →', - 'planner.placeOne': '1 lugar', - 'planner.placeN': '{n} lugares', - 'planner.addNote': 'Añadir nota', - 'planner.noEntries': 'No hay entradas para este día', - 'planner.addPlace': 'Añadir lugar/actividad', - 'planner.addPlaceShort': '+ Añadir lugar/actividad', - 'planner.resPending': 'Reserva pendiente · ', - 'planner.resConfirmed': 'Reserva confirmada · ', - 'planner.notePlaceholder': 'Nota…', - 'planner.noteTimePlaceholder': 'Hora (opcional)', - 'planner.noteExamplePlaceholder': 'p. ej. S3 a las 14:30 desde la estación central, ferry desde el muelle 7, pausa para comer…', - 'planner.totalCost': 'Coste total', - 'planner.searchPlaces': 'Buscar lugares…', - 'planner.allCategories': 'Todas las categorías', - 'planner.noPlacesFound': 'No se encontraron lugares', - 'planner.addFirstPlace': 'Añadir el primer lugar', - 'planner.noReservations': 'Sin reservas', - 'planner.addFirstReservation': 'Añadir la primera reserva', - 'planner.new': 'Nuevo', - 'planner.addToDay': '+ Día', - 'planner.calculating': 'Calculando…', - 'planner.route': 'Ruta', - 'planner.optimize': 'Optimizar', - 'planner.openGoogleMaps': 'Abrir en Google Maps', - 'planner.selectDayHint': 'Selecciona un día de la lista izquierda para ver su plan', - 'planner.noPlacesForDay': 'Aún no hay lugares para este día', - 'planner.addPlacesLink': 'Añadir lugares →', - 'planner.minTotal': 'min en total', - 'planner.noReservation': 'Sin reserva', - 'planner.removeFromDay': 'Quitar del día', - 'planner.addToThisDay': 'Añadir al día', - 'planner.overview': 'Vista general', - 'planner.noDays': 'No hay días todavía', - 'planner.editTripToAddDays': 'Edita el viaje para añadir días', - 'planner.dayCount': '{n} días', - 'planner.clickToUnlock': 'Haz clic para desbloquear', - 'planner.keepPosition': 'Mantener posición durante la optimización de ruta', - 'planner.dayDetails': 'Detalles del día', - 'planner.dayN': 'Día {n}', - // Dashboard Stats - 'stats.countries': 'Países', - 'stats.cities': 'Ciudades', - 'stats.trips': 'Viajes', - 'stats.places': 'Lugares', - 'stats.worldProgress': 'Progreso mundial', - 'stats.visited': 'visitados', - 'stats.remaining': 'restantes', - 'stats.visitedCountries': 'Países visitados', - - // Day Detail Panel - 'day.precipProb': 'Probabilidad de lluvia', - 'day.precipitation': 'Precipitación', - 'day.wind': 'Viento', - 'day.sunrise': 'Amanecer', - 'day.sunset': 'Atardecer', - 'day.hourlyForecast': 'Pronóstico por horas', - 'day.climateHint': 'Promedios históricos: el pronóstico real está disponible dentro de los 16 días previos a la fecha.', - 'day.noWeather': 'No hay datos meteorológicos disponibles. Añade un lugar con coordenadas.', - 'day.overview': 'Resumen diario', - 'day.accommodation': 'Alojamiento', - 'day.addAccommodation': 'Añadir alojamiento', - 'day.hotelDayRange': 'Aplicar a los días', - 'day.noPlacesForHotel': 'Añade primero lugares al viaje', - 'day.allDays': 'Todos', - 'day.checkIn': 'Registro de entrada', - 'day.checkInUntil': 'Hasta', - 'day.checkOut': 'Registro de salida', - 'day.confirmation': 'Confirmación', - 'day.editAccommodation': 'Editar alojamiento', - 'day.reservations': 'Reservas', - - // Memories / Immich - 'memories.title': 'Fotos', - 'memories.notConnected': 'Immich no conectado', - 'memories.notConnectedHint': 'Conecta tu instancia de Immich en Ajustes para ver tus fotos de viaje aquí.', - 'memories.notConnectedMultipleHint': 'Conecta alguno de estos proveedores de fotos: {provider_names} en Configuración para poder añadir fotos a este viaje.', - 'memories.noDates': 'Añade fechas a tu viaje para cargar fotos.', - 'memories.noPhotos': 'No se encontraron fotos', - 'memories.noPhotosHint': 'No se encontraron fotos en Immich para el rango de fechas de este viaje.', - 'memories.photosFound': 'fotos', - 'memories.fromOthers': 'de otros', - 'memories.sharePhotos': 'Compartir fotos', - 'memories.sharing': 'Compartiendo', - 'memories.reviewTitle': 'Revisar tus fotos', - 'memories.reviewHint': 'Haz clic en las fotos para excluirlas de compartir.', - 'memories.shareCount': 'Compartir {count} fotos', - 'memories.providerUrl': 'URL del servidor', - 'memories.providerApiKey': 'Clave API', - 'memories.providerUsername': 'Nombre de usuario', - 'memories.providerPassword': 'Contraseña', - 'memories.providerOTP': 'Código MFA (si está habilitado)', - 'memories.skipSSLVerification': 'Omitir verificación del certificado SSL', - 'memories.immichAutoUpload': 'Duplicar las fotos del journey en Immich al subirlas', - 'memories.providerUrlHintSynology': 'Incluye la ruta de la aplicación Photos en la URL, p.ej. https://nas:5001/photo', - 'memories.testConnection': 'Probar conexión', - 'memories.testShort': 'Probar', - 'memories.testFirst': 'Probar conexión primero', - 'memories.connected': 'Conectado', - 'memories.disconnected': 'No conectado', - 'memories.connectionSuccess': 'Conectado a Immich', - 'memories.connectionError': 'No se pudo conectar a Immich', - 'memories.saved': 'Configuración de {provider_name} guardada', - 'memories.providerDisconnectedBanner': 'Se perdió la conexión con {provider_name}. Vuelve a conectar en Configuración para ver las fotos.', - 'memories.saveError': 'No se pudieron guardar los ajustes de {provider_name}', - 'memories.saveRouteNotConfigured': 'La ruta de guardado no está configurada para este proveedor', - 'memories.testRouteNotConfigured': 'La ruta de prueba no está configurada para este proveedor', - 'memories.fillRequiredFields': 'Por favor complete todos los campos requeridos', - 'memories.oldest': 'Más antiguas', - 'memories.newest': 'Más recientes', - 'memories.allLocations': 'Todas las ubicaciones', - 'memories.addPhotos': 'Añadir fotos', - 'memories.linkAlbum': 'Vincular álbum', - 'memories.selectAlbum': 'Seleccionar álbum de Immich', - 'memories.selectAlbumMultiple': 'Seleccionar álbum', - 'memories.noAlbums': 'No se encontraron álbumes', - 'memories.syncAlbum': 'Sincronizar álbum', - 'memories.unlinkAlbum': 'Desvincular', - 'memories.photos': 'fotos', - 'memories.selectPhotos': 'Seleccionar fotos de Immich', - 'memories.selectPhotosMultiple': 'Seleccionar fotos', - 'memories.selectHint': 'Toca las fotos para seleccionarlas.', - 'memories.selected': 'seleccionado(s)', - 'memories.addSelected': 'Añadir {count} fotos', - 'memories.alreadyAdded': 'Añadido', - 'memories.private': 'Privado', - 'memories.stopSharing': 'Dejar de compartir', - 'memories.tripDates': 'Fechas del viaje', - 'memories.allPhotos': 'Todas las fotos', - 'memories.confirmShareTitle': '¿Compartir con los miembros del viaje?', - 'memories.confirmShareHint': '{count} fotos serán visibles para todos los miembros de este viaje. Puedes hacer fotos individuales privadas más tarde.', - 'memories.confirmShareButton': 'Compartir fotos', - - // Collab Addon - 'collab.tabs.chat': 'Mensajes', - 'collab.tabs.notes': 'Notas', - 'collab.tabs.polls': 'Encuestas', - 'collab.whatsNext.title': 'Qué viene ahora', - 'collab.whatsNext.today': 'Hoy', - 'collab.whatsNext.tomorrow': 'Mañana', - 'collab.whatsNext.empty': 'No hay actividades próximas', - 'collab.whatsNext.until': 'hasta', - 'collab.whatsNext.emptyHint': 'Las actividades con hora aparecerán aquí', - 'collab.chat.send': 'Enviar', - 'collab.chat.placeholder': 'Escribe un mensaje...', - 'collab.chat.empty': 'Empieza la conversación', - 'collab.chat.emptyHint': 'Los mensajes se comparten con todos los miembros del viaje', - 'collab.chat.emptyDesc': 'Comparte ideas, planes y novedades con tu grupo de viaje', - 'collab.chat.today': 'Hoy', - 'collab.chat.yesterday': 'Ayer', - 'collab.chat.deletedMessage': 'eliminó un mensaje', - 'collab.chat.reply': 'Responder', - 'collab.chat.loadMore': 'Cargar mensajes anteriores', - 'collab.chat.justNow': 'justo ahora', - 'collab.chat.minutesAgo': 'hace {n} min', - 'collab.chat.hoursAgo': 'hace {n} h', - 'collab.notes.title': 'Notas', - 'collab.notes.new': 'Nueva nota', - 'collab.notes.empty': 'Aún no hay notas', - 'collab.notes.emptyHint': 'Empieza a capturar ideas y planes', - 'collab.notes.all': 'Todas', - 'collab.notes.titlePlaceholder': 'Título de la nota', - 'collab.notes.contentPlaceholder': 'Escribe algo...', - 'collab.notes.categoryPlaceholder': 'Categoría', - 'collab.notes.newCategory': 'Nueva categoría...', - 'collab.notes.category': 'Categoría', - 'collab.notes.noCategory': 'Sin categoría', - 'collab.notes.color': 'Color', - 'collab.notes.save': 'Guardar', - 'collab.notes.cancel': 'Cancelar', - 'collab.notes.edit': 'Editar', - 'collab.notes.delete': 'Eliminar', - 'collab.notes.pin': 'Fijar', - 'collab.notes.unpin': 'Desfijar', - 'collab.notes.daysAgo': 'hace {n} d', - 'collab.notes.categorySettings': 'Gestionar categorías', - 'collab.notes.create': 'Crear', - 'collab.notes.website': 'Sitio web', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Adjuntar archivos', - 'collab.notes.noCategoriesYet': 'Aún no hay categorías', - 'collab.notes.emptyDesc': 'Crea una nota para empezar', - 'collab.polls.title': 'Encuestas', - 'collab.polls.new': 'Nueva encuesta', - 'collab.polls.empty': 'Aún no hay encuestas', - 'collab.polls.emptyHint': 'Pregunta al grupo y votad juntos', - 'collab.polls.question': 'Pregunta', - 'collab.polls.questionPlaceholder': '¿Qué deberíamos hacer?', - 'collab.polls.addOption': '+ Añadir opción', - 'collab.polls.optionPlaceholder': 'Opción {n}', - 'collab.polls.create': 'Crear encuesta', - 'collab.polls.close': 'Cerrar', - 'collab.polls.closed': 'Cerrada', - 'collab.polls.votes': '{n} votos', - 'collab.polls.vote': '{n} voto', - 'collab.polls.multipleChoice': 'Selección múltiple', - 'collab.polls.multiChoice': 'Selección múltiple', - 'collab.polls.deadline': 'Fecha límite', - 'collab.polls.option': 'Opción', - 'collab.polls.options': 'Opciones', - 'collab.polls.delete': 'Eliminar', - 'collab.polls.closedSection': 'Cerradas', - - // Files management (2.6.2) - 'files.trash': 'Papelera', - 'files.trashEmpty': 'La papelera está vacía', - 'files.emptyTrash': 'Vaciar papelera', - 'files.restore': 'Restaurar', - 'files.star': 'Destacar', - 'files.unstar': 'Quitar destacado', - 'files.assign': 'Asignar', - 'files.assignTitle': 'Asignar archivo', - 'files.assignPlace': 'Lugar', - 'files.assignBooking': 'Reserva', - 'files.assignTransport': 'Transporte', - 'files.unassigned': 'Sin asignar', - 'files.unlink': 'Eliminar vínculo', - 'files.noteLabel': 'Nota', - 'files.notePlaceholder': 'Añadir una nota...', - 'files.toast.trashed': 'Movido a la papelera', - 'files.toast.restored': 'Archivo restaurado', - 'files.toast.trashEmptied': 'Papelera vaciada', - 'files.toast.assigned': 'Archivo asignado', - 'files.toast.assignError': 'Error al asignar', - 'files.toast.restoreError': 'Error al restaurar', - 'files.confirm.permanentDelete': 'Eliminar este archivo permanentemente? No se puede deshacer.', - 'files.confirm.emptyTrash': 'Eliminar todos los archivos de la papelera? No se puede deshacer.', - - // Reservation metadata (2.6.2) - 'reservations.meta.airline': 'Aerolínea', - 'reservations.meta.flightNumber': 'N° de vuelo', - 'reservations.meta.from': 'Desde', - 'reservations.meta.to': 'Hasta', - 'reservations.needsReview': 'Revisar', - 'reservations.needsReviewHint': 'No se pudo identificar el aeropuerto automáticamente — por favor confirma la ubicación.', - 'reservations.searchLocation': 'Buscar estación, puerto, dirección...', - 'airport.searchPlaceholder': 'Código o ciudad del aeropuerto (ej. FRA)', - 'map.connections': 'Conexiones', - 'map.showConnections': 'Mostrar rutas de reservas', - 'map.hideConnections': 'Ocultar rutas de reservas', - 'settings.bookingLabels': 'Etiquetas de rutas de reservas', - 'settings.bookingLabelsHint': 'Muestra nombres de estaciones / aeropuertos en el mapa. Desactivado, solo se muestra el icono.', - 'reservations.meta.trainNumber': 'N° de tren', - 'reservations.meta.platform': 'Andén', - 'reservations.meta.seat': 'Asiento', - 'reservations.meta.checkIn': 'Registro de entrada', - 'reservations.meta.checkInUntil': 'Check-in hasta', - 'reservations.meta.checkOut': 'Registro de salida', - 'reservations.meta.linkAccommodation': 'Alojamiento', - 'reservations.meta.pickAccommodation': 'Vincular con alojamiento', - 'reservations.meta.noAccommodation': 'Ninguno', - 'reservations.meta.hotelPlace': 'Alojamiento', - 'reservations.meta.pickHotel': 'Seleccionar alojamiento', - 'reservations.meta.fromDay': 'Desde', - 'reservations.meta.toDay': 'Hasta', - 'reservations.meta.selectDay': 'Seleccionar día', - - // OIDC-only mode (2.6.2) - 'admin.oidcOnlyMode': 'Desactivar autenticación por contraseña', - 'admin.oidcOnlyModeHint': 'Si está activado, solo se permite el inicio de sesión con SSO. El inicio de sesión y registro con contraseña se bloquean.', - 'login.oidcOnly': 'La autenticación por contraseña está desactivada. Por favor, inicia sesión con tu proveedor SSO.', - 'login.oidcLoggedOut': 'Has cerrado sesión. Vuelve a iniciar sesión con tu proveedor SSO.', - - // Settings (2.6.2) - 'settings.currentPasswordRequired': 'La contraseña actual es obligatoria', - 'settings.passwordWeak': 'La contraseña debe contener mayúsculas, minúsculas, números y un carácter especial', - - // Permissions - 'admin.tabs.permissions': 'Permisos', - 'perm.title': 'Configuración de permisos', - 'perm.subtitle': 'Controla quién puede realizar acciones en la aplicación', - 'perm.saved': 'Configuración de permisos guardada', - 'perm.resetDefaults': 'Restablecer valores predeterminados', - 'perm.customized': 'personalizado', - 'perm.level.admin': 'Solo administrador', - 'perm.level.tripOwner': 'Propietario del viaje', - 'perm.level.tripMember': 'Miembros del viaje', - 'perm.level.everybody': 'Todos', - 'perm.cat.trip': 'Gestión de viajes', - 'perm.cat.members': 'Gestión de miembros', - 'perm.cat.files': 'Archivos', - 'perm.cat.content': 'Contenido y horario', - 'perm.cat.extras': 'Presupuesto, equipaje y colaboración', - 'perm.action.trip_create': 'Crear viajes', - 'perm.action.trip_edit': 'Editar detalles del viaje', - 'perm.action.trip_delete': 'Eliminar viajes', - 'perm.action.trip_archive': 'Archivar / desarchivar viajes', - 'perm.action.trip_cover_upload': 'Subir imagen de portada', - 'perm.action.member_manage': 'Añadir / eliminar miembros', - 'perm.action.file_upload': 'Subir archivos', - 'perm.action.file_edit': 'Editar metadatos del archivo', - 'perm.action.file_delete': 'Eliminar archivos', - 'perm.action.place_edit': 'Añadir / editar / eliminar lugares', - 'perm.action.day_edit': 'Editar días, notas y asignaciones', - 'perm.action.reservation_edit': 'Gestionar reservas', - 'perm.action.budget_edit': 'Gestionar presupuesto', - 'perm.action.packing_edit': 'Gestionar listas de equipaje', - 'perm.action.collab_edit': 'Colaboración (notas, encuestas, chat)', - 'perm.action.share_manage': 'Gestionar enlaces compartidos', - 'perm.actionHint.trip_create': 'Quién puede crear nuevos viajes', - 'perm.actionHint.trip_edit': 'Quién puede cambiar el nombre, fechas, descripción y moneda del viaje', - 'perm.actionHint.trip_delete': 'Quién puede eliminar permanentemente un viaje', - 'perm.actionHint.trip_archive': 'Quién puede archivar o desarchivar un viaje', - 'perm.actionHint.trip_cover_upload': 'Quién puede subir o cambiar la imagen de portada', - 'perm.actionHint.member_manage': 'Quién puede invitar o eliminar miembros del viaje', - 'perm.actionHint.file_upload': 'Quién puede subir archivos a un viaje', - 'perm.actionHint.file_edit': 'Quién puede editar descripciones y enlaces de archivos', - 'perm.actionHint.file_delete': 'Quién puede mover archivos a la papelera o eliminarlos permanentemente', - 'perm.actionHint.place_edit': 'Quién puede añadir, editar o eliminar lugares', - 'perm.actionHint.day_edit': 'Quién puede editar días, notas de días y asignaciones de lugares', - 'perm.actionHint.reservation_edit': 'Quién puede crear, editar o eliminar reservas', - 'perm.actionHint.budget_edit': 'Quién puede crear, editar o eliminar partidas del presupuesto', - 'perm.actionHint.packing_edit': 'Quién puede gestionar artículos de equipaje y bolsas', - 'perm.actionHint.collab_edit': 'Quién puede crear notas, encuestas y enviar mensajes', - 'perm.actionHint.share_manage': 'Quién puede crear o eliminar enlaces compartidos públicos', - // Undo - 'undo.button': 'Deshacer', - 'undo.tooltip': 'Deshacer: {action}', - 'undo.assignPlace': 'Lugar asignado al día', - 'undo.removeAssignment': 'Lugar eliminado del día', - 'undo.reorder': 'Lugares reordenados', - 'undo.optimize': 'Ruta optimizada', - 'undo.deletePlace': 'Lugar eliminado', - 'undo.deletePlaces': 'Lugares eliminados', - 'undo.moveDay': 'Lugar movido a otro día', - 'undo.lock': 'Bloqueo de lugar activado/desactivado', - 'undo.importGpx': 'Importación GPX', - 'undo.importKeyholeMarkup': 'Importación KMZ/KML', - 'undo.importGoogleList': 'Importación de Google Maps', - 'undo.importNaverList': 'Importación de Naver Maps', - - // Notifications - 'notifications.title': 'Notificaciones', - 'notifications.markAllRead': 'Marcar todo como leído', - 'notifications.deleteAll': 'Eliminar todo', - 'notifications.showAll': 'Ver todas las notificaciones', - 'notifications.empty': 'Sin notificaciones', - 'notifications.emptyDescription': '¡Estás al día!', - 'notifications.all': 'Todas', - 'notifications.unreadOnly': 'No leídas', - 'notifications.markRead': 'Marcar como leída', - 'notifications.markUnread': 'Marcar como no leída', - 'notifications.delete': 'Eliminar', - 'notifications.system': 'Sistema', - 'notifications.synologySessionCleared.title': 'Synology Photos desconectado', - 'notifications.synologySessionCleared.text': 'Tu servidor o cuenta ha cambiado — ve a Configuración para probar la conexión de nuevo.', - 'memories.error.loadAlbums': 'Error al cargar los álbumes', - 'memories.error.linkAlbum': 'Error al vincular el álbum', - 'memories.error.unlinkAlbum': 'Error al desvincular el álbum', - 'memories.error.syncAlbum': 'Error al sincronizar el álbum', - 'memories.error.loadPhotos': 'Error al cargar las fotos', - 'memories.error.addPhotos': 'Error al agregar las fotos', - 'memories.error.removePhoto': 'Error al eliminar la foto', - 'memories.error.toggleSharing': 'Error al actualizar el uso compartido', - 'undo.addPlace': 'Lugar agregado', - 'undo.done': 'Deshecho: {action}', - 'notifications.test.title': 'Notificación de prueba de {actor}', - 'notifications.test.text': 'Esta es una notificación de prueba simple.', - 'notifications.test.booleanTitle': '{actor} solicita tu aprobación', - 'notifications.test.booleanText': 'Notificación de prueba booleana.', - 'notifications.test.accept': 'Aprobar', - 'notifications.test.decline': 'Rechazar', - 'notifications.test.navigateTitle': 'Mira esto', - 'notifications.test.navigateText': 'Notificación de prueba de navegación.', - 'notifications.test.goThere': 'Ir allí', - 'notifications.test.adminTitle': 'Difusión de administrador', - 'notifications.test.adminText': '{actor} envió una notificación de prueba a todos los administradores.', - 'notifications.test.tripTitle': '{actor} publicó en tu viaje', - 'notifications.test.tripText': 'Notificación de prueba para el viaje "{trip}".', - - // Todo - 'todo.subtab.packing': 'Lista de equipaje', - 'todo.subtab.todo': 'Por hacer', - 'todo.completed': 'completado(s)', - 'todo.filter.all': 'Todo', - 'todo.filter.open': 'Abierto', - 'todo.filter.done': 'Hecho', - 'todo.uncategorized': 'Sin categoría', - 'todo.namePlaceholder': 'Nombre de la tarea', - 'todo.descriptionPlaceholder': 'Descripción (opcional)', - 'todo.unassigned': 'Sin asignar', - 'todo.noCategory': 'Sin categoría', - 'todo.hasDescription': 'Con descripción', - 'todo.addItem': 'Nueva tarea', - 'todo.sidebar.sortBy': 'Ordenar por', - 'todo.priority': 'Prioridad', - 'todo.newCategoryLabel': 'nueva', - 'budget.categoriesLabel': 'categorías', - 'todo.newCategory': 'Nombre de la categoría', - 'todo.addCategory': 'Añadir categoría', - 'todo.newItem': 'Nueva tarea', - 'todo.empty': 'Aún no hay tareas. ¡Añade una tarea para empezar!', - 'todo.filter.my': 'Mis tareas', - 'todo.filter.overdue': 'Vencida', - 'todo.sidebar.tasks': 'Tareas', - 'todo.sidebar.categories': 'Categorías', - 'todo.detail.title': 'Tarea', - 'todo.detail.description': 'Descripción', - 'todo.detail.category': 'Categoría', - 'todo.detail.dueDate': 'Fecha límite', - 'todo.detail.assignedTo': 'Asignado a', - 'todo.detail.delete': 'Eliminar', - 'todo.detail.save': 'Guardar cambios', - 'todo.detail.create': 'Crear tarea', - 'todo.detail.priority': 'Prioridad', - 'todo.detail.noPriority': 'Ninguna', - 'todo.sortByPrio': 'Prioridad', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nueva versión disponible', - 'settings.notificationPreferences.noChannels': 'No hay canales de notificación configurados. Pide a un administrador que configure notificaciones por correo o webhook.', - 'settings.webhookUrl.label': 'URL del webhook', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Introduce tu URL de webhook de Discord, Slack o personalizada para recibir notificaciones.', - 'settings.webhookUrl.saved': 'URL del webhook guardada', - 'settings.webhookUrl.test': 'Probar', - 'settings.webhookUrl.testSuccess': 'Webhook de prueba enviado correctamente', - 'settings.webhookUrl.testFailed': 'Error al enviar el webhook de prueba', - 'settings.ntfyUrl.topicLabel': 'Tema de Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL del servidor Ntfy (opcional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Introduce tu tema de Ntfy para recibir notificaciones push. Deja el servidor en blanco para usar el predeterminado configurado por tu administrador.', - 'settings.ntfyUrl.tokenLabel': 'Token de acceso (opcional)', - 'settings.ntfyUrl.tokenHint': 'Requerido para temas protegidos con contraseña.', - 'settings.ntfyUrl.saved': 'Configuración de Ntfy guardada', - 'settings.ntfyUrl.test': 'Probar', - 'settings.ntfyUrl.testSuccess': 'Notificación de prueba de Ntfy enviada correctamente', - 'settings.ntfyUrl.testFailed': 'Error en la notificación de prueba de Ntfy', - 'settings.ntfyUrl.tokenCleared': 'Token de acceso eliminado', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Las notificaciones in-app siempre están activas y no se pueden desactivar globalmente.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook de admin', - 'admin.notifications.adminWebhookPanel.hint': 'Este webhook se usa exclusivamente para notificaciones de admin (ej. alertas de versión). Es independiente de los webhooks de usuario y se activa automáticamente si hay una URL configurada.', - 'admin.notifications.adminWebhookPanel.saved': 'URL del webhook de admin guardada', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de prueba enviado correctamente', - 'admin.notifications.adminWebhookPanel.testFailed': 'Error al enviar el webhook de prueba', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'El webhook de admin se activa automáticamente si hay una URL configurada', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Permite a los usuarios configurar sus propios temas ntfy para notificaciones push. Establece el servidor predeterminado a continuación para rellenar automáticamente los ajustes del usuario.', - 'admin.notifications.testNtfy': 'Enviar Ntfy de prueba', - 'admin.notifications.testNtfySuccess': 'Ntfy de prueba enviado correctamente', - 'admin.notifications.testNtfyFailed': 'Error al enviar el Ntfy de prueba', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', - 'admin.notifications.adminNtfyPanel.hint': 'Este tema Ntfy se usa exclusivamente para notificaciones de admin (ej. alertas de versión). Es independiente de los temas por usuario y siempre se activa cuando está configurado.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL del servidor Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'También se usa como servidor predeterminado para las notificaciones ntfy de los usuarios. Déjalo en blanco para usar ntfy.sh. Los usuarios pueden cambiarlo en sus propios ajustes.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Tema de admin', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acceso (opcional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Token de acceso de admin eliminado', - 'admin.notifications.adminNtfyPanel.saved': 'Configuración de Ntfy de admin guardada', - 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de prueba', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de prueba enviado correctamente', - 'admin.notifications.adminNtfyPanel.testFailed': 'Error al enviar el Ntfy de prueba', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'El Ntfy de admin siempre se activa cuando hay un tema configurado', - 'admin.notifications.adminNotificationsHint': 'Configura qué canales entregan notificaciones de admin (ej. alertas de versión). El webhook se activa automáticamente si hay una URL de webhook de admin configurada.', - 'admin.notifications.tripReminders.title': 'Recordatorios de viaje', - 'admin.notifications.tripReminders.hint': 'Envía una notificación de recordatorio antes de que comience un viaje (requiere días de recordatorio configurados en el viaje).', - 'admin.notifications.tripReminders.enabled': 'Recordatorios de viaje activados', - 'admin.notifications.tripReminders.disabled': 'Recordatorios de viaje desactivados', - 'admin.tabs.notifications': 'Notificaciones', - 'notifications.versionAvailable.title': 'Actualización disponible', - 'notifications.versionAvailable.text': 'TREK {version} ya está disponible.', - 'notifications.versionAvailable.button': 'Ver detalles', - 'notif.test.title': '[Test] Notificación', - 'notif.test.simple.text': 'Esta es una notificación de prueba simple.', - 'notif.test.boolean.text': '¿Aceptas esta notificación de prueba?', - 'notif.test.navigate.text': 'Haz clic abajo para ir al panel de control.', - - // Notifications - 'notif.trip_invite.title': 'Invitación al viaje', - 'notif.trip_invite.text': '{actor} te invitó a {trip}', - 'notif.booking_change.title': 'Reserva actualizada', - 'notif.booking_change.text': '{actor} actualizó una reserva en {trip}', - 'notif.trip_reminder.title': 'Recordatorio de viaje', - 'notif.trip_reminder.text': '¡Tu viaje {trip} se acerca!', - 'notif.todo_due.title': 'Tarea pendiente', - 'notif.todo_due.text': '{todo} en {trip} vence el {due}', - 'notif.vacay_invite.title': 'Invitación Vacay Fusion', - 'notif.vacay_invite.text': '{actor} te invitó a fusionar planes de vacaciones', - 'notif.photos_shared.title': 'Fotos compartidas', - 'notif.photos_shared.text': '{actor} compartió {count} foto(s) en {trip}', - 'notif.collab_message.title': 'Nuevo mensaje', - 'notif.collab_message.text': '{actor} envió un mensaje en {trip}', - 'notif.packing_tagged.title': 'Asignación de equipaje', - 'notif.packing_tagged.text': '{actor} te asignó a {category} en {trip}', - 'notif.version_available.title': 'Nueva versión disponible', - 'notif.version_available.text': 'TREK {version} ya está disponible', - 'notif.action.view_trip': 'Ver viaje', - 'notif.action.view_collab': 'Ver mensajes', - 'notif.action.view_packing': 'Ver equipaje', - 'notif.action.view_photos': 'Ver fotos', - 'notif.action.view_vacay': 'Ver Vacay', - 'notif.action.view_admin': 'Ir al admin', - 'notif.action.view': 'Ver', - 'notif.action.accept': 'Aceptar', - 'notif.action.decline': 'Rechazar', - 'notif.generic.title': 'Notificación', - 'notif.generic.text': 'Tienes una nueva notificación', - 'notif.dev.unknown_event.title': '[DEV] Evento desconocido', - 'notif.dev.unknown_event.text': 'El tipo de evento "{event}" no está registrado en EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'justo ahora', - 'common.hoursAgo': 'hace {count}h', - 'common.daysAgo': 'hace {count}d', - 'journey.search.placeholder': 'Buscar viajes…', - 'journey.search.noResults': 'Ningún viaje coincide con "{query}"', - 'journey.title': 'Travesía', - 'journey.subtitle': 'Registra tus viajes en tiempo real', - 'journey.new': 'Nueva travesía', - 'journey.create': 'Crear', - 'journey.titlePlaceholder': '¿A dónde vas?', - 'journey.empty': 'Aún no hay travesías', - 'journey.emptyHint': 'Empieza a documentar tu próximo viaje', - 'journey.deleted': 'Travesía eliminada', - 'journey.createError': 'No se pudo crear la travesía', - 'journey.deleteError': 'No se pudo eliminar la travesía', - 'journey.deleteConfirmTitle': 'Eliminar', - 'journey.deleteConfirmMessage': '¿Eliminar "{title}"? Esta acción no se puede deshacer.', - 'journey.deleteConfirmGeneric': '¿Estás seguro de que quieres eliminar esto?', - 'journey.notFound': 'Travesía no encontrada', - 'journey.photos': 'Fotos', - 'journey.timelineEmpty': 'Aún no hay paradas', - 'journey.timelineEmptyHint': 'Añade un registro de ubicación o escribe una entrada de diario para empezar', - 'journey.status.draft': 'Borrador', - 'journey.status.active': 'Activa', - 'journey.status.completed': 'Completada', - 'journey.status.upcoming': 'Próxima', - 'journey.status.archived': 'Archivado', - 'journey.checkin.add': 'Registrar ubicación', - 'journey.checkin.namePlaceholder': 'Nombre del lugar', - 'journey.checkin.notesPlaceholder': 'Notas (opcional)', - 'journey.checkin.save': 'Guardar', - 'journey.checkin.error': 'No se pudo guardar el registro', - 'journey.entry.add': 'Diario', - 'journey.entry.edit': 'Editar entrada', - 'journey.entry.titlePlaceholder': 'Título (opcional)', - 'journey.entry.bodyPlaceholder': '¿Qué pasó hoy?', - 'journey.entry.save': 'Guardar', - 'journey.entry.error': 'No se pudo guardar la entrada', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Error al subir', - 'journey.share.share': 'Compartir', - 'journey.share.public': 'Público', - 'journey.share.linkCopied': 'Enlace público copiado', - 'journey.share.disabled': 'Compartir público desactivado', - 'journey.editor.titlePlaceholder': 'Dale un nombre a este momento...', - 'journey.editor.bodyPlaceholder': 'Cuenta la historia de este día...', - 'journey.editor.placePlaceholder': 'Ubicación (opcional)', - 'journey.editor.tagsPlaceholder': 'Etiquetas: joya oculta, mejor comida, hay que volver...', - 'journey.visibility.private': 'Privado', - 'journey.visibility.shared': 'Compartido', - 'journey.visibility.public': 'Público', - 'journey.emptyState.title': 'Tu historia empieza aquí', - 'journey.emptyState.subtitle': 'Registra una ubicación o escribe tu primera entrada de diario', - 'journey.frontpage.subtitle': 'Convierte tus viajes en historias que nunca olvidarás', - 'journey.frontpage.createJourney': 'Crear travesía', - 'journey.frontpage.activeJourney': 'Travesía activa', - 'journey.frontpage.allJourneys': 'Todas las travesías', - 'journey.frontpage.journeys': 'travesías', - 'journey.frontpage.createNew': 'Crear una nueva travesía', - 'journey.frontpage.createNewSub': 'Elige viajes, escribe historias, comparte tus aventuras', - 'journey.frontpage.live': 'En vivo', - 'journey.frontpage.synced': 'Sincronizado', - 'journey.frontpage.continueWriting': 'Seguir escribiendo', - 'journey.frontpage.updated': 'Actualizado {time}', - 'journey.frontpage.suggestionLabel': 'El viaje acaba de terminar', - 'journey.frontpage.suggestionText': 'Convierte {title} en una travesía', - 'journey.frontpage.dismiss': 'Descartar', - 'journey.frontpage.journeyName': 'Nombre de la travesía', - 'journey.frontpage.namePlaceholder': 'p. ej. Sudeste Asiático 2026', - 'journey.frontpage.selectTrips': 'Seleccionar viajes', - 'journey.frontpage.tripsSelected': 'viajes seleccionados', - 'journey.frontpage.trips': 'viajes', - 'journey.frontpage.placesImported': 'lugares serán importados', - 'journey.frontpage.places': 'lugares', - 'journey.detail.backToJourney': 'Volver a la travesía', - 'journey.detail.syncedWithTrips': 'Sincronizado con viajes', - 'journey.detail.addEntry': 'Añadir entrada', - 'journey.detail.newEntry': 'Nueva entrada', - 'journey.detail.editEntry': 'Editar entrada', - 'journey.detail.noEntries': 'Aún no hay entradas', - 'journey.detail.noEntriesHint': 'Añade un viaje para empezar con entradas preliminares', - 'journey.detail.noPhotos': 'Aún no hay fotos', - 'journey.detail.noPhotosHint': 'Sube fotos a las entradas o explora tu biblioteca de Immich/Synology', - 'journey.detail.journeyStats': 'Estadísticas de la travesía', - 'journey.detail.syncedTrips': 'Viajes sincronizados', - 'journey.detail.noTripsLinked': 'Aún no hay viajes vinculados', - 'journey.detail.contributors': 'Colaboradores', - 'journey.detail.readMore': 'Leer más', - 'journey.detail.prosCons': 'Pros y contras', - 'journey.detail.photos': 'fotos', - 'journey.detail.day': 'Día {number}', - 'journey.detail.places': 'lugares', - 'journey.stats.days': 'Días', - 'journey.stats.cities': 'Ciudades', - 'journey.stats.entries': 'Entradas', - 'journey.stats.photos': 'Fotos', - 'journey.stats.places': 'Lugares', - 'journey.skeletons.show': 'Mostrar sugerencias', - 'journey.skeletons.hide': 'Ocultar sugerencias', - 'journey.verdict.lovedIt': 'Me encantó', - 'journey.verdict.couldBeBetter': 'Podría mejorar', - 'journey.synced.places': 'lugares', - 'journey.synced.synced': 'sincronizado', - 'journey.editor.discardChangesConfirm': 'Tienes cambios sin guardar. ¿Descartarlos?', - 'journey.editor.uploadFailed': 'Error al subir fotos', - 'journey.editor.uploadPhotos': 'Subir fotos', - 'journey.editor.uploading': 'Subiendo...', - 'journey.editor.uploadingProgress': 'Subiendo {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} de {total} fotos fallaron — guarda de nuevo para reintentar', - 'journey.editor.fromGallery': 'Desde galería', - 'journey.editor.allPhotosAdded': 'Todas las fotos ya fueron añadidas', - 'journey.editor.writeStory': 'Escribe tu historia...', - 'journey.editor.prosCons': 'Pros y contras', - 'journey.editor.pros': 'Pros', - 'journey.editor.cons': 'Contras', - 'journey.editor.proPlaceholder': 'Algo genial...', - 'journey.editor.conPlaceholder': 'No tan genial...', - 'journey.editor.addAnother': 'Añadir otro', - 'journey.editor.date': 'Fecha', - 'journey.editor.location': 'Ubicación', - 'journey.editor.searchLocation': 'Buscar ubicación...', - 'journey.editor.mood': 'Estado de ánimo', - 'journey.editor.weather': 'Clima', - 'journey.editor.photoFirst': '1º', - 'journey.editor.makeFirst': 'Hacer 1º', - 'journey.editor.searching': 'Buscando...', - 'journey.mood.amazing': 'Increíble', - 'journey.mood.good': 'Bien', - 'journey.mood.neutral': 'Neutral', - 'journey.mood.rough': 'Difícil', - 'journey.weather.sunny': 'Soleado', - 'journey.weather.partly': 'Parcialmente nublado', - 'journey.weather.cloudy': 'Nublado', - 'journey.weather.rainy': 'Lluvioso', - 'journey.weather.stormy': 'Tormentoso', - 'journey.weather.cold': 'Nevado', - 'journey.trips.linkTrip': 'Vincular viaje', - 'journey.trips.searchTrip': 'Buscar viaje', - 'journey.trips.searchPlaceholder': 'Nombre del viaje o destino...', - 'journey.trips.noTripsAvailable': 'No hay viajes disponibles', - 'journey.trips.link': 'Vincular', - 'journey.trips.tripLinked': 'Viaje vinculado', - 'journey.trips.linkFailed': 'No se pudo vincular el viaje', - 'journey.trips.addTrip': 'Añadir viaje', - 'journey.trips.unlinkTrip': 'Desvincular viaje', - 'journey.trips.unlinkMessage': '¿Desvincular "{title}"? Todas las entradas y fotos sincronizadas de este viaje se eliminarán permanentemente. Esta acción no se puede deshacer.', - 'journey.trips.unlink': 'Desvincular', - 'journey.trips.tripUnlinked': 'Viaje desvinculado', - 'journey.trips.unlinkFailed': 'No se pudo desvincular el viaje', - 'journey.trips.noTripsLinkedSettings': 'No hay viajes vinculados', - 'journey.contributors.invite': 'Invitar colaborador', - 'journey.contributors.searchUser': 'Buscar usuario', - 'journey.contributors.searchPlaceholder': 'Nombre de usuario o correo...', - 'journey.contributors.noUsers': 'No se encontraron usuarios', - 'journey.contributors.role': 'Rol', - 'journey.contributors.added': 'Colaborador añadido', - 'journey.contributors.addFailed': 'No se pudo añadir al colaborador', - 'journey.share.publicShare': 'Compartir público', - 'journey.share.createLink': 'Crear enlace para compartir', - 'journey.share.linkCreated': 'Enlace para compartir creado', - 'journey.share.createFailed': 'No se pudo crear el enlace', - 'journey.share.copy': 'Copiar', - 'journey.share.copied': '¡Copiado!', - 'journey.share.timeline': 'Cronología', - 'journey.share.gallery': 'Galería', - 'journey.share.map': 'Mapa', - 'journey.share.removeLink': 'Eliminar enlace para compartir', - 'journey.share.linkDeleted': 'Enlace para compartir eliminado', - 'journey.share.deleteFailed': 'No se pudo eliminar', - 'journey.share.updateFailed': 'No se pudo actualizar', - - // Journey — Invite - 'journey.invite.role': 'Rol', - 'journey.invite.viewer': 'Lector', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Invitar', - 'journey.invite.inviting': 'Invitando...', - 'journey.settings.title': 'Ajustes de la travesía', - 'journey.settings.coverImage': 'Imagen de portada', - 'journey.settings.changeCover': 'Cambiar portada', - 'journey.settings.addCover': 'Añadir imagen de portada', - 'journey.settings.name': 'Nombre', - 'journey.settings.subtitle': 'Subtítulo', - 'journey.settings.subtitlePlaceholder': 'p. ej. Tailandia, Vietnam y Camboya', - 'journey.settings.endJourney': 'Archivar viaje', - 'journey.settings.reopenJourney': 'Restaurar viaje', - 'journey.settings.archived': 'Viaje archivado', - 'journey.settings.reopened': 'Viaje reabierto', - 'journey.settings.endDescription': 'Oculta la insignia En Vivo. Puedes reabrirlo en cualquier momento.', - 'journey.settings.delete': 'Eliminar', - 'journey.settings.deleteJourney': 'Eliminar travesía', - 'journey.settings.deleteMessage': '¿Eliminar "{title}"? Todas las entradas y fotos se perderán.', - 'journey.settings.saved': 'Ajustes guardados', - 'journey.settings.saveFailed': 'No se pudo guardar', - 'journey.settings.coverUpdated': 'Portada actualizada', - 'journey.settings.coverFailed': 'Error al subir', - 'journey.settings.failedToDelete': 'Error al eliminar', - 'journey.entries.deleteTitle': 'Eliminar entrada', - 'journey.photosUploaded': '{count} fotos subidas', - 'journey.photosUploadFailed': 'Algunas fotos no se pudieron subir', - 'journey.photosAdded': '{count} fotos añadidas', - 'journey.public.notFound': 'No encontrado', - 'journey.public.notFoundMessage': 'Esta travesía no existe o el enlace ha expirado.', - 'journey.public.readOnly': 'Solo lectura · Travesía pública', - 'journey.public.tagline': 'Kit de recursos y exploración de viajes', - 'journey.public.sharedVia': 'Compartido mediante', - 'journey.public.madeWith': 'Hecho con', - 'journey.pdf.journeyBook': 'Libro de travesía', - 'journey.pdf.madeWith': 'Hecho con TREK', - 'journey.pdf.day': 'Día', - 'journey.pdf.theEnd': 'Fin', - 'journey.pdf.saveAsPdf': 'Guardar como PDF', - 'journey.pdf.pages': 'páginas', - 'journey.picker.tripPeriod': 'Período del viaje', - 'journey.picker.dateRange': 'Rango de fechas', - 'journey.picker.allPhotos': 'Todas las fotos', - 'journey.picker.albums': 'Álbumes', - 'journey.picker.selected': 'seleccionados', - 'journey.picker.addTo': 'Añadir a', - 'journey.picker.newGallery': 'Nueva galería', - 'journey.picker.selectAll': 'Seleccionar todo', - 'journey.picker.deselectAll': 'Deseleccionar todo', - 'journey.picker.noAlbums': 'No se encontraron álbumes', - 'journey.picker.selectDate': 'Seleccionar fecha', - 'journey.picker.search': 'Buscar', - 'dashboard.greeting.morning': 'Buenos días,', - 'dashboard.greeting.afternoon': 'Buenas tardes,', - 'dashboard.greeting.evening': 'Buenas noches,', - 'dashboard.mobile.liveNow': 'En vivo ahora', - 'dashboard.mobile.tripProgress': 'Progreso del viaje', - 'dashboard.mobile.daysLeft': '{count} días restantes', - 'dashboard.mobile.places': 'Lugares', - 'dashboard.mobile.buddies': 'Compañeros', - 'dashboard.mobile.newTrip': 'Nuevo viaje', - 'dashboard.mobile.currency': 'Moneda', - 'dashboard.mobile.timezone': 'Zona horaria', - 'dashboard.mobile.upcomingTrips': 'Próximos viajes', - 'dashboard.mobile.yourTrips': 'Tus viajes', - 'dashboard.mobile.trips': 'viajes', - 'dashboard.mobile.starts': 'Comienza', - 'dashboard.mobile.duration': 'Duración', - 'dashboard.mobile.day': 'día', - 'dashboard.mobile.days': 'días', - 'dashboard.mobile.ongoing': 'En curso', - 'dashboard.mobile.startsToday': 'Comienza hoy', - 'dashboard.mobile.tomorrow': 'Mañana', - 'dashboard.mobile.inDays': 'En {count} días', - 'dashboard.mobile.inMonths': 'En {count} meses', - 'dashboard.mobile.completed': 'Completado', - 'dashboard.mobile.currencyConverter': 'Conversor de monedas', - 'nav.profile': 'Perfil', - 'nav.bottomSettings': 'Ajustes', - 'nav.bottomAdmin': 'Administración', - 'nav.bottomLogout': 'Cerrar sesión', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Añadir lugar', - 'dayplan.mobile.searchPlaces': 'Buscar lugares...', - 'dayplan.mobile.allAssigned': 'Todos los lugares asignados', - 'dayplan.mobile.noMatch': 'Sin coincidencias', - 'dayplan.mobile.createNew': 'Crear nuevo lugar', - 'admin.addons.catalog.journey.name': 'Travesía', - 'admin.addons.catalog.journey.description': 'Seguimiento de viajes y diario de viajero con registros de ubicación, fotos e historias diarias', - // OAuth scope groups - 'oauth.scope.group.trips': 'Viajes', - 'oauth.scope.group.places': 'Lugares', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Equipaje', - 'oauth.scope.group.todos': 'Tareas', - 'oauth.scope.group.budget': 'Presupuesto', - 'oauth.scope.group.reservations': 'Reservas', - 'oauth.scope.group.collab': 'Colaboración', - 'oauth.scope.group.notifications': 'Notificaciones', - 'oauth.scope.group.vacay': 'Vacaciones', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Clima', - 'oauth.scope.group.journey': 'Travesía', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Ver viajes e itinerarios', - 'oauth.scope.trips:read.description': 'Leer viajes, días, notas y miembros', - 'oauth.scope.trips:write.label': 'Editar viajes e itinerarios', - 'oauth.scope.trips:write.description': 'Crear y actualizar viajes, días, notas y gestionar miembros', - 'oauth.scope.trips:delete.label': 'Eliminar viajes', - 'oauth.scope.trips:delete.description': 'Eliminar viajes permanentemente — esta acción es irreversible', - 'oauth.scope.trips:share.label': 'Gestionar enlaces de compartir', - 'oauth.scope.trips:share.description': 'Crear, actualizar y revocar enlaces públicos de viaje', - 'oauth.scope.places:read.label': 'Ver lugares y datos del mapa', - 'oauth.scope.places:read.description': 'Leer lugares, asignaciones de días, etiquetas y categorías', - 'oauth.scope.places:write.label': 'Gestionar lugares', - 'oauth.scope.places:write.description': 'Crear, actualizar y eliminar lugares, asignaciones y etiquetas', - 'oauth.scope.atlas:read.label': 'Ver Atlas', - 'oauth.scope.atlas:read.description': 'Leer países visitados, regiones y lista de deseos', - 'oauth.scope.atlas:write.label': 'Gestionar Atlas', - 'oauth.scope.atlas:write.description': 'Marcar países y regiones como visitados, gestionar lista de deseos', - 'oauth.scope.packing:read.label': 'Ver listas de equipaje', - 'oauth.scope.packing:read.description': 'Leer artículos, maletas y responsables de categoría', - 'oauth.scope.packing:write.label': 'Gestionar listas de equipaje', - 'oauth.scope.packing:write.description': 'Agregar, actualizar, eliminar, marcar y reordenar artículos y maletas', - 'oauth.scope.todos:read.label': 'Ver listas de tareas', - 'oauth.scope.todos:read.description': 'Leer tareas del viaje y responsables de categoría', - 'oauth.scope.todos:write.label': 'Gestionar listas de tareas', - 'oauth.scope.todos:write.description': 'Crear, actualizar, marcar, eliminar y reordenar tareas', - 'oauth.scope.budget:read.label': 'Ver presupuesto', - 'oauth.scope.budget:read.description': 'Leer partidas de presupuesto y desglose de gastos', - 'oauth.scope.budget:write.label': 'Gestionar presupuesto', - 'oauth.scope.budget:write.description': 'Crear, actualizar y eliminar partidas de presupuesto', - 'oauth.scope.reservations:read.label': 'Ver reservas', - 'oauth.scope.reservations:read.description': 'Leer reservas y detalles de alojamiento', - 'oauth.scope.reservations:write.label': 'Gestionar reservas', - 'oauth.scope.reservations:write.description': 'Crear, actualizar, eliminar y reordenar reservas', - 'oauth.scope.collab:read.label': 'Ver colaboración', - 'oauth.scope.collab:read.description': 'Leer notas colaborativas, encuestas y mensajes', - 'oauth.scope.collab:write.label': 'Gestionar colaboración', - 'oauth.scope.collab:write.description': 'Crear, actualizar y eliminar notas, encuestas y mensajes', - 'oauth.scope.notifications:read.label': 'Ver notificaciones', - 'oauth.scope.notifications:read.description': 'Leer notificaciones y conteos no leídos', - 'oauth.scope.notifications:write.label': 'Gestionar notificaciones', - 'oauth.scope.notifications:write.description': 'Marcar notificaciones como leídas y responderlas', - 'oauth.scope.vacay:read.label': 'Ver planes de vacaciones', - 'oauth.scope.vacay:read.description': 'Leer datos de planificación, entradas y estadísticas de vacaciones', - 'oauth.scope.vacay:write.label': 'Gestionar planes de vacaciones', - 'oauth.scope.vacay:write.description': 'Crear y gestionar entradas de vacaciones, festivos y planes de equipo', - 'oauth.scope.geo:read.label': 'Mapas y geocodificación', - 'oauth.scope.geo:read.description': 'Buscar lugares, resolver URLs de mapa y geocodificar coordenadas', - 'oauth.scope.weather:read.label': 'Previsiones meteorológicas', - 'oauth.scope.weather:read.description': 'Obtener previsiones meteorológicas para lugares y fechas del viaje', - 'oauth.scope.journey:read.label': 'Ver travesías', - 'oauth.scope.journey:read.description': 'Leer travesías, entradas y lista de colaboradores', - 'oauth.scope.journey:write.label': 'Gestionar travesías', - 'oauth.scope.journey:write.description': 'Crear, actualizar y eliminar travesías y sus entradas', - 'oauth.scope.journey:share.label': 'Gestionar enlaces de travesías', - 'oauth.scope.journey:share.description': 'Crear, actualizar y revocar enlaces públicos de compartir para travesías', - - // System notices - 'system_notice.welcome_v1.title': 'Bienvenido a TREK', - 'system_notice.welcome_v1.body': 'Tu planificador de viajes todo en uno. Crea itinerarios, comparte viajes con amigos y mantente organizado, online o sin conexión.', - 'system_notice.welcome_v1.cta_label': 'Planificar un viaje', - 'system_notice.welcome_v1.hero_alt': 'Destino de viaje pintoresco con la interfaz de TREK', - 'system_notice.welcome_v1.highlight_plan': 'Itinerarios día a día para cualquier viaje', - 'system_notice.welcome_v1.highlight_share': 'Colabora con tus compañeros de viaje', - 'system_notice.welcome_v1.highlight_offline': 'Funciona sin conexión en móvil', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Aviso anterior', - 'system_notice.pager.next': 'Siguiente aviso', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Ir al aviso {n}', - 'system_notice.pager.position': 'Aviso {current} de {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Las fotos se han movido en 3.0', - 'system_notice.v3_photos.body': '**Fotos** en el Planificador de Viajes han sido eliminadas. Tus fotos están a salvo — TREK nunca modificó tu biblioteca de Immich o Synology.\n\nLas fotos ahora viven en el addon **Journey**. Journey es opcional — si aún no está disponible, pide a tu admin que lo active en Admin → Complementos.', - 'system_notice.v3_journey.title': 'Conoce Journey — diario de viaje', - 'system_notice.v3_journey.body': 'Documenta tus viajes como historias enriquecidas con cronologías, galerías de fotos y mapas interactivos.', - 'system_notice.v3_journey.cta_label': 'Abrir Journey', - 'system_notice.v3_journey.highlight_timeline': 'Cronología y galería por día', - 'system_notice.v3_journey.highlight_photos': 'Importar desde Immich o Synology', - 'system_notice.v3_journey.highlight_share': 'Compartir públicamente — sin inicio de sesión', - 'system_notice.v3_journey.highlight_export': 'Exportar como libro de fotos PDF', - 'system_notice.v3_features.title': 'Más novedades en 3.0', - 'system_notice.v3_features.body': 'Otras cosas que vale la pena conocer de esta versión.', - 'system_notice.v3_features.highlight_dashboard': 'Rediseño del panel mobile-first', - 'system_notice.v3_features.highlight_offline': 'Modo sin conexión completo como PWA', - 'system_notice.v3_features.highlight_search': 'Autocompletado de lugares en tiempo real', - 'system_notice.v3_features.highlight_import': 'Importar lugares desde archivos KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: actualización OAuth 2.1', - 'system_notice.v3_mcp.body': 'La integración MCP ha sido completamente renovada. OAuth 2.1 es ahora el método de autenticación recomendado. Los tokens estáticos (trek_…) están obsoletos y se eliminarán en una versión futura.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recomendado (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 ámbitos de permisos granulares', - 'system_notice.v3_mcp.highlight_deprecated': 'Tokens estáticos trek_ obsoletos', - 'system_notice.v3_mcp.highlight_tools': 'Herramientas y prompts ampliados', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Una nota personal de mi parte', - 'system_notice.v3_thankyou.body': 'Antes de seguir — quiero tomarme un momento.\n\nTREK empezó como un proyecto personal que construí para mis propios viajes. Nunca imaginé que crecería hasta convertirse en algo en lo que 4.000 de vosotros confían para planificar sus aventuras. Cada estrella, cada issue, cada solicitud de funcionalidad — los leo todos, y son lo que me mantiene en pie durante las noches largas entre un trabajo a jornada completa y la universidad.\n\nQuiero que sepáis: TREK siempre será open source, siempre self-hosted, siempre vuestro. Sin rastreo, sin suscripciones, sin letra pequeña. Solo una herramienta hecha por alguien que ama viajar tanto como vosotros.\n\nUn agradecimiento especial a [jubnl](https://github.com/jubnl) — te has convertido en un colaborador increíble. Mucho de lo que hace grande la versión 3.0 lleva tu huella. Gracias por creer en este proyecto cuando todavía era un borrador.\n\nY a cada uno de vosotros que reportó un bug, tradujo un texto, compartió TREK con un amigo o simplemente lo usó para planificar un viaje — **gracias**. Vosotros sois la razón de que esto exista.\n\nPor muchas más aventuras juntos.\n\n— Maurice\n\n---\n\n[Únete a la comunidad en Discord](https://discord.gg/7Q6M6jDwzf)\n\nSi TREK mejora tus viajes, un [pequeño café](https://ko-fi.com/mauriceboe) siempre mantiene las luces encendidas.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Acción requerida: conflicto de cuenta de usuario', - 'system_notice.v3014_whitespace_collision.body': 'La actualización 3.0.14 detectó uno o más conflictos de nombre de usuario o correo electrónico causados por espacios en blanco al inicio o al final de los valores almacenados. Las cuentas afectadas se renombraron automáticamente. Revisa los registros del servidor en busca de líneas que empiecen por **[migration] WHITESPACE COLLISION** para identificar qué cuentas necesitan revisión.', - 'transport.addTransport': 'Añadir transporte', - 'transport.modalTitle.create': 'Añadir transporte', - 'transport.modalTitle.edit': 'Editar transporte', - 'transport.title': 'Transportes', - 'transport.addManual': 'Transporte manual', -} - -export default es - diff --git a/client/src/i18n/translations/fr.ts b/client/src/i18n/translations/fr.ts deleted file mode 100644 index 56246dac..00000000 --- a/client/src/i18n/translations/fr.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const fr: Record = { - // Common - 'common.save': 'Enregistrer', - 'common.showMore': 'Voir plus', - 'common.showLess': 'Voir moins', - 'common.cancel': 'Annuler', - 'common.clear': 'Effacer', - 'common.delete': 'Supprimer', - 'common.edit': 'Modifier', - 'common.add': 'Ajouter', - 'common.loading': 'Chargement…', - 'common.import': 'Importer', - 'common.select': 'Sélectionner', - 'common.selectAll': 'Tout sélectionner', - 'common.deselectAll': 'Tout désélectionner', - 'common.error': 'Erreur', - 'common.unknownError': 'Erreur inconnue', - 'common.tooManyAttempts': 'Trop de tentatives. Veuillez réessayer plus tard.', - 'common.back': 'Retour', - 'common.all': 'Tout', - 'common.close': 'Fermer', - 'common.open': 'Ouvrir', - 'common.upload': 'Importer', - 'common.search': 'Rechercher', - 'common.confirm': 'Confirmer', - 'common.ok': 'OK', - 'common.yes': 'Oui', - 'common.no': 'Non', - 'common.or': 'ou', - 'common.none': 'Aucun', - 'common.date': 'Date', - 'common.rename': 'Renommer', - 'common.discardChanges': 'Ignorer les modifications', - 'common.discard': 'Ignorer', - 'common.name': 'Nom', - 'common.email': 'E-mail', - 'common.password': 'Mot de passe', - 'common.saving': 'Enregistrement…', - 'common.saved': 'Enregistré', - 'trips.memberRemoved': '{username} supprimé', - 'trips.memberRemoveError': 'Échec de la suppression', - 'trips.memberAdded': '{username} ajouté', - 'trips.memberAddError': "Échec de l'ajout", - 'common.expand': 'Développer', - 'common.collapse': 'Réduire', - 'trips.reminder': 'Rappel', - 'trips.reminderNone': 'Aucun', - 'trips.reminderDay': 'jour', - 'trips.reminderDays': 'jours', - 'trips.reminderCustom': 'Personnalisé', - 'trips.reminderDaysBefore': 'jours avant le départ', - 'trips.reminderDisabledHint': 'Les rappels de voyage sont désactivés. Activez-les dans Admin > Paramètres > Notifications.', - 'common.update': 'Mettre à jour', - 'common.change': 'Modifier', - 'common.uploading': 'Import en cours…', - 'common.backToPlanning': 'Retour à la planification', - 'common.reset': 'Réinitialiser', - - // Navbar - 'nav.trip': 'Voyage', - 'nav.share': 'Partager', - 'nav.settings': 'Paramètres', - 'nav.admin': 'Admin', - 'nav.logout': 'Déconnexion', - 'nav.lightMode': 'Mode clair', - 'nav.darkMode': 'Mode sombre', - 'nav.autoMode': 'Mode auto', - 'nav.administrator': 'Administrateur', - - // Dashboard - 'dashboard.title': 'Mes voyages', - 'dashboard.subtitle.loading': 'Chargement des voyages…', - 'dashboard.subtitle.trips': '{count} voyages ({archived} archivés)', - 'dashboard.subtitle.empty': 'Commencez votre premier voyage', - 'dashboard.subtitle.activeOne': '{count} voyage actif', - 'dashboard.subtitle.activeMany': '{count} voyages actifs', - 'dashboard.subtitle.archivedSuffix': ' · {count} archivés', - 'dashboard.newTrip': 'Nouveau voyage', - 'dashboard.gridView': 'Vue en grille', - 'dashboard.listView': 'Vue en liste', - 'dashboard.currency': 'Devise', - 'dashboard.timezone': 'Fuseau horaire', - 'dashboard.localTime': 'Heure locale', - 'dashboard.timezoneCustomTitle': 'Fuseau horaire personnalisé', - 'dashboard.timezoneCustomLabelPlaceholder': 'Libellé (facultatif)', - 'dashboard.timezoneCustomTzPlaceholder': 'ex. America/New_York', - 'dashboard.timezoneCustomAdd': 'Ajouter', - 'dashboard.timezoneCustomErrorEmpty': 'Saisissez un identifiant de fuseau horaire', - 'dashboard.timezoneCustomErrorInvalid': 'Fuseau horaire invalide. Utilisez un format comme Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Déjà ajouté', - 'dashboard.emptyTitle': 'Aucun voyage', - 'dashboard.emptyText': 'Créez votre premier voyage et commencez à planifier !', - 'dashboard.emptyButton': 'Créer un premier voyage', - 'dashboard.nextTrip': 'Prochain voyage', - 'dashboard.shared': 'Partagé', - 'dashboard.sharedBy': 'Partagé par {name}', - 'dashboard.days': 'Jours', - 'dashboard.places': 'Lieux', - 'dashboard.members': 'Compagnons de voyage', - 'dashboard.archive': 'Archiver', - 'dashboard.copyTrip': 'Copier', - 'dashboard.copySuffix': 'copie', - 'dashboard.restore': 'Restaurer', - 'dashboard.archived': 'Archivé', - 'dashboard.status.ongoing': 'En cours', - 'dashboard.status.today': "Aujourd'hui", - 'dashboard.status.tomorrow': 'Demain', - 'dashboard.status.past': 'Passé', - 'dashboard.status.daysLeft': '{count} jours restants', - 'dashboard.toast.loadError': 'Impossible de charger les voyages', - 'dashboard.toast.created': 'Voyage créé avec succès !', - 'dashboard.toast.createError': 'Impossible de créer le voyage', - 'dashboard.toast.updated': 'Voyage mis à jour !', - 'dashboard.toast.updateError': 'Impossible de mettre à jour le voyage', - 'dashboard.toast.deleted': 'Voyage supprimé', - 'dashboard.toast.deleteError': 'Impossible de supprimer le voyage', - 'dashboard.toast.archived': 'Voyage archivé', - 'dashboard.toast.archiveError': "Impossible d'archiver le voyage", - 'dashboard.toast.restored': 'Voyage restauré', - 'dashboard.toast.restoreError': 'Impossible de restaurer le voyage', - 'dashboard.toast.copied': 'Voyage copié !', - 'dashboard.toast.copyError': 'Impossible de copier le voyage', - 'dashboard.confirm.delete': 'Supprimer le voyage « {title} » ? Tous les lieux et plans seront définitivement supprimés.', - 'dashboard.editTrip': 'Modifier le voyage', - 'dashboard.createTrip': 'Créer un nouveau voyage', - 'dashboard.tripTitle': 'Titre', - 'dashboard.tripTitlePlaceholder': 'ex. Été au Japon', - 'dashboard.tripDescription': 'Description', - 'dashboard.tripDescriptionPlaceholder': 'De quoi parle ce voyage ?', - 'dashboard.startDate': 'Date de début', - 'dashboard.endDate': 'Date de fin', - 'dashboard.dayCount': 'Nombre de jours', - 'dashboard.dayCountHint': 'Nombre de jours à planifier lorsqu\'aucune date de voyage n\'est définie.', - 'dashboard.noDateHint': 'Aucune date définie — 7 jours par défaut seront créés. Vous pouvez modifier cela à tout moment.', - 'dashboard.coverImage': 'Image de couverture', - 'dashboard.addCoverImage': 'Ajouter une image de couverture', - 'dashboard.addMembers': 'Compagnons de voyage', - 'dashboard.addMember': 'Ajouter un membre', - 'dashboard.coverSaved': 'Image de couverture enregistrée', - 'dashboard.coverUploadError': 'Échec de l\'import', - 'dashboard.coverRemoveError': 'Échec de la suppression', - 'dashboard.titleRequired': 'Le titre est obligatoire', - 'dashboard.endDateError': 'La date de fin doit être postérieure à la date de début', - - // Settings - 'settings.title': 'Paramètres', - 'settings.subtitle': 'Configurez vos paramètres personnels', - 'settings.tabs.display': 'Affichage', - 'settings.tabs.map': 'Carte', - 'settings.tabs.notifications': 'Notifications', - 'settings.tabs.integrations': 'Intégrations', - 'settings.tabs.account': 'Compte', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'À propos', - 'settings.map': 'Carte', - 'settings.mapTemplate': 'Modèle de carte', - 'settings.mapTemplatePlaceholder.select': 'Sélectionner un modèle…', - 'settings.mapDefaultHint': 'Laissez vide pour OpenStreetMap (par défaut)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'Modèle d\'URL pour les tuiles de carte', - 'settings.mapProvider': 'Fournisseur de carte', - 'settings.mapProviderHint': 'Affecte les cartes Trip Planner et Journey. Atlas utilise toujours Leaflet.', - 'settings.mapLeafletSubtitle': 'Classique 2D, toutes tuiles raster', - 'settings.mapMapboxSubtitle': 'Tuiles vectorielles, bâtiments 3D & terrain', - 'settings.mapExperimental': 'Expérimental', - 'settings.mapMapboxToken': 'Jeton d\'accès Mapbox', - 'settings.mapMapboxTokenHint': 'Jeton public (pk.*) depuis', - 'settings.mapMapboxTokenLink': 'mapbox.com → Jetons d\'accès', - 'settings.mapStyle': 'Style de carte', - 'settings.mapStylePlaceholder': 'Sélectionner un style Mapbox', - 'settings.mapStyleHint': 'Preset ou votre propre URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': 'Bâtiments 3D & terrain', - 'settings.map3dHint': 'Inclinaison + extrusions 3D réelles des bâtiments — fonctionne avec tous les styles, y compris satellite.', - 'settings.mapHighQuality': 'Mode haute qualité', - 'settings.mapHighQualityHint': 'Anticrénelage + projection globe pour des bords plus nets et une vue réaliste du monde.', - 'settings.mapHighQualityWarning': 'Peut affecter les performances sur les appareils moins puissants.', - 'settings.mapTipLabel': 'Astuce :', - 'settings.mapTip': 'Clic droit et glisser pour pivoter/incliner la carte. Clic milieu pour ajouter un lieu (le clic droit est réservé à la rotation).', - 'settings.latitude': 'Latitude', - 'settings.longitude': 'Longitude', - 'settings.saveMap': 'Enregistrer la carte', - 'settings.apiKeys': 'Clés API', - 'settings.mapsKey': 'Clé API Google Maps', - 'settings.mapsKeyHint': 'Pour la recherche de lieux. Nécessite l\'API Places (New). Obtenez-la sur console.cloud.google.com', - 'settings.weatherKey': 'Clé API OpenWeatherMap', - 'settings.weatherKeyHint': 'Pour les données météo. Gratuit sur openweathermap.org/api', - 'settings.keyPlaceholder': 'Saisir la clé…', - 'settings.configured': 'Configuré', - 'settings.saveKeys': 'Enregistrer les clés', - 'settings.display': 'Affichage', - 'settings.colorMode': 'Mode de couleur', - 'settings.light': 'Clair', - 'settings.dark': 'Sombre', - 'settings.auto': 'Auto', - 'settings.language': 'Langue', - 'settings.temperature': 'Unité de température', - 'settings.timeFormat': 'Format de l\'heure', - 'settings.blurBookingCodes': 'Masquer les codes de réservation', - 'settings.notifications': 'Notifications', - 'settings.notifyTripInvite': 'Invitations de voyage', - 'settings.notifyBookingChange': 'Modifications de réservation', - 'settings.notifyTripReminder': 'Rappels de voyage', - 'settings.notifyTodoDue': 'Tâche à échéance', - 'settings.notifyVacayInvite': 'Invitations de fusion Vacay', - 'settings.notifyPhotosShared': 'Photos partagées (Immich)', - 'settings.notifyCollabMessage': 'Messages de chat (Collab)', - 'settings.notifyPackingTagged': 'Liste de bagages : attributions', - 'settings.notifyWebhook': 'Notifications webhook', - 'settings.notificationsDisabled': 'Les notifications ne sont pas configurées. Demandez à un administrateur d\'activer les notifications par e-mail ou webhook.', - 'settings.notificationsActive': 'Canal actif', - 'settings.notificationsManagedByAdmin': 'Les événements de notification sont configurés par votre administrateur.', - 'admin.notifications.title': 'Notifications', - 'admin.notifications.hint': 'Choisissez un canal de notification. Un seul peut être actif à la fois.', - 'admin.notifications.none': 'Désactivé', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Enregistrer les paramètres de notification', - 'admin.notifications.saved': 'Paramètres de notification enregistrés', - 'admin.notifications.testWebhook': 'Envoyer un webhook de test', - 'admin.notifications.testWebhookSuccess': 'Webhook de test envoyé avec succès', - 'admin.notifications.testWebhookFailed': 'Échec du webhook de test', - 'admin.smtp.title': 'E-mail et notifications', - 'admin.smtp.hint': 'Configuration SMTP pour l\'envoi des notifications par e-mail.', - 'admin.smtp.testButton': 'Envoyer un e-mail de test', - 'admin.webhook.hint': 'Envoyer des notifications vers un webhook externe (Discord, Slack, etc.).', - 'admin.smtp.testSuccess': 'E-mail de test envoyé avec succès', - 'admin.smtp.testFailed': 'Échec de l\'e-mail de test', - 'dayplan.icsTooltip': 'Exporter le calendrier (ICS)', - 'share.linkTitle': 'Lien public', - 'share.linkHint': 'Créez un lien que n\'importe qui peut utiliser pour consulter ce voyage sans se connecter. Lecture seule — aucune modification possible.', - 'share.createLink': 'Créer un lien', - 'share.deleteLink': 'Supprimer le lien', - 'share.createError': 'Impossible de créer le lien', - 'common.copy': 'Copier', - 'common.copied': 'Copié', - 'share.permMap': 'Carte et plan', - 'share.permBookings': 'Réservations', - 'share.permPacking': 'Bagages', - 'shared.expired': 'Lien expiré ou invalide', - 'shared.expiredHint': 'Ce lien de partage n\'est plus actif.', - 'shared.readOnly': 'Vue en lecture seule', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Réservations', - 'shared.tabPacking': 'Bagages', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'jours', - 'shared.places': 'lieux', - 'shared.other': 'Autre', - 'shared.totalBudget': 'Budget total', - 'shared.messages': 'messages', - 'shared.sharedVia': 'Partagé via', - 'shared.confirmed': 'Confirmé', - 'shared.pending': 'En attente', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - 'settings.on': 'Activé', - 'settings.off': 'Désactivé', - 'settings.mcp.title': 'Configuration MCP', - 'settings.mcp.endpoint': 'Point de terminaison MCP', - 'settings.mcp.clientConfig': 'Configuration du client', - 'settings.mcp.clientConfigHint': 'Remplacez par un token API de la liste ci-dessous. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Remplacez et par les identifiants affichés dans le client OAuth 2.1 créé ci-dessus. mcp-remote ouvrira votre navigateur pour finaliser l\'autorisation lors de la première connexion. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).', - 'settings.mcp.copy': 'Copier', - 'settings.mcp.copied': 'Copié !', - 'settings.mcp.apiTokens': 'Tokens API', - 'settings.mcp.createToken': 'Créer un token', - 'settings.mcp.noTokens': 'Aucun token pour l\'instant. Créez-en un pour connecter des clients MCP.', - 'settings.mcp.tokenCreatedAt': 'Créé', - 'settings.mcp.tokenUsedAt': 'Utilisé', - 'settings.mcp.deleteTokenTitle': 'Supprimer le token', - 'settings.mcp.deleteTokenMessage': 'Ce token cessera de fonctionner immédiatement. Tout client MCP l\'utilisant perdra l\'accès.', - 'settings.mcp.modal.createTitle': 'Créer un token API', - 'settings.mcp.modal.tokenName': 'Nom du token', - 'settings.mcp.modal.tokenNamePlaceholder': 'ex. Claude Desktop, Ordinateur pro', - 'settings.mcp.modal.creating': 'Création…', - 'settings.mcp.modal.create': 'Créer le token', - 'settings.mcp.modal.createdTitle': 'Token créé', - 'settings.mcp.modal.createdWarning': 'Ce token ne sera affiché qu\'une seule fois. Copiez-le et conservez-le maintenant — il ne pourra pas être récupéré.', - 'settings.mcp.modal.done': 'Terminé', - 'settings.mcp.toast.created': 'Token créé', - 'settings.mcp.toast.createError': 'Impossible de créer le token', - 'settings.mcp.toast.deleted': 'Token supprimé', - 'settings.mcp.toast.deleteError': 'Impossible de supprimer le token', - 'settings.mcp.apiTokensDeprecated': 'Les tokens API sont dépréciés et seront supprimés dans une prochaine version. Veuillez utiliser les clients OAuth 2.1 à la place.', - 'settings.oauth.clients': 'Clients OAuth 2.1', - 'settings.oauth.clientsHint': 'Enregistrez des clients OAuth 2.1 pour permettre à des applications MCP tierces (Claude Web, Cursor, etc.) de se connecter sans tokens statiques.', - 'settings.oauth.createClient': 'Nouveau client', - 'settings.oauth.noClients': 'Aucun client OAuth enregistré.', - 'settings.oauth.clientId': 'ID client', - 'settings.oauth.clientSecret': 'Secret client', - 'settings.oauth.deleteClient': 'Supprimer le client', - 'settings.oauth.deleteClientMessage': 'Ce client et toutes les sessions actives seront définitivement supprimés. Toute application l\'utilisant perdra immédiatement l\'accès.', - 'settings.oauth.rotateSecret': 'Renouveler le secret', - 'settings.oauth.rotateSecretMessage': 'Un nouveau secret client sera généré et toutes les sessions existantes seront immédiatement invalidées. Mettez à jour votre application avant de fermer cette fenêtre.', - 'settings.oauth.rotateSecretConfirm': 'Renouveler', - 'settings.oauth.rotateSecretConfirming': 'Renouvellement…', - 'settings.oauth.rotateSecretDoneTitle': 'Nouveau secret généré', - 'settings.oauth.rotateSecretDoneWarning': 'Ce secret n\'est affiché qu\'une seule fois. Copiez-le maintenant et mettez à jour votre application — toutes les sessions précédentes ont été invalidées.', - 'settings.oauth.activeSessions': 'Sessions OAuth actives', - 'settings.oauth.sessionScopes': 'Portées', - 'settings.oauth.sessionExpires': 'Expire', - 'settings.oauth.revoke': 'Révoquer', - 'settings.oauth.revokeSession': 'Révoquer la session', - 'settings.oauth.revokeSessionMessage': 'Cela révoquera immédiatement l\'accès pour cette session OAuth.', - 'settings.oauth.modal.createTitle': 'Enregistrer un client OAuth', - 'settings.oauth.modal.presets': 'Préréglages rapides', - 'settings.oauth.modal.clientName': 'Nom de l\'application', - 'settings.oauth.modal.clientNamePlaceholder': 'ex. Claude Web, Mon app MCP', - 'settings.oauth.modal.redirectUris': 'URIs de redirection', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Une URI par ligne. HTTPS requis (localhost exempté). Correspondance exacte.', - 'settings.oauth.modal.scopes': 'Portées autorisées', - 'settings.oauth.modal.scopesHint': 'list_trips et get_trip_summary sont toujours disponibles — aucune portée requise. Ils permettent à l\'IA de découvrir les IDs de voyage nécessaires.', - 'settings.oauth.modal.selectAll': 'Tout sélectionner', - 'settings.oauth.modal.deselectAll': 'Tout désélectionner', - 'settings.oauth.modal.creating': 'Enregistrement…', - 'settings.oauth.modal.create': 'Enregistrer le client', - 'settings.oauth.modal.createdTitle': 'Client enregistré', - 'settings.oauth.modal.createdWarning': 'Le secret client n\'est affiché qu\'une seule fois. Copiez-le maintenant — il ne peut pas être récupéré.', - 'settings.oauth.toast.createError': 'Impossible d\'enregistrer le client OAuth', - 'settings.oauth.toast.deleted': 'Client OAuth supprimé', - 'settings.oauth.toast.deleteError': 'Impossible de supprimer le client OAuth', - 'settings.oauth.toast.revoked': 'Session révoquée', - 'settings.oauth.toast.revokeError': 'Impossible de révoquer la session', - 'settings.oauth.toast.rotateError': 'Impossible de renouveler le secret client', - 'settings.oauth.modal.machineClient': 'Client machine (sans connexion navigateur)', - 'settings.oauth.modal.machineClientHint': 'Utilise le grant client_credentials — aucune URI de redirection requise. Le token est émis directement via client_id + client_secret et agit en votre nom dans les portées sélectionnées.', - 'settings.oauth.modal.machineClientUsage': 'Obtenir un token : POST /oauth/token avec grant_type=client_credentials, client_id et client_secret. Sans navigateur, sans token de rafraîchissement.', - 'settings.oauth.badge.machine': 'machine', - 'settings.account': 'Compte', - 'settings.about': 'À propos', - 'settings.about.reportBug': 'Signaler un bug', - 'settings.about.reportBugHint': 'Un problème ? Faites-le nous savoir', - 'settings.about.featureRequest': 'Proposer une fonctionnalité', - 'settings.about.featureRequestHint': 'Suggérez une nouvelle fonctionnalité', - 'settings.about.wikiHint': 'Documentation et guides', - 'settings.about.supporters.badge': 'Soutiens Mensuels', - 'settings.about.supporters.title': 'Compagnons de voyage pour TREK', - 'settings.about.supporters.subtitle': 'Pendant que tu planifies ton prochain itinéraire, ces personnes aident à planifier l\'avenir de TREK. Leur contribution mensuelle va directement au développement et aux heures réellement passées — pour que TREK reste Open Source.', - 'settings.about.supporters.since': 'soutien depuis {date}', - 'settings.about.supporters.tierEmpty': 'Sois le premier', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK est un planificateur de voyage auto-hébergé qui vous aide à organiser vos voyages de la première idée au dernier souvenir. Planification journalière, budget, listes de bagages, photos et bien plus — le tout au même endroit, sur votre propre serveur.', - 'settings.about.madeWith': 'Fait avec', - 'settings.about.madeBy': 'par Maurice et une communauté open-source grandissante.', - 'settings.username': 'Nom d\'utilisateur', - 'settings.email': 'E-mail', - 'settings.role': 'Rôle', - 'settings.roleAdmin': 'Administrateur', - 'settings.oidcLinked': 'Lié avec', - 'settings.changePassword': 'Changer le mot de passe', - 'settings.mustChangePassword': 'Vous devez changer votre mot de passe avant de continuer. Veuillez définir un nouveau mot de passe ci-dessous.', - 'settings.currentPassword': 'Mot de passe actuel', - 'settings.currentPasswordRequired': 'Le mot de passe actuel est requis', - 'settings.newPassword': 'Nouveau mot de passe', - 'settings.confirmPassword': 'Confirmer le nouveau mot de passe', - 'settings.updatePassword': 'Mettre à jour le mot de passe', - 'settings.passwordRequired': 'Veuillez saisir le mot de passe actuel et le nouveau', - 'settings.passwordTooShort': 'Le mot de passe doit comporter au moins 8 caractères', - 'settings.passwordMismatch': 'Les mots de passe ne correspondent pas', - 'settings.passwordWeak': 'Le mot de passe doit contenir des majuscules, des minuscules, un chiffre et un caractère spécial', - 'settings.passwordChanged': 'Mot de passe modifié avec succès', - 'settings.deleteAccount': 'Supprimer le compte', - 'settings.deleteAccountTitle': 'Supprimer votre compte ?', - 'settings.deleteAccountWarning': 'Votre compte ainsi que tous vos voyages, lieux et fichiers seront définitivement supprimés. Cette action est irréversible.', - 'settings.deleteAccountConfirm': 'Supprimer définitivement', - 'settings.deleteBlockedTitle': 'Suppression impossible', - 'settings.deleteBlockedMessage': 'Vous êtes le seul administrateur. Promouvez un autre utilisateur en tant qu\'administrateur avant de supprimer votre compte.', - 'settings.roleUser': 'Utilisateur', - 'settings.saveProfile': 'Enregistrer le profil', - 'settings.mfa.title': 'Authentification à deux facteurs (2FA)', - 'settings.mfa.description': 'Ajoute une étape supplémentaire lors de la connexion. Utilisez une application d\'authentification (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'Votre administrateur exige l\'authentification à deux facteurs. Configurez une application d\'authentification ci-dessous avant de continuer.', - 'settings.mfa.backupTitle': 'Codes de secours', - 'settings.mfa.backupDescription': 'Utilisez ces codes à usage unique si vous perdez l\'accès à votre application d\'authentification.', - 'settings.mfa.backupWarning': 'Enregistrez ces codes maintenant. Chaque code n\'est utilisable qu\'une seule fois.', - 'settings.mfa.backupCopy': 'Copier les codes', - 'settings.mfa.backupDownload': 'Télécharger TXT', - 'settings.mfa.backupPrint': 'Imprimer / PDF', - 'settings.mfa.backupCopied': 'Codes de secours copiés', - 'settings.mfa.enabled': '2FA est activé sur votre compte.', - 'settings.mfa.disabled': '2FA n\'est pas activé.', - 'settings.mfa.setup': 'Configurer l\'authentificateur', - 'settings.mfa.scanQr': 'Scannez ce code QR avec votre application ou entrez la clé manuellement.', - 'settings.mfa.secretLabel': 'Clé secrète (saisie manuelle)', - 'settings.mfa.codePlaceholder': 'Code à 6 chiffres', - 'settings.mfa.enable': 'Activer 2FA', - 'settings.mfa.cancelSetup': 'Annuler', - 'settings.mfa.disableTitle': 'Désactiver 2FA', - 'settings.mfa.disableHint': 'Entrez votre mot de passe et un code actuel de votre authentificateur.', - 'settings.mfa.disable': 'Désactiver 2FA', - 'settings.mfa.toastEnabled': 'Authentification à deux facteurs activée', - 'settings.mfa.toastDisabled': 'Authentification à deux facteurs désactivée', - 'settings.mfa.demoBlocked': 'Non disponible en mode démo', - 'settings.toast.mapSaved': 'Paramètres de carte enregistrés', - 'settings.toast.keysSaved': 'Clés API enregistrées', - 'settings.toast.displaySaved': 'Paramètres d\'affichage enregistrés', - 'settings.toast.profileSaved': 'Profil enregistré', - 'settings.uploadAvatar': 'Importer une photo de profil', - 'settings.removeAvatar': 'Supprimer la photo de profil', - 'settings.avatarUploaded': 'Photo de profil mise à jour', - 'settings.avatarRemoved': 'Photo de profil supprimée', - 'settings.avatarError': 'Échec de l\'import', - - // Login - 'login.error': 'Échec de la connexion. Veuillez vérifier vos identifiants.', - 'login.tagline': 'Vos voyages.\nVotre organisation.', - 'login.description': 'Planifiez vos voyages en collaboration avec des cartes interactives, des budgets et la synchronisation en temps réel.', - 'login.features.maps': 'Cartes interactives', - 'login.features.mapsDesc': 'Google Places, itinéraires et regroupement', - 'login.features.realtime': 'Synchronisation en temps réel', - 'login.features.realtimeDesc': 'Planifiez ensemble via WebSocket', - 'login.features.budget': 'Suivi du budget', - 'login.features.budgetDesc': 'Catégories, graphiques et coûts par personne', - 'login.features.collab': 'Collaboration', - 'login.features.collabDesc': 'Multi-utilisateurs avec voyages partagés', - 'login.features.packing': 'Listes de bagages', - 'login.features.packingDesc': 'Catégories, progression et suggestions', - 'login.features.bookings': 'Réservations', - 'login.features.bookingsDesc': 'Vols, hôtels, restaurants et plus', - 'login.features.files': 'Documents', - 'login.features.filesDesc': 'Importez et gérez vos documents', - 'login.features.routes': 'Itinéraires intelligents', - 'login.features.routesDesc': 'Optimisation automatique et export Google Maps', - 'login.selfHosted': 'Auto-hébergé · Open Source · Vos données restent les vôtres', - 'login.title': 'Connexion', - 'login.subtitle': 'Bon retour', - 'login.signingIn': 'Connexion en cours…', - 'login.signIn': 'Se connecter', - 'login.createAdmin': 'Créer un compte administrateur', - 'login.createAdminHint': 'Configurez le premier compte administrateur pour TREK.', - 'login.setNewPassword': 'Définir un nouveau mot de passe', - 'login.setNewPasswordHint': 'Vous devez changer votre mot de passe avant de continuer.', - 'login.createAccount': 'Créer un compte', - 'login.createAccountHint': 'Créez un nouveau compte.', - 'login.creating': 'Création…', - 'login.noAccount': 'Pas encore de compte ?', - 'login.hasAccount': 'Vous avez déjà un compte ?', - 'login.register': 'S\'inscrire', - 'login.emailPlaceholder': 'votre@email.com', - 'login.username': 'Nom d\'utilisateur', - 'login.oidc.registrationDisabled': 'Les inscriptions sont désactivées. Contactez votre administrateur.', - 'login.oidc.noEmail': 'Aucun e-mail reçu du fournisseur.', - 'login.mfaTitle': 'Authentification à deux facteurs', - 'login.mfaSubtitle': 'Entrez le code à 6 chiffres de votre application d\'authentification.', - 'login.mfaCodeLabel': 'Code de vérification', - 'login.mfaCodeRequired': 'Entrez le code de votre application d\'authentification.', - 'login.mfaHint': 'Ouvrez Google Authenticator, Authy ou une autre application TOTP.', - 'login.mfaBack': '← Retour à la connexion', - 'login.mfaVerify': 'Vérifier', - 'login.invalidInviteLink': 'Lien d\'invitation invalide ou expiré', - 'login.oidcFailed': 'Échec de connexion OIDC', - 'login.usernameRequired': 'Le nom d\'utilisateur est obligatoire', - 'login.passwordMinLength': 'Le mot de passe doit comporter au moins 8 caractères', - 'login.forgotPassword': 'Mot de passe oublié ?', - 'login.forgotPasswordTitle': 'Réinitialiser votre mot de passe', - 'login.forgotPasswordBody': 'Entrez l\'adresse e-mail associée à votre compte. Si un compte existe, nous enverrons un lien de réinitialisation.', - 'login.forgotPasswordSubmit': 'Envoyer le lien', - 'login.forgotPasswordSentTitle': 'Vérifiez vos e-mails', - 'login.forgotPasswordSentBody': 'Si un compte existe pour cette adresse, un lien de réinitialisation est en route. Il expire dans 60 minutes.', - 'login.forgotPasswordSmtpHintOff': 'Remarque : votre administrateur n\'a pas configuré SMTP. Le lien de réinitialisation sera écrit dans la console du serveur au lieu d\'être envoyé par e-mail.', - 'login.backToLogin': 'Retour à la connexion', - 'login.newPassword': 'Nouveau mot de passe', - 'login.confirmPassword': 'Confirmer le nouveau mot de passe', - 'login.passwordsDontMatch': 'Les mots de passe ne correspondent pas', - 'login.mfaCode': 'Code 2FA', - 'login.resetPasswordTitle': 'Définir un nouveau mot de passe', - 'login.resetPasswordBody': 'Choisissez un mot de passe fort que vous n\'avez pas encore utilisé ici. 8 caractères minimum.', - 'login.resetPasswordMfaBody': 'Entrez votre code 2FA ou un code de secours pour finaliser la réinitialisation.', - 'login.resetPasswordSubmit': 'Réinitialiser', - 'login.resetPasswordVerify': 'Vérifier et réinitialiser', - 'login.resetPasswordSuccessTitle': 'Mot de passe mis à jour', - 'login.resetPasswordSuccessBody': 'Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.', - 'login.resetPasswordInvalidLink': 'Lien de réinitialisation invalide', - 'login.resetPasswordInvalidLinkBody': 'Ce lien est manquant ou invalide. Demandez-en un nouveau pour continuer.', - 'login.resetPasswordFailed': 'Échec de la réinitialisation. Le lien a peut-être expiré.', - 'login.oidc.tokenFailed': 'L\'authentification a échoué.', - 'login.oidc.invalidState': 'Session invalide. Veuillez réessayer.', - 'login.demoFailed': 'Échec de la connexion démo', - 'login.oidcSignIn': 'Se connecter avec {name}', - 'login.oidcOnly': 'L\'authentification par mot de passe est désactivée. Veuillez vous connecter via votre fournisseur SSO.', - 'login.oidcLoggedOut': 'Vous avez été déconnecté. Reconnectez-vous via votre fournisseur SSO.', - 'login.demoHint': 'Essayez la démo — aucune inscription nécessaire', - - // Register - 'register.passwordMismatch': 'Les mots de passe ne correspondent pas', - 'register.passwordTooShort': 'Le mot de passe doit comporter au moins 8 caractères', - 'register.failed': 'Échec de l\'inscription', - 'register.getStarted': 'Commencer', - 'register.subtitle': 'Créez un compte et commencez à planifier vos voyages de rêve.', - 'register.feature1': 'Plans de voyage illimités', - 'register.feature2': 'Vue carte interactive', - 'register.feature3': 'Gérez les lieux et catégories', - 'register.feature4': 'Suivez les réservations', - 'register.feature5': 'Créez des listes de bagages', - 'register.feature6': 'Stockez photos et fichiers', - 'register.createAccount': 'Créer un compte', - 'register.startPlanning': 'Commencez à planifier vos voyages', - 'register.minChars': 'Min. 6 caractères', - 'register.confirmPassword': 'Confirmer le mot de passe', - 'register.repeatPassword': 'Répéter le mot de passe', - 'register.registering': 'Inscription en cours…', - 'register.register': 'S\'inscrire', - 'register.hasAccount': 'Vous avez déjà un compte ?', - 'register.signIn': 'Se connecter', - - // Admin - 'admin.title': 'Administration', - 'admin.subtitle': 'Gestion des utilisateurs et paramètres système', - 'admin.tabs.users': 'Utilisateurs', - 'admin.tabs.categories': 'Catégories', - 'admin.tabs.backup': 'Sauvegarde', - 'admin.stats.users': 'Utilisateurs', - 'admin.stats.trips': 'Voyages', - 'admin.stats.places': 'Lieux', - 'admin.stats.photos': 'Photos', - 'admin.stats.files': 'Fichiers', - 'admin.table.user': 'Utilisateur', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Rôle', - 'admin.table.created': 'Créé le', - 'admin.table.lastLogin': 'Dernière connexion', - 'admin.table.actions': 'Actions', - 'admin.you': '(Vous)', - 'admin.editUser': 'Modifier l\'utilisateur', - 'admin.newPassword': 'Nouveau mot de passe', - 'admin.newPasswordHint': 'Laissez vide pour conserver le mot de passe actuel', - 'admin.deleteUser': 'Supprimer l\'utilisateur « {name} » ? Tous les voyages seront définitivement supprimés.', - 'admin.deleteUserTitle': 'Supprimer l\'utilisateur', - 'admin.newPasswordPlaceholder': 'Saisir le nouveau mot de passe…', - 'admin.toast.loadError': 'Impossible de charger les données d\'administration', - 'admin.toast.userUpdated': 'Utilisateur mis à jour', - 'admin.toast.updateError': 'Échec de la mise à jour', - 'admin.toast.userDeleted': 'Utilisateur supprimé', - 'admin.toast.deleteError': 'Échec de la suppression', - 'admin.toast.cannotDeleteSelf': 'Impossible de supprimer votre propre compte', - 'admin.toast.userCreated': 'Utilisateur créé', - 'admin.toast.createError': 'Échec de la création de l\'utilisateur', - 'admin.toast.fieldsRequired': 'Le nom d\'utilisateur, l\'e-mail et le mot de passe sont requis', - 'admin.createUser': 'Créer un utilisateur', - 'admin.invite.title': 'Liens d\'invitation', - 'admin.invite.subtitle': 'Créer des liens d\'inscription à usage unique', - 'admin.invite.create': 'Créer un lien', - 'admin.invite.createAndCopy': 'Créer et copier', - 'admin.invite.empty': 'Aucun lien d\'invitation créé', - 'admin.invite.maxUses': 'Utilisations max.', - 'admin.invite.expiry': 'Expire après', - 'admin.invite.uses': 'utilisé(s)', - 'admin.invite.expiresAt': 'expire le', - 'admin.invite.createdBy': 'par', - 'admin.invite.active': 'Actif', - 'admin.invite.expired': 'Expiré', - 'admin.invite.usedUp': 'Épuisé', - 'admin.invite.copied': 'Lien d\'invitation copié', - 'admin.invite.copyLink': 'Copier le lien', - 'admin.invite.deleted': 'Lien d\'invitation supprimé', - 'admin.invite.createError': 'Erreur lors de la création du lien', - 'admin.invite.deleteError': 'Erreur lors de la suppression du lien', - 'admin.tabs.settings': 'Paramètres', - 'admin.allowRegistration': 'Autoriser les inscriptions', - 'admin.allowRegistrationHint': 'Les nouveaux utilisateurs peuvent s\'inscrire eux-mêmes', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Exiger l\'authentification à deux facteurs (2FA)', - 'admin.requireMfaHint': 'Les utilisateurs sans 2FA doivent terminer la configuration dans Paramètres avant d\'utiliser l\'application.', - 'admin.apiKeys': 'Clés API', - 'admin.apiKeysHint': 'Facultatif. Active les données de lieu étendues comme les photos et la météo.', - 'admin.mapsKey': 'Clé API Google Maps', - 'admin.mapsKeyHint': 'Requise pour la recherche de lieux. Obtenez-la sur console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Sans clé API, OpenStreetMap est utilisé pour la recherche de lieux. Avec une clé Google API, les photos, notes et horaires d\'ouverture peuvent également être chargés. Obtenez-en une sur console.cloud.google.com.', - 'admin.recommended': 'Recommandé', - 'admin.weatherKey': 'Clé API OpenWeatherMap', - 'admin.weatherKeyHint': 'Pour les données météo. Gratuit sur openweathermap.org', - 'admin.validateKey': 'Tester', - 'admin.keyValid': 'Connecté', - 'admin.keyInvalid': 'Invalide', - 'admin.keySaved': 'Clés API enregistrées', - 'admin.oidcTitle': 'Authentification unique (OIDC)', - 'admin.oidcSubtitle': 'Autorisez la connexion via des fournisseurs externes comme Google, Apple, Authentik ou Keycloak.', - 'admin.oidcDisplayName': 'Nom d\'affichage', - 'admin.oidcIssuer': 'URL de l\'émetteur', - 'admin.oidcIssuerHint': 'L\'URL de l\'émetteur OpenID Connect du fournisseur. ex. https://accounts.google.com', - 'admin.oidcSaved': 'Configuration OIDC enregistrée', - 'admin.oidcOnlyMode': 'Désactiver l\'authentification par mot de passe', - 'admin.oidcOnlyModeHint': 'Lorsqu\'activé, seule la connexion SSO est autorisée. La connexion et l\'inscription par mot de passe sont bloquées.', - - // File Types - 'admin.fileTypes': 'Types de fichiers autorisés', - 'admin.fileTypesHint': 'Configurez les types de fichiers que les utilisateurs peuvent importer.', - 'admin.fileTypesFormat': 'Extensions séparées par des virgules (ex. jpg,png,pdf,doc). Utilisez * pour autoriser tous les types.', - 'admin.fileTypesSaved': 'Paramètres des types de fichiers enregistrés', - - 'admin.placesPhotos.title': 'Photos de lieux', - 'admin.placesPhotos.subtitle': "Récupère les photos depuis l'API Google Places. Désactivez pour économiser le quota API. Les photos Wikimedia ne sont pas affectées.", - 'admin.placesAutocomplete.title': 'Autocomplétion des lieux', - 'admin.placesAutocomplete.subtitle': "Utilise l'API Google Places pour les suggestions de recherche. Désactivez pour économiser le quota API.", - 'admin.placesDetails.title': 'Détails du lieu', - 'admin.placesDetails.subtitle': "Récupère les informations détaillées du lieu (horaires, note, site web) depuis l'API Google Places. Désactivez pour économiser le quota API.", - 'admin.bagTracking.title': 'Suivi des bagages', - 'admin.bagTracking.subtitle': 'Activer le poids et l\'attribution de bagages pour les articles', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Messagerie en temps réel pour la collaboration', - 'admin.collab.notes.title': 'Notes', - 'admin.collab.notes.subtitle': 'Notes et documents partagés', - 'admin.collab.polls.title': 'Sondages', - 'admin.collab.polls.subtitle': 'Sondages et votes de groupe', - 'admin.collab.whatsnext.title': 'Et ensuite', - 'admin.collab.whatsnext.subtitle': "Suggestions d'activités et prochaines étapes", - 'admin.tabs.config': 'Personnalisation', - 'admin.tabs.defaults': 'Valeurs par défaut', - 'admin.defaultSettings.title': 'Paramètres utilisateur par défaut', - 'admin.defaultSettings.description': "Définissez des valeurs par défaut pour toute l'instance. Les utilisateurs n'ayant pas modifié un paramètre verront ces valeurs. Leurs propres modifications ont toujours la priorité.", - 'admin.defaultSettings.saved': 'Valeur par défaut enregistrée', - 'admin.defaultSettings.reset': 'Réinitialiser à la valeur par défaut intégrée', - 'admin.defaultSettings.resetToBuiltIn': 'réinitialiser', - 'admin.tabs.templates': 'Modèles de bagages', - 'admin.packingTemplates.title': 'Modèles de bagages', - 'admin.packingTemplates.subtitle': 'Créer des listes de bagages réutilisables pour vos voyages', - 'admin.packingTemplates.create': 'Nouveau modèle', - 'admin.packingTemplates.namePlaceholder': 'Nom du modèle (ex. Vacances à la plage)', - 'admin.packingTemplates.empty': 'Aucun modèle créé', - 'admin.packingTemplates.items': 'articles', - 'admin.packingTemplates.categories': 'catégories', - 'admin.packingTemplates.itemName': 'Nom de l\'article', - 'admin.packingTemplates.itemCategory': 'Catégorie', - 'admin.packingTemplates.categoryName': 'Nom de catégorie (ex. Vêtements)', - 'admin.packingTemplates.addCategory': 'Ajouter une catégorie', - 'admin.packingTemplates.created': 'Modèle créé', - 'admin.packingTemplates.deleted': 'Modèle supprimé', - 'admin.packingTemplates.loadError': 'Erreur de chargement des modèles', - 'admin.packingTemplates.createError': 'Erreur de création du modèle', - 'admin.packingTemplates.deleteError': 'Erreur de suppression du modèle', - 'admin.packingTemplates.saveError': 'Erreur de sauvegarde', - - // Addons - 'admin.tabs.addons': 'Extensions', - 'admin.addons.title': 'Extensions', - 'admin.addons.subtitle': 'Activez ou désactivez des fonctionnalités pour personnaliser votre expérience TREK.', - 'admin.addons.catalog.memories.name': 'Photos (Immich)', - 'admin.addons.catalog.memories.description': 'Partagez vos photos de voyage via votre instance Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Protocole de contexte de modèle pour l\'intégration d\'assistants IA', - 'admin.addons.catalog.packing.name': 'Listes', - 'admin.addons.catalog.packing.description': 'Listes de bagages et tâches à faire pour vos voyages', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Suivez les dépenses et planifiez votre budget de voyage', - 'admin.addons.catalog.documents.name': 'Documents', - 'admin.addons.catalog.documents.description': 'Stockez et gérez vos documents de voyage', - 'admin.addons.catalog.vacay.name': 'Vacances', - 'admin.addons.catalog.vacay.description': 'Planificateur de vacances personnel avec vue calendrier', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Carte du monde avec pays visités et statistiques de voyage', - 'admin.addons.catalog.collab.name': 'Collaboration', - 'admin.addons.catalog.collab.description': 'Notes en temps réel, sondages et chat pour la planification de voyage', - 'admin.addons.subtitleBefore': 'Activez ou désactivez des fonctionnalités pour personnaliser votre expérience ', - 'admin.addons.subtitleAfter': '.', - 'admin.addons.enabled': 'Activé', - 'admin.addons.disabled': 'Désactivé', - 'admin.addons.type.trip': 'Voyage', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Intégration', - 'admin.addons.tripHint': 'Disponible comme onglet dans chaque voyage', - 'admin.addons.globalHint': 'Disponible comme section autonome dans la navigation principale', - 'admin.addons.integrationHint': 'Services backend et intégrations API sans page dédiée', - 'admin.addons.toast.updated': 'Extension mise à jour', - 'admin.addons.toast.error': 'Échec de la mise à jour de l\'extension', - 'admin.addons.noAddons': 'Aucune extension disponible', - // Weather info - 'admin.weather.title': 'Données météo', - 'admin.weather.badge': 'Depuis le 24 mars 2026', - 'admin.weather.description': 'TREK utilise Open-Meteo comme source de données météo. Open-Meteo est un service météo gratuit et open source — aucune clé API requise.', - 'admin.weather.forecast': 'Prévisions sur 16 jours', - 'admin.weather.forecastDesc': 'Auparavant 5 jours (OpenWeatherMap)', - 'admin.weather.climate': 'Données climatiques historiques', - 'admin.weather.climateDesc': 'Moyennes des 85 dernières années pour les jours au-delà des prévisions de 16 jours', - 'admin.weather.requests': '10 000 requêtes / jour', - 'admin.weather.requestsDesc': 'Gratuit, aucune clé API requise', - 'admin.weather.locationHint': 'La météo est basée sur le premier lieu avec des coordonnées de chaque jour. Si aucun lieu n\'est attribué à un jour, un lieu de la liste est utilisé comme référence.', - - 'admin.tabs.audit': 'Audit', - - 'admin.audit.subtitle': 'Événements sensibles de sécurité et d\'administration (sauvegardes, utilisateurs, 2FA, paramètres).', - 'admin.audit.empty': 'Aucune entrée d\'audit.', - 'admin.audit.refresh': 'Actualiser', - 'admin.audit.loadMore': 'Charger plus', - 'admin.audit.showing': '{count} chargées · {total} au total', - 'admin.audit.col.time': 'Heure', - 'admin.audit.col.user': 'Utilisateur', - 'admin.audit.col.action': 'Action', - 'admin.audit.col.resource': 'Ressource', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Détails', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'Accès MCP', - 'admin.mcpTokens.title': 'Accès MCP', - 'admin.mcpTokens.subtitle': 'Gérer les sessions OAuth et les tokens API de tous les utilisateurs', - 'admin.mcpTokens.sectionTitle': 'Tokens API', - 'admin.mcpTokens.owner': 'Propriétaire', - 'admin.mcpTokens.tokenName': 'Nom du token', - 'admin.mcpTokens.created': 'Créé', - 'admin.mcpTokens.lastUsed': 'Dernière utilisation', - 'admin.mcpTokens.never': 'Jamais', - 'admin.mcpTokens.empty': 'Aucun token MCP n\'a encore été créé', - 'admin.mcpTokens.deleteTitle': 'Supprimer le token', - 'admin.mcpTokens.deleteMessage': 'Ce token sera révoqué immédiatement. L\'utilisateur perdra l\'accès MCP via ce token.', - 'admin.mcpTokens.deleteSuccess': 'Token supprimé', - 'admin.mcpTokens.deleteError': 'Impossible de supprimer le token', - 'admin.mcpTokens.loadError': 'Impossible de charger les tokens', - 'admin.oauthSessions.sectionTitle': 'Sessions OAuth', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Propriétaire', - 'admin.oauthSessions.scopes': 'Portées', - 'admin.oauthSessions.created': 'Créé', - 'admin.oauthSessions.empty': 'Aucune session OAuth active', - 'admin.oauthSessions.revokeTitle': 'Révoquer la session', - 'admin.oauthSessions.revokeMessage': 'Cette session OAuth sera révoquée immédiatement. Le client perdra l\'accès MCP.', - 'admin.oauthSessions.revokeSuccess': 'Session révoquée', - 'admin.oauthSessions.revokeError': 'Impossible de révoquer la session', - 'admin.oauthSessions.loadError': 'Impossible de charger les sessions OAuth', - - // GitHub - 'admin.tabs.github': 'GitHub', - 'admin.github.title': 'Historique des versions', - 'admin.github.subtitle': 'Dernières mises à jour de {repo}', - 'admin.github.latest': 'Dernière', - 'admin.github.prerelease': 'Pré-version', - 'admin.github.showDetails': 'Afficher les détails', - 'admin.github.hideDetails': 'Masquer les détails', - 'admin.github.loadMore': 'Charger plus', - 'admin.github.loading': 'Chargement…', - 'admin.github.support': 'Aidez à poursuivre le développement de TREK', - 'admin.github.error': 'Impossible de charger les versions', - 'admin.github.by': 'par', - - 'admin.update.available': 'Mise à jour disponible', - 'admin.update.text': 'TREK {version} est disponible. Vous utilisez {current}.', - 'admin.update.button': 'Voir sur GitHub', - 'admin.update.install': 'Installer la mise à jour', - 'admin.update.confirmTitle': 'Installer la mise à jour ?', - 'admin.update.confirmText': 'TREK sera mis à jour de {current} vers {version}. Le serveur redémarrera automatiquement ensuite.', - 'admin.update.dataInfo': 'Toutes vos données (voyages, utilisateurs, clés API, importations, Vacances, Atlas, budgets) seront préservées.', - 'admin.update.warning': 'L\'application sera brièvement indisponible pendant le redémarrage.', - 'admin.update.confirm': 'Mettre à jour maintenant', - 'admin.update.installing': 'Mise à jour…', - 'admin.update.success': 'Mise à jour installée ! Le serveur redémarre…', - 'admin.update.failed': 'Échec de la mise à jour', - 'admin.update.backupHint': 'Nous recommandons de créer une sauvegarde avant la mise à jour.', - 'admin.update.backupLink': 'Aller aux sauvegardes', - 'admin.update.howTo': 'Comment mettre à jour', - 'admin.update.dockerText': 'Votre instance TREK fonctionne dans Docker. Pour mettre à jour vers {version}, exécutez les commandes suivantes sur votre serveur :', - 'admin.update.reloadHint': 'Veuillez recharger la page dans quelques secondes.', - - // Vacay addon - 'vacay.subtitle': 'Planifiez et gérez vos jours de congés', - 'vacay.settings': 'Paramètres', - 'vacay.year': 'Année', - 'vacay.addYear': 'Ajouter l\'année suivante', - 'vacay.addPrevYear': 'Ajouter l\'année précédente', - 'vacay.removeYear': 'Supprimer l\'année', - 'vacay.removeYearConfirm': 'Supprimer {year} ?', - 'vacay.removeYearHint': 'Toutes les entrées de vacances et jours fériés d\'entreprise de cette année seront définitivement supprimés.', - 'vacay.remove': 'Supprimer', - 'vacay.persons': 'Personnes', - 'vacay.noPersons': 'Aucune personne ajoutée', - 'vacay.addPerson': 'Ajouter une personne', - 'vacay.editPerson': 'Modifier la personne', - 'vacay.removePerson': 'Supprimer la personne', - 'vacay.removePersonConfirm': 'Supprimer {name} ?', - 'vacay.removePersonHint': 'Toutes les entrées de vacances de cette personne seront définitivement supprimées.', - 'vacay.personName': 'Nom', - 'vacay.personNamePlaceholder': 'Saisir le nom', - 'vacay.color': 'Couleur', - 'vacay.add': 'Ajouter', - 'vacay.legend': 'Légende', - 'vacay.publicHoliday': 'Jour férié', - 'vacay.companyHoliday': 'Jour férié d\'entreprise', - 'vacay.weekend': 'Week-end', - 'vacay.modeVacation': 'Vacances', - 'vacay.modeCompany': 'Jour férié d\'entreprise', - 'vacay.entitlement': 'Droits', - 'vacay.entitlementDays': 'Jours', - 'vacay.used': 'Utilisés', - 'vacay.remaining': 'Restants', - 'vacay.carriedOver': 'de {year}', - 'vacay.weekendDays': 'Jours de week-end', - 'vacay.mon': 'Lun', - 'vacay.tue': 'Mar', - 'vacay.wed': 'Mer', - 'vacay.thu': 'Jeu', - 'vacay.fri': 'Ven', - 'vacay.sat': 'Sam', - 'vacay.sun': 'Dim', - 'vacay.blockWeekends': 'Bloquer les week-ends', - 'vacay.blockWeekendsHint': 'Empêcher les entrées de vacances les samedis et dimanches', - 'vacay.publicHolidays': 'Jours fériés', - 'vacay.publicHolidaysHint': 'Marquer les jours fériés dans le calendrier', - 'vacay.selectCountry': 'Sélectionner un pays', - 'vacay.selectRegion': 'Sélectionner une région (facultatif)', - 'vacay.companyHolidays': 'Jours fériés d\'entreprise', - 'vacay.companyHolidaysHint': 'Autoriser le marquage des jours fériés d\'entreprise', - 'vacay.companyHolidaysNoDeduct': 'Les jours fériés d\'entreprise ne sont pas déduits des jours de vacances.', - 'vacay.weekStart': 'La semaine commence le', - 'vacay.weekStartHint': 'Choisissez si la semaine commence le lundi ou le dimanche', - 'vacay.carryOver': 'Report', - 'vacay.carryOverHint': 'Reporter automatiquement les jours de vacances restants à l\'année suivante', - 'vacay.sharing': 'Partage', - 'vacay.sharingHint': 'Partagez votre plan de vacances avec d\'autres utilisateurs TREK', - 'vacay.owner': 'Propriétaire', - 'vacay.shareEmailPlaceholder': 'E-mail de l\'utilisateur TREK', - 'vacay.shareSuccess': 'Plan partagé avec succès', - 'vacay.shareError': 'Impossible de partager le plan', - 'vacay.dissolve': 'Séparer les calendriers', - 'vacay.dissolveHint': 'Séparer à nouveau les calendriers. Vos entrées seront conservées.', - 'vacay.dissolveAction': 'Dissoudre', - 'vacay.dissolved': 'Calendrier séparé', - 'vacay.fusedWith': 'Partagé avec', - 'vacay.you': 'vous', - 'vacay.noData': 'Aucune donnée', - 'vacay.changeColor': 'Changer la couleur', - 'vacay.inviteUser': 'Inviter un utilisateur', - 'vacay.inviteHint': 'Invitez un autre utilisateur TREK à partager un calendrier de vacances combiné.', - 'vacay.selectUser': 'Sélectionner un utilisateur', - 'vacay.sendInvite': 'Envoyer l\'invitation', - 'vacay.inviteSent': 'Invitation envoyée', - 'vacay.inviteError': 'Impossible d\'envoyer l\'invitation', - 'vacay.pending': 'en attente', - 'vacay.noUsersAvailable': 'Aucun utilisateur disponible', - 'vacay.accept': 'Accepter', - 'vacay.decline': 'Refuser', - 'vacay.acceptFusion': 'Accepter et fusionner', - 'vacay.inviteTitle': 'Demande de fusion', - 'vacay.inviteWantsToFuse': 'souhaite partager un calendrier de vacances avec vous.', - 'vacay.fuseInfo1': 'Vous verrez tous les deux toutes les entrées de vacances dans un calendrier partagé.', - 'vacay.fuseInfo2': 'Les deux parties peuvent créer et modifier des entrées pour l\'autre.', - 'vacay.fuseInfo3': 'Les deux parties peuvent supprimer des entrées et modifier les droits aux vacances.', - 'vacay.fuseInfo4': 'Les paramètres comme les jours fériés et les jours d\'entreprise sont partagés.', - 'vacay.fuseInfo5': 'La fusion peut être dissoute à tout moment par l\'une ou l\'autre partie. Vos entrées seront préservées.', - 'vacay.addCalendar': 'Ajouter un calendrier', - 'vacay.calendarColor': 'Couleur', - 'vacay.calendarLabel': 'Libellé', - 'vacay.noCalendars': 'Aucun calendrier', - 'nav.myTrips': 'Mes voyages', - - // Atlas addon - 'atlas.subtitle': 'Votre empreinte de voyage à travers le monde', - 'atlas.countries': 'Pays', - 'atlas.trips': 'Voyages', - 'atlas.places': 'Lieux', - 'atlas.days': 'Jours', - 'atlas.visitedCountries': 'Pays visités', - 'atlas.cities': 'Villes', - 'atlas.noData': 'Aucune donnée de voyage', - 'atlas.noDataHint': 'Créez un voyage et ajoutez des lieux pour voir votre carte du monde', - 'atlas.lastTrip': 'Dernier voyage', - 'atlas.nextTrip': 'Prochain voyage', - 'atlas.daysLeft': 'jours restants', - 'atlas.streak': 'Série', - 'atlas.year': 'an', - 'atlas.years': 'ans', - 'atlas.yearInRow': 'année consécutive', - 'atlas.yearsInRow': 'années consécutives', - 'atlas.tripIn': 'voyage en', - 'atlas.tripsIn': 'voyages en', - 'atlas.since': 'depuis', - 'atlas.europe': 'Europe', - 'atlas.asia': 'Asie', - 'atlas.northAmerica': 'Amérique du N.', - 'atlas.southAmerica': 'Amérique du S.', - 'atlas.africa': 'Afrique', - 'atlas.oceania': 'Océanie', - 'atlas.other': 'Autre', - 'atlas.firstVisit': 'Premier voyage', - 'atlas.lastVisitLabel': 'Dernier voyage', - 'atlas.tripSingular': 'Voyage', - 'atlas.tripPlural': 'Voyages', - 'atlas.placeVisited': 'Lieu visité', - 'atlas.placesVisited': 'Lieux visités', - 'atlas.statsTab': 'Statistiques', - 'atlas.bucketTab': 'Bucket List', - 'atlas.addBucket': 'Ajouter à la bucket list', - 'atlas.bucketNamePlaceholder': 'Lieu ou destination...', - 'atlas.bucketNotesPlaceholder': 'Notes (optionnel)', - 'atlas.bucketEmpty': 'Votre bucket list est vide', - 'atlas.bucketEmptyHint': 'Ajoutez des lieux que vous rêvez de visiter', - 'atlas.unmark': 'Retirer', - 'atlas.confirmMark': 'Marquer ce pays comme visité ?', - 'atlas.confirmUnmark': 'Retirer ce pays de votre liste ?', - 'atlas.confirmUnmarkRegion': 'Retirer cette région de votre liste ?', - 'atlas.markVisited': 'Marquer comme visité', - 'atlas.markVisitedHint': 'Ajouter ce pays à votre liste de visités', - 'atlas.markRegionVisitedHint': 'Ajouter cette région à votre liste de visités', - 'atlas.addToBucket': 'Ajouter à la bucket list', - 'atlas.addPoi': 'Ajouter un lieu', - 'atlas.searchCountry': 'Rechercher un pays…', - 'atlas.month': 'Mois', - 'atlas.addToBucketHint': 'Sauvegarder comme lieu à visiter', - 'atlas.bucketWhen': 'Quand prévoyez-vous d\'y aller ?', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transports', - 'trip.tabs.reservations': 'Réservations', - 'trip.tabs.reservationsShort': 'Résa', - 'trip.tabs.packing': 'Liste de bagages', - 'trip.tabs.packingShort': 'Bagages', - 'trip.tabs.lists': 'Listes', - 'trip.tabs.listsShort': 'Listes', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'Fichiers', - 'trip.loading': 'Chargement du voyage…', - 'trip.loadingPhotos': 'Chargement des photos des lieux...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Lieux', - 'trip.toast.placeUpdated': 'Lieu mis à jour', - 'trip.toast.placeAdded': 'Lieu ajouté', - 'trip.toast.placeDeleted': 'Lieu supprimé', - 'trip.toast.selectDay': 'Veuillez d\'abord sélectionner un jour', - 'trip.toast.assignedToDay': 'Lieu attribué au planning', - 'trip.toast.reorderError': 'Échec de la réorganisation', - 'trip.toast.reservationUpdated': 'Réservation mise à jour', - 'trip.toast.reservationAdded': 'Réservation ajoutée', - 'trip.toast.deleted': 'Supprimé', - 'trip.confirm.deletePlace': 'Voulez-vous vraiment supprimer ce lieu ?', - 'trip.confirm.deletePlaces': 'Supprimer {count} lieux?', - 'trip.toast.placesDeleted': '{count} lieux supprimés', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Aucun lieu prévu pour ce jour', - 'dayplan.addNote': 'Ajouter une note', - 'dayplan.editNote': 'Modifier la note', - 'dayplan.noteAdd': 'Ajouter une note', - 'dayplan.noteEdit': 'Modifier la note', - 'dayplan.noteTitle': 'Note', - 'dayplan.noteSubtitle': 'Note du jour', - 'dayplan.totalCost': 'Coût total', - 'dayplan.days': 'Jours', - 'dayplan.dayN': 'Jour {n}', - 'dayplan.calculating': 'Calcul en cours…', - 'dayplan.route': 'Itinéraire', - 'dayplan.optimize': 'Optimiser', - 'dayplan.optimized': 'Itinéraire optimisé', - 'dayplan.routeError': 'Impossible de calculer l\'itinéraire', - 'dayplan.toast.needTwoPlaces': 'Au moins deux lieux nécessaires pour optimiser l\'itinéraire', - 'dayplan.toast.routeOptimized': 'Itinéraire optimisé', - 'dayplan.toast.noGeoPlaces': 'Aucun lieu avec des coordonnées trouvé pour le calcul d\'itinéraire', - 'dayplan.confirmed': 'Confirmé', - 'dayplan.pendingRes': 'En attente', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Exporter le plan du jour en PDF', - 'dayplan.pdfError': 'Échec de l\'export PDF', - 'dayplan.cannotReorderTransport': 'Les réservations avec une heure fixe ne peuvent pas être réorganisées', - 'dayplan.confirmRemoveTimeTitle': 'Supprimer l\'heure ?', - 'dayplan.confirmRemoveTimeBody': 'Ce lieu a une heure fixe ({time}). Le déplacer supprimera l\'heure et permettra un tri libre.', - 'dayplan.confirmRemoveTimeAction': 'Supprimer l\'heure et déplacer', - 'dayplan.cannotDropOnTimed': 'Les éléments ne peuvent pas être placés entre des entrées à heure fixe', - 'dayplan.cannotBreakChronology': 'Cela briserait l\'ordre chronologique des éléments et réservations planifiés', - - // Places Sidebar - 'places.addPlace': 'Ajouter un lieu/activité', - 'places.importFile': 'Importer un fichier', - 'places.sidebarDrop': 'Déposer pour importer', - 'places.importFileHint': 'Importez des fichiers .gpx, .kml ou .kmz depuis des outils comme Google My Maps, Google Earth ou un traceur GPS.', - 'places.importFileDropHere': 'Cliquez pour sélectionner un fichier ou glissez-déposez ici', - 'places.importFileDropActive': 'Déposez le fichier pour le sélectionner', - 'places.importFileUnsupported': 'Type de fichier non pris en charge. Utilisez .gpx, .kml ou .kmz.', - 'places.importFileTooLarge': 'Le fichier est trop volumineux. La taille maximale est de {maxMb} MB.', - 'places.importFileError': 'Importation échouée', - 'places.importAllSkipped': 'Tous les lieux étaient déjà dans le voyage.', - 'places.gpxImported': '{count} lieux importés depuis GPX', - 'places.gpxImportTypes': 'Que voulez-vous importer?', - 'places.gpxImportWaypoints': 'Points de passage', - 'places.gpxImportRoutes': 'Itinéraires', - 'places.gpxImportTracks': 'Traces (avec géométrie)', - 'places.gpxImportNoneSelected': 'Sélectionnez au moins un type à importer.', - 'places.kmlImportTypes': 'Que souhaitez-vous importer ?', - 'places.kmlImportPoints': 'Points (Placemarks)', - 'places.kmlImportPaths': 'Chemins (LineStrings)', - 'places.kmlImportNoneSelected': 'Sélectionnez au moins un type.', - 'places.selectionCount': '{count} sélectionné(s)', - 'places.deleteSelected': 'Supprimer la sélection', - 'places.kmlKmzImported': '{count} lieux importés depuis KMZ/KML', - 'places.urlResolved': 'Lieu importé depuis l\'URL', - 'places.importList': 'Import de liste', - 'places.kmlKmzSummaryValues': 'Placemarks : {total} • Importés : {created} • Ignorés : {skipped}', - 'places.importGoogleList': 'Liste Google', - 'places.importNaverList': 'Liste Naver', - 'places.googleListHint': 'Collez un lien de liste Google Maps partagée pour importer tous les lieux.', - 'places.googleListImported': '{count} lieux importés depuis "{list}"', - 'places.googleListError': 'Impossible d\'importer la liste Google Maps', - 'places.naverListHint': 'Collez un lien de liste Naver Maps partagée pour importer tous les lieux.', - 'places.naverListImported': '{count} lieux importés depuis "{list}"', - 'places.naverListError': 'Impossible d\'importer la liste Naver Maps', - 'places.viewDetails': 'Voir les détails', - 'places.assignToDay': 'Ajouter à quel jour ?', - 'places.all': 'Tous', - 'places.unplanned': 'Non planifiés', - 'places.filterTracks': 'Traces', - 'places.search': 'Rechercher des lieux…', - 'places.allCategories': 'Toutes les catégories', - 'places.categoriesSelected': 'catégories', - 'places.clearFilter': 'Effacer le filtre', - 'places.count': '{count} lieux', - 'places.countSingular': '1 lieu', - 'places.allPlanned': 'Tous les lieux sont planifiés', - 'places.noneFound': 'Aucun lieu trouvé', - 'places.editPlace': 'Modifier le lieu', - 'places.formName': 'Nom', - 'places.formNamePlaceholder': 'ex. Tour Eiffel', - 'places.formDescription': 'Description', - 'places.formDescriptionPlaceholder': 'Brève description…', - 'places.formAddress': 'Adresse', - 'places.formAddressPlaceholder': 'Rue, ville, pays', - 'places.formLat': 'Latitude (ex. 48.8566)', - 'places.formLng': 'Longitude (ex. 2.3522)', - 'places.formCategory': 'Catégorie', - 'places.noCategory': 'Sans catégorie', - 'places.categoryNamePlaceholder': 'Nom de la catégorie', - 'places.formTime': 'Heure', - 'places.startTime': 'Début', - 'places.endTime': 'Fin', - 'places.endTimeBeforeStart': 'L\'heure de fin est antérieure à l\'heure de début', - 'places.timeCollision': 'Chevauchement horaire avec :', - 'places.formWebsite': 'Site web', - 'places.formNotes': 'Notes', - 'places.formNotesPlaceholder': 'Notes personnelles…', - 'places.formReservation': 'Réservation', - 'places.reservationNotesPlaceholder': 'Notes de réservation, numéro de confirmation…', - 'places.mapsSearchPlaceholder': 'Rechercher des lieux…', - 'places.mapsSearchError': 'La recherche de lieu a échoué.', - 'places.loadingDetails': 'Chargement des détails du lieu…', - 'places.osmHint': 'Recherche via OpenStreetMap (pas de photos, horaires ni notes). Ajoutez une clé API Google dans les paramètres pour plus de détails.', - 'places.osmActive': 'Recherche via OpenStreetMap (pas de photos, notes ni horaires). Ajoutez une clé API Google dans les paramètres pour des données enrichies.', - 'places.categoryCreateError': 'Impossible de créer la catégorie', - 'places.nameRequired': 'Veuillez saisir un nom', - 'places.saveError': 'Échec de l\'enregistrement', - // Place Inspector - 'inspector.opened': 'Ouvert', - 'inspector.closed': 'Fermé', - 'inspector.openingHours': 'Horaires d\'ouverture', - 'inspector.showHours': 'Afficher les horaires', - 'inspector.files': 'Fichiers', - 'inspector.filesCount': '{count} fichiers', - 'inspector.removeFromDay': 'Retirer du jour', - 'inspector.remove': 'Supprimer', - 'inspector.addToDay': 'Ajouter au jour', - 'inspector.confirmedRes': 'Réservation confirmée', - 'inspector.pendingRes': 'Réservation en attente', - 'inspector.google': 'Ouvrir dans Google Maps', - 'inspector.website': 'Ouvrir le site web', - 'inspector.addRes': 'Réservation', - 'inspector.editRes': 'Modifier la réservation', - 'inspector.participants': 'Participants', - 'inspector.trackStats': 'Données du parcours', - - // Reservations - 'reservations.title': 'Réservations', - 'reservations.empty': 'Aucune réservation', - 'reservations.emptyHint': 'Ajoutez des réservations pour les vols, hôtels et plus', - 'reservations.add': 'Ajouter une réservation', - 'reservations.addManual': 'Réservation manuelle', - 'reservations.placeHint': 'Conseil : les réservations sont mieux créées directement depuis un lieu pour les lier à votre plan du jour.', - 'reservations.confirmed': 'Confirmée', - 'reservations.pending': 'En attente', - 'reservations.summary': '{confirmed} confirmées, {pending} en attente', - 'reservations.fromPlan': 'Du plan', - 'reservations.showFiles': 'Afficher les fichiers', - 'reservations.editTitle': 'Modifier la réservation', - 'reservations.status': 'Statut', - 'reservations.datetime': 'Date et heure', - 'reservations.startTime': 'Heure de début', - 'reservations.endTime': 'Heure de fin', - 'reservations.date': 'Date', - 'reservations.time': 'Heure', - 'reservations.timeAlt': 'Heure (alternative, ex. 19h30)', - 'reservations.notes': 'Notes', - 'reservations.notesPlaceholder': 'Notes supplémentaires…', - 'reservations.meta.airline': 'Compagnie aérienne', - 'reservations.meta.flightNumber': 'N° de vol', - 'reservations.meta.from': 'De', - 'reservations.meta.to': 'À', - 'reservations.needsReview': 'Vérifier', - 'reservations.needsReviewHint': 'L\'aéroport n\'a pas pu être identifié automatiquement — veuillez confirmer l\'emplacement.', - 'reservations.searchLocation': 'Rechercher une gare, un port, une adresse…', - 'airport.searchPlaceholder': 'Code ou ville de l\'aéroport (ex. FRA)', - 'map.connections': 'Connexions', - 'map.showConnections': 'Afficher les itinéraires', - 'map.hideConnections': 'Masquer les itinéraires', - 'settings.bookingLabels': 'Étiquettes des itinéraires', - 'settings.bookingLabelsHint': 'Affiche les noms des gares / aéroports sur la carte. Si désactivé, seule l\'icône est affichée.', - 'reservations.meta.trainNumber': 'N° de train', - 'reservations.meta.platform': 'Quai', - 'reservations.meta.seat': 'Place', - 'reservations.meta.checkIn': 'Arrivée', - 'reservations.meta.checkInUntil': "Check-in jusqu'à", - 'reservations.meta.checkOut': 'Départ', - 'reservations.meta.linkAccommodation': 'Hébergement', - 'reservations.meta.pickAccommodation': 'Lier à un hébergement', - 'reservations.meta.noAccommodation': 'Aucun', - 'reservations.meta.hotelPlace': 'Hébergement', - 'reservations.meta.pickHotel': 'Sélectionner un hébergement', - 'reservations.meta.fromDay': 'Du', - 'reservations.meta.toDay': 'Au', - 'reservations.meta.selectDay': 'Sélectionner un jour', - 'reservations.type.flight': 'Vol', - 'reservations.type.hotel': 'Hébergement', - 'reservations.type.restaurant': 'Restaurant', - 'reservations.type.train': 'Train', - 'reservations.type.car': 'Voiture', - 'reservations.type.cruise': 'Croisière', - 'reservations.type.event': 'Événement', - 'reservations.type.tour': 'Visite', - 'reservations.type.other': 'Autre', - 'reservations.confirm.delete': 'Voulez-vous vraiment supprimer la réservation « {name} » ?', - 'reservations.confirm.deleteTitle': 'Supprimer la réservation ?', - 'reservations.confirm.deleteBody': '« {name} » sera définitivement supprimé.', - 'reservations.toast.updated': 'Réservation mise à jour', - 'reservations.toast.removed': 'Réservation supprimée', - 'reservations.toast.fileUploaded': 'Fichier importé', - 'reservations.toast.uploadError': 'Échec de l\'import', - 'reservations.newTitle': 'Nouvelle réservation', - 'reservations.bookingType': 'Type de réservation', - 'reservations.titleLabel': 'Titre', - 'reservations.titlePlaceholder': 'ex. Lufthansa LH123, Hôtel Adlon, …', - 'reservations.locationAddress': 'Lieu / Adresse', - 'reservations.locationPlaceholder': 'Adresse, aéroport, hôtel…', - 'reservations.confirmationCode': 'Code de réservation', - 'reservations.confirmationPlaceholder': 'ex. ABC12345', - 'reservations.day': 'Jour', - 'reservations.noDay': 'Aucun jour', - 'reservations.place': 'Lieu', - 'reservations.noPlace': 'Aucun lieu', - 'reservations.pendingSave': 'sera enregistré…', - 'reservations.uploading': 'Importation…', - 'reservations.attachFile': 'Joindre un fichier', - 'reservations.linkExisting': 'Lier un fichier existant', - 'reservations.toast.saveError': 'Échec de l\'enregistrement', - 'reservations.toast.updateError': 'Échec de la mise à jour', - 'reservations.toast.deleteError': 'Échec de la suppression', - 'reservations.confirm.remove': 'Supprimer la réservation pour « {name} » ?', - 'reservations.linkAssignment': 'Lier à l\'affectation du jour', - 'reservations.pickAssignment': 'Sélectionnez une affectation de votre plan…', - 'reservations.noAssignment': 'Aucun lien (autonome)', - 'reservations.price': 'Prix', - 'reservations.budgetCategory': 'Catégorie budgétaire', - 'reservations.budgetCategoryPlaceholder': 'ex. Transport, Hébergement', - 'reservations.budgetCategoryAuto': 'Auto (selon le type de réservation)', - 'reservations.budgetHint': 'Une entrée budgétaire sera créée automatiquement lors de l\'enregistrement.', - 'reservations.departureDate': 'Départ', - 'reservations.arrivalDate': 'Arrivée', - 'reservations.departureTime': 'Heure dép.', - 'reservations.arrivalTime': 'Heure arr.', - 'reservations.pickupDate': 'Prise en charge', - 'reservations.returnDate': 'Restitution', - 'reservations.pickupTime': 'Heure prise en charge', - 'reservations.returnTime': 'Heure restitution', - 'reservations.endDate': 'Date de fin', - 'reservations.meta.departureTimezone': 'TZ dép.', - 'reservations.meta.arrivalTimezone': 'TZ arr.', - 'reservations.span.departure': 'Départ', - 'reservations.span.arrival': 'Arrivée', - 'reservations.span.inTransit': 'En transit', - 'reservations.span.pickup': 'Prise en charge', - 'reservations.span.return': 'Restitution', - 'reservations.span.active': 'Actif', - 'reservations.span.start': 'Début', - 'reservations.span.end': 'Fin', - 'reservations.span.ongoing': 'En cours', - 'reservations.validation.endBeforeStart': 'La date/heure de fin doit être postérieure à la date/heure de début', - 'reservations.addBooking': 'Ajouter une réservation', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'Exporter CSV', - 'budget.emptyTitle': 'Aucun budget créé', - 'budget.emptyText': 'Créez des catégories et des entrées pour planifier votre budget de voyage', - 'budget.emptyPlaceholder': 'Nom de la catégorie…', - 'budget.createCategory': 'Créer une catégorie', - 'budget.category': 'Catégorie', - 'budget.categoryName': 'Nom de la catégorie', - 'budget.table.name': 'Nom', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Personnes', - 'budget.table.days': 'Jours', - 'budget.table.perPerson': 'Par personne', - 'budget.table.perDay': 'Par jour', - 'budget.table.perPersonDay': 'P. p / Jour', - 'budget.table.note': 'Note', - 'budget.table.date': 'Date', - 'budget.newEntry': 'Nouvelle entrée', - 'budget.defaultEntry': 'Nouvelle entrée', - 'budget.defaultCategory': 'Nouvelle catégorie', - 'budget.total': 'Total', - 'budget.totalBudget': 'Budget total', - 'budget.byCategory': 'Par catégorie', - 'budget.editTooltip': 'Cliquez pour modifier', - 'budget.linkedToReservation': 'Lié à une réservation — modifiez le nom depuis celle-ci', - 'budget.confirm.deleteCategory': 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} entrées ?', - 'budget.deleteCategory': 'Supprimer la catégorie', - 'budget.perPerson': 'Par personne', - 'budget.paid': 'Payé', - 'budget.open': 'Ouvert', - 'budget.noMembers': 'Aucun membre assigné', - 'budget.settlement': 'Règlement', - 'budget.settlementInfo': 'Cliquez sur l\'avatar d\'un membre sur un poste budgétaire pour le marquer en vert — cela signifie qu\'il a payé. Le règlement indique ensuite qui doit combien à qui.', - 'budget.netBalances': 'Soldes nets', - - // Files - 'files.title': 'Fichiers', - 'files.pageTitle': 'Fichiers et documents', - 'files.subtitle': '{count} fichiers pour {trip}', - 'files.download': 'Télécharger', - 'files.openError': "Impossible d'ouvrir le fichier", - 'files.downloadPdf': 'Télécharger le PDF', - 'files.count': '{count} fichiers', - 'files.countSingular': '1 fichier', - 'files.uploaded': '{count} importés', - 'files.uploadError': 'Échec de l\'import', - 'files.dropzone': 'Déposez les fichiers ici', - 'files.dropzoneHint': 'ou cliquez pour parcourir', - 'files.allowedTypes': 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 Mo', - 'files.uploading': 'Importation…', - 'files.filterAll': 'Tous', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Images', - 'files.filterDocs': 'Documents', - 'files.filterCollab': 'Notes Collab', - 'files.sourceCollab': 'Depuis les notes Collab', - 'files.empty': 'Aucun fichier', - 'files.emptyHint': 'Importez des fichiers pour les joindre à votre voyage', - 'files.openTab': 'Ouvrir dans un nouvel onglet', - 'files.confirm.delete': 'Voulez-vous vraiment supprimer ce fichier ?', - 'files.toast.deleted': 'Fichier supprimé', - 'files.toast.deleteError': 'Impossible de supprimer le fichier', - 'files.sourcePlan': 'Plan du jour', - 'files.sourceBooking': 'Réservation', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Joindre', - 'files.pasteHint': 'Vous pouvez aussi coller des images depuis le presse-papiers (Ctrl+V)', - 'files.trash': 'Corbeille', - 'files.trashEmpty': 'La corbeille est vide', - 'files.emptyTrash': 'Vider la corbeille', - 'files.restore': 'Restaurer', - 'files.star': 'Favori', - 'files.unstar': 'Retirer des favoris', - 'files.assign': 'Assigner', - 'files.assignTitle': 'Assigner le fichier', - 'files.assignPlace': 'Lieu', - 'files.assignBooking': 'Réservation', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Non attribué', - 'files.unlink': 'Supprimer le lien', - 'files.toast.trashed': 'Déplacé dans la corbeille', - 'files.toast.restored': 'Fichier restauré', - 'files.toast.trashEmptied': 'Corbeille vidée', - 'files.toast.assigned': 'Fichier attribué', - 'files.toast.assignError': 'Échec de l\'assignation', - 'files.toast.restoreError': 'Échec de la restauration', - 'files.confirm.permanentDelete': 'Supprimer définitivement ce fichier ? Cette action est irréversible.', - 'files.confirm.emptyTrash': 'Supprimer définitivement tous les fichiers de la corbeille ? Cette action est irréversible.', - 'files.noteLabel': 'Note', - 'files.notePlaceholder': 'Ajouter une note…', - - // Packing - 'packing.title': 'Liste de bagages', - 'packing.empty': 'La liste de bagages est vide', - 'packing.import': 'Importer', - 'packing.importTitle': 'Importer la liste', - 'packing.importHint': 'Un élément par ligne. Catégorie et quantité optionnelles séparées par virgule, point-virgule ou tabulation : Nom, Catégorie, Quantité', - 'packing.importPlaceholder': 'Brosse à dents\nCrème solaire, Hygiène\nT-Shirts, Vêtements, 5\nPasseport, Documents', - 'packing.importCsv': 'Charger CSV/TXT', - 'packing.importAction': 'Importer {count}', - 'packing.importSuccess': '{count} éléments importés', - 'packing.importError': 'Échec de l\'import', - 'packing.importEmpty': 'Aucun élément à importer', - 'packing.progress': '{packed} sur {total} emballés ({percent} %)', - 'packing.clearChecked': 'Supprimer {count} cochés', - 'packing.clearCheckedShort': 'Supprimer {count}', - 'packing.suggestions': 'Suggestions', - 'packing.suggestionsTitle': 'Ajouter des suggestions', - 'packing.allSuggested': 'Toutes les suggestions ajoutées', - 'packing.allPacked': 'Tout est emballé !', - 'packing.addPlaceholder': 'Ajouter un nouvel article…', - 'packing.categoryPlaceholder': 'Catégorie…', - 'packing.filterAll': 'Tous', - 'packing.filterOpen': 'À faire', - 'packing.filterDone': 'Fait', - 'packing.emptyTitle': 'La liste de bagages est vide', - 'packing.emptyHint': 'Ajoutez des articles ou utilisez les suggestions', - 'packing.emptyFiltered': 'Aucun article ne correspond à ce filtre', - 'packing.menuRename': 'Renommer', - 'packing.menuCheckAll': 'Tout cocher', - 'packing.menuUncheckAll': 'Tout décocher', - 'packing.menuDeleteCat': 'Supprimer la catégorie', - 'packing.addItem': 'Ajouter un article', - 'packing.addItemPlaceholder': 'Nom de l\'article...', - 'packing.addCategory': 'Ajouter une catégorie', - 'packing.newCategoryPlaceholder': 'Nom de catégorie (ex. Vêtements)', - 'packing.applyTemplate': 'Appliquer un modèle', - 'packing.template': 'Modèle', - 'packing.templateApplied': '{count} articles ajoutés depuis le modèle', - 'packing.templateError': 'Erreur lors de l\'application du modèle', - 'packing.saveAsTemplate': 'Enregistrer comme modèle', - 'packing.templateName': 'Nom du modèle', - 'packing.templateSaved': 'Liste de voyage enregistrée comme modèle', - 'packing.noMembers': 'Aucun membre', - 'packing.bags': 'Bagages', - 'packing.noBag': 'Non assigné', - 'packing.totalWeight': 'Poids total', - 'packing.bagName': 'Nom...', - 'packing.addBag': 'Ajouter un bagage', - 'packing.changeCategory': 'Changer de catégorie', - 'packing.confirm.clearChecked': 'Voulez-vous vraiment supprimer {count} articles cochés ?', - 'packing.confirm.deleteCat': 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} articles ?', - 'packing.defaultCategory': 'Autre', - 'packing.toast.saveError': 'Échec de l\'enregistrement', - 'packing.toast.deleteError': 'Échec de la suppression', - 'packing.toast.renameError': 'Échec du renommage', - 'packing.toast.addError': 'Échec de l\'ajout', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Passeport', category: 'Documents' }, - { name: 'Carte d\'identité', category: 'Documents' }, - { name: 'Assurance voyage', category: 'Documents' }, - { name: 'Billets d\'avion', category: 'Documents' }, - { name: 'Carte bancaire', category: 'Finances' }, - { name: 'Espèces', category: 'Finances' }, - { name: 'Visa', category: 'Documents' }, - { name: 'T-shirts', category: 'Vêtements' }, - { name: 'Pantalons', category: 'Vêtements' }, - { name: 'Sous-vêtements', category: 'Vêtements' }, - { name: 'Chaussettes', category: 'Vêtements' }, - { name: 'Veste', category: 'Vêtements' }, - { name: 'Pyjama', category: 'Vêtements' }, - { name: 'Maillot de bain', category: 'Vêtements' }, - { name: 'Imperméable', category: 'Vêtements' }, - { name: 'Chaussures confortables', category: 'Vêtements' }, - { name: 'Brosse à dents', category: 'Hygiène' }, - { name: 'Dentifrice', category: 'Hygiène' }, - { name: 'Shampooing', category: 'Hygiène' }, - { name: 'Déodorant', category: 'Hygiène' }, - { name: 'Crème solaire', category: 'Hygiène' }, - { name: 'Rasoir', category: 'Hygiène' }, - { name: 'Chargeur', category: 'Électronique' }, - { name: 'Batterie externe', category: 'Électronique' }, - { name: 'Écouteurs', category: 'Électronique' }, - { name: 'Adaptateur de voyage', category: 'Électronique' }, - { name: 'Appareil photo', category: 'Électronique' }, - { name: 'Antidouleurs', category: 'Santé' }, - { name: 'Pansements', category: 'Santé' }, - { name: 'Désinfectant', category: 'Santé' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Partager le voyage', - 'members.inviteUser': 'Inviter un utilisateur', - 'members.selectUser': 'Sélectionner un utilisateur…', - 'members.invite': 'Inviter', - 'members.allHaveAccess': 'Tous les utilisateurs ont déjà accès.', - 'members.access': 'Accès', - 'members.person': 'personne', - 'members.persons': 'personnes', - 'members.you': 'vous', - 'members.owner': 'Propriétaire', - 'members.leaveTrip': 'Quitter le voyage', - 'members.removeAccess': 'Retirer l\'accès', - 'members.confirmLeave': 'Quitter le voyage ? Vous perdrez l\'accès.', - 'members.confirmRemove': 'Retirer l\'accès à cet utilisateur ?', - 'members.loadError': 'Impossible de charger les membres', - 'members.added': 'ajouté', - 'members.addError': 'Échec de l\'ajout', - 'members.removed': 'Membre retiré', - 'members.removeError': 'Échec de la suppression', - - // Categories (Admin) - 'categories.title': 'Catégories', - 'categories.subtitle': 'Gérer les catégories de lieux', - 'categories.new': 'Nouvelle catégorie', - 'categories.empty': 'Aucune catégorie', - 'categories.namePlaceholder': 'Nom de la catégorie', - 'categories.icon': 'Icône', - 'categories.color': 'Couleur', - 'categories.customColor': 'Choisir une couleur personnalisée', - 'categories.preview': 'Aperçu', - 'categories.defaultName': 'Catégorie', - 'categories.update': 'Mettre à jour', - 'categories.create': 'Créer', - 'categories.confirm.delete': 'Supprimer la catégorie ? Les lieux de cette catégorie ne seront pas supprimés.', - 'categories.toast.loadError': 'Impossible de charger les catégories', - 'categories.toast.nameRequired': 'Veuillez saisir un nom', - 'categories.toast.updated': 'Catégorie mise à jour', - 'categories.toast.created': 'Catégorie créée', - 'categories.toast.saveError': 'Échec de l\'enregistrement', - 'categories.toast.deleted': 'Catégorie supprimée', - 'categories.toast.deleteError': 'Échec de la suppression', - - // Backup (Admin) - 'backup.title': 'Sauvegarde des données', - 'backup.subtitle': 'Base de données et tous les fichiers importés', - 'backup.refresh': 'Actualiser', - 'backup.upload': 'Importer une sauvegarde', - 'backup.uploading': 'Importation…', - 'backup.create': 'Créer une sauvegarde', - 'backup.creating': 'Création…', - 'backup.empty': 'Aucune sauvegarde', - 'backup.createFirst': 'Créer la première sauvegarde', - 'backup.download': 'Télécharger', - 'backup.restore': 'Restaurer', - 'backup.confirm.restore': 'Restaurer la sauvegarde « {name} » ?\n\nToutes les données actuelles seront remplacées par la sauvegarde.', - 'backup.confirm.uploadRestore': 'Importer et restaurer le fichier de sauvegarde « {name} » ?\n\nToutes les données actuelles seront écrasées.', - 'backup.confirm.delete': 'Supprimer la sauvegarde « {name} » ?', - 'backup.toast.loadError': 'Impossible de charger les sauvegardes', - 'backup.toast.created': 'Sauvegarde créée avec succès', - 'backup.toast.createError': 'Impossible de créer la sauvegarde', - 'backup.toast.restored': 'Sauvegarde restaurée. La page va se recharger…', - 'backup.toast.restoreError': 'Échec de la restauration', - 'backup.toast.uploadError': 'Échec de l\'import', - 'backup.toast.deleted': 'Sauvegarde supprimée', - 'backup.toast.deleteError': 'Échec de la suppression', - 'backup.toast.downloadError': 'Échec du téléchargement', - 'backup.toast.settingsSaved': 'Paramètres de sauvegarde automatique enregistrés', - 'backup.toast.settingsError': 'Impossible d\'enregistrer les paramètres', - 'backup.auto.title': 'Sauvegarde automatique', - 'backup.auto.subtitle': 'Sauvegarde automatique programmée', - 'backup.auto.enable': 'Activer la sauvegarde automatique', - 'backup.auto.enableHint': 'Les sauvegardes seront créées automatiquement selon le calendrier choisi', - 'backup.auto.interval': 'Intervalle', - 'backup.auto.hour': 'Exécuter à l\'heure', - 'backup.auto.hourHint': 'Heure locale du serveur (format {format})', - 'backup.auto.dayOfWeek': 'Jour de la semaine', - 'backup.auto.dayOfMonth': 'Jour du mois', - 'backup.auto.dayOfMonthHint': 'Limité à 1–28 pour la compatibilité avec tous les mois', - 'backup.auto.scheduleSummary': 'Planification', - 'backup.auto.summaryDaily': 'Tous les jours à {hour}h00', - 'backup.auto.summaryWeekly': 'Chaque {day} à {hour}h00', - 'backup.auto.summaryMonthly': 'Le {day} de chaque mois à {hour}h00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'La sauvegarde automatique est configurée via les variables d\'environnement Docker. Pour modifier ces paramètres, mettez à jour votre docker-compose.yml et redémarrez le conteneur.', - 'backup.auto.copyEnv': 'Copier les variables d\'env Docker', - 'backup.auto.envCopied': 'Variables d\'env Docker copiées dans le presse-papiers', - 'backup.auto.keepLabel': 'Supprimer les anciennes sauvegardes après', - 'backup.dow.sunday': 'Dim', - 'backup.dow.monday': 'Lun', - 'backup.dow.tuesday': 'Mar', - 'backup.dow.wednesday': 'Mer', - 'backup.dow.thursday': 'Jeu', - 'backup.dow.friday': 'Ven', - 'backup.dow.saturday': 'Sam', - 'backup.interval.hourly': 'Toutes les heures', - 'backup.interval.daily': 'Quotidien', - 'backup.interval.weekly': 'Hebdomadaire', - 'backup.interval.monthly': 'Mensuel', - 'backup.keep.1day': '1 jour', - 'backup.keep.3days': '3 jours', - 'backup.keep.7days': '7 jours', - 'backup.keep.14days': '14 jours', - 'backup.keep.30days': '30 jours', - 'backup.keep.forever': 'Conserver indéfiniment', - - // Photos - 'photos.title': 'Photos', - 'photos.subtitle': '{count} photos pour {trip}', - 'photos.dropHere': 'Déposez des photos ici...', - 'photos.dropHereActive': 'Déposez des photos ici', - 'photos.captionForAll': 'Légende (pour tous)', - 'photos.captionPlaceholder': 'Légende optionnelle...', - 'photos.addCaption': 'Ajouter une légende...', - 'photos.allDays': 'Tous les jours', - 'photos.noPhotos': 'Aucune photo', - 'photos.uploadHint': 'Importez vos photos de voyage', - 'photos.clickToSelect': 'ou cliquez pour sélectionner', - 'photos.linkPlace': 'Lier au lieu', - 'photos.noPlace': 'Aucun lieu', - 'photos.uploadN': '{n} photo(s) importée(s)', - 'photos.linkDay': 'Lier le jour', - 'photos.noDay': 'Aucun jour', - 'photos.dayLabel': 'Jour {number}', - 'photos.photoSelected': 'Photo sélectionnée', - 'photos.photosSelected': 'Photos sélectionnées', - 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 Mo · jusqu'à 30 photos", - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Restaurer la sauvegarde ?', - 'backup.restoreWarning': 'Toutes les données actuelles (voyages, lieux, utilisateurs, importations) seront définitivement remplacées par la sauvegarde. Cette action est irréversible.', - 'backup.restoreTip': 'Conseil : créez une sauvegarde de l\'état actuel avant de restaurer.', - 'backup.restoreConfirm': 'Oui, restaurer', - - // PDF - 'pdf.travelPlan': 'Plan de voyage', - 'pdf.planned': 'Planifié', - 'pdf.costLabel': 'Coût EUR', - 'pdf.preview': 'Aperçu PDF', - 'pdf.saveAsPdf': 'Enregistrer en PDF', - - // Planner - 'planner.places': 'Lieux', - 'planner.bookings': 'Réservations', - 'planner.packingList': 'Liste de bagages', - 'planner.documents': 'Documents', - 'planner.dayPlan': 'Plan du jour', - 'planner.reservations': 'Réservations', - 'planner.minTwoPlaces': 'Au moins 2 lieux avec coordonnées nécessaires', - 'planner.noGeoPlaces': 'Aucun lieu avec coordonnées disponible', - 'planner.routeCalculated': 'Itinéraire calculé', - 'planner.routeCalcFailed': 'L\'itinéraire n\'a pas pu être calculé', - 'planner.routeError': 'Erreur lors du calcul de l\'itinéraire', - 'planner.icsExportFailed': 'Échec de l\'export ICS', - 'planner.routeOptimized': 'Itinéraire optimisé', - 'planner.reservationUpdated': 'Réservation mise à jour', - 'planner.reservationAdded': 'Réservation ajoutée', - 'planner.confirmDeleteReservation': 'Supprimer la réservation ?', - 'planner.reservationDeleted': 'Réservation supprimée', - 'planner.days': 'Jours', - 'planner.allPlaces': 'Tous les lieux', - 'planner.totalPlaces': '{n} lieux au total', - 'planner.noDaysPlanned': 'Aucun jour planifié', - 'planner.editTrip': 'Modifier le voyage \u2192', - 'planner.placeOne': '1 lieu', - 'planner.placeN': '{n} lieux', - 'planner.addNote': 'Ajouter une note', - 'planner.noEntries': 'Aucune entrée pour ce jour', - 'planner.addPlace': 'Ajouter un lieu ou une activité', - 'planner.addPlaceShort': '+ Ajouter un lieu ou une activité', - 'planner.resPending': 'Réservation en attente · ', - 'planner.resConfirmed': 'Réservation confirmée · ', - 'planner.notePlaceholder': 'Note…', - 'planner.noteTimePlaceholder': 'Heure (facultatif)', - 'planner.noteExamplePlaceholder': 'ex. S3 à 14h30 depuis la gare centrale, ferry depuis le quai 7, pause déjeuner…', - 'planner.totalCost': 'Coût total', - 'planner.searchPlaces': 'Rechercher des lieux…', - 'planner.allCategories': 'Toutes les catégories', - 'planner.noPlacesFound': 'Aucun lieu trouvé', - 'planner.addFirstPlace': 'Ajouter un premier lieu', - 'planner.noReservations': 'Aucune réservation', - 'planner.addFirstReservation': 'Ajouter une première réservation', - 'planner.new': 'Nouveau', - 'planner.addToDay': '+ Jour', - 'planner.calculating': 'Calcul…', - 'planner.route': 'Itinéraire', - 'planner.optimize': 'Optimiser', - 'planner.openGoogleMaps': 'Ouvrir dans Google Maps', - 'planner.selectDayHint': 'Sélectionnez un jour dans la liste de gauche pour voir le plan du jour', - 'planner.noPlacesForDay': 'Aucun lieu pour ce jour', - 'planner.addPlacesLink': 'Ajouter des lieux \u2192', - 'planner.minTotal': 'min. total', - 'planner.noReservation': 'Pas de réservation', - 'planner.removeFromDay': 'Retirer du jour', - 'planner.addToThisDay': 'Ajouter au jour', - 'planner.overview': 'Aperçu', - 'planner.noDays': 'Aucun jour', - 'planner.editTripToAddDays': 'Modifiez le voyage pour ajouter des jours', - 'planner.dayCount': '{n} jours', - 'planner.clickToUnlock': 'Cliquez pour déverrouiller', - 'planner.keepPosition': 'Maintenir la position lors de l\'optimisation de l\'itinéraire', - 'planner.dayDetails': 'Détails du jour', - 'planner.dayN': 'Jour {n}', - - // Dashboard Stats - 'stats.countries': 'Pays', - 'stats.cities': 'Villes', - 'stats.trips': 'Voyages', - 'stats.places': 'Lieux', - 'stats.worldProgress': 'Progression mondiale', - 'stats.visited': 'visités', - 'stats.remaining': 'restants', - 'stats.visitedCountries': 'Pays visités', - - // Day Detail Panel - 'day.precipProb': 'Probabilité de pluie', - 'day.precipitation': 'Précipitations', - 'day.wind': 'Vent', - 'day.sunrise': 'Lever du soleil', - 'day.sunset': 'Coucher du soleil', - 'day.hourlyForecast': 'Prévisions horaires', - 'day.climateHint': 'Moyennes historiques — prévisions réelles disponibles dans les 16 jours précédant cette date.', - 'day.noWeather': 'Aucune donnée météo disponible. Ajoutez un lieu avec des coordonnées.', - 'day.overview': 'Aperçu du jour', - 'day.accommodation': 'Hébergement', - 'day.addAccommodation': 'Ajouter un hébergement', - 'day.hotelDayRange': 'Appliquer aux jours', - 'day.noPlacesForHotel': 'Ajoutez d\'abord des lieux à votre voyage', - 'day.allDays': 'Tous', - 'day.checkIn': 'Arrivée', - 'day.checkInUntil': "Jusqu'à", - 'day.checkOut': 'Départ', - 'day.confirmation': 'Confirmation', - 'day.editAccommodation': 'Modifier l\'hébergement', - 'day.reservations': 'Réservations', - - // Memories / Immich - 'memories.title': 'Photos', - 'memories.notConnected': 'Immich non connecté', - 'memories.notConnectedHint': 'Connectez votre instance Immich dans les paramètres pour voir vos photos de voyage ici.', - 'memories.notConnectedMultipleHint': 'Connectez un de ces fournisseurs de photos : {provider_names} dans les Paramètres pour pouvoir ajouter des photos à ce voyage.', - 'memories.noDates': 'Ajoutez des dates à votre voyage pour charger les photos.', - 'memories.noPhotos': 'Aucune photo trouvée', - 'memories.noPhotosHint': 'Aucune photo trouvée dans Immich pour la période de ce voyage.', - 'memories.photosFound': 'photos', - 'memories.fromOthers': 'd\'autres', - 'memories.sharePhotos': 'Partager les photos', - 'memories.sharing': 'Partagé', - 'memories.reviewTitle': 'Vérifier vos photos', - 'memories.reviewHint': 'Cliquez sur les photos pour les exclure du partage.', - 'memories.shareCount': 'Partager {count} photos', - 'memories.providerUrl': 'URL du serveur', - 'memories.providerApiKey': 'Clé API', - 'memories.providerUsername': 'Nom d\'utilisateur', - 'memories.providerPassword': 'Mot de passe', - 'memories.providerOTP': 'Code MFA (si activé)', - 'memories.skipSSLVerification': 'Ignorer la vérification du certificat SSL', - 'memories.immichAutoUpload': 'Répliquer les photos du journey vers Immich au téléversement', - 'memories.providerUrlHintSynology': 'Incluez le chemin de l\'application Photos dans l\'URL, ex. https://nas:5001/photo', - 'memories.testConnection': 'Tester la connexion', - 'memories.testShort': 'Tester', - 'memories.testFirst': 'Testez la connexion avant de sauvegarder', - 'memories.connected': 'Connecté', - 'memories.disconnected': 'Non connecté', - 'memories.connectionSuccess': 'Connecté à Immich', - 'memories.connectionError': 'Impossible de se connecter à Immich', - 'memories.saved': 'Paramètres {provider_name} enregistrés', - 'memories.providerDisconnectedBanner': 'Votre connexion {provider_name} est perdue. Reconnectez-vous dans les Paramètres pour voir les photos.', - 'memories.saveError': 'Impossible d\'enregistrer les paramètres de {provider_name}', - 'memories.saveRouteNotConfigured': "La route de sauvegarde n'est pas configurée pour ce fournisseur", - 'memories.testRouteNotConfigured': "La route de test n'est pas configurée pour ce fournisseur", - 'memories.fillRequiredFields': 'Veuillez remplir tous les champs obligatoires', - 'memories.oldest': 'Plus anciennes', - 'memories.newest': 'Plus récentes', - 'memories.allLocations': 'Tous les lieux', - 'memories.addPhotos': 'Ajouter des photos', - 'memories.linkAlbum': 'Lier un album', - 'memories.selectAlbum': 'Choisir un album Immich', - 'memories.selectAlbumMultiple': 'Sélectionner un album', - 'memories.noAlbums': 'Aucun album trouvé', - 'memories.syncAlbum': 'Synchroniser', - 'memories.unlinkAlbum': 'Délier', - 'memories.photos': 'photos', - 'memories.selectPhotos': 'Sélectionner des photos depuis Immich', - 'memories.selectPhotosMultiple': 'Sélectionner des photos', - 'memories.selectHint': 'Appuyez sur les photos pour les sélectionner.', - 'memories.selected': 'sélectionné(s)', - 'memories.addSelected': 'Ajouter {count} photos', - 'memories.alreadyAdded': 'Ajouté', - 'memories.private': 'Privé', - 'memories.stopSharing': 'Arrêter le partage', - 'memories.tripDates': 'Dates du voyage', - 'memories.allPhotos': 'Toutes les photos', - 'memories.confirmShareTitle': 'Partager avec les membres du voyage ?', - 'memories.confirmShareHint': '{count} photos seront visibles par tous les membres de ce voyage. Vous pourrez rendre des photos individuelles privées plus tard.', - 'memories.confirmShareButton': 'Partager les photos', - - // Collab Addon - 'collab.tabs.chat': 'Discussion', - 'collab.tabs.notes': 'Notes', - 'collab.tabs.polls': 'Sondages', - 'collab.whatsNext.title': 'À venir', - 'collab.whatsNext.today': 'Aujourd\'hui', - 'collab.whatsNext.tomorrow': 'Demain', - 'collab.whatsNext.empty': 'Aucune activité à venir', - 'collab.whatsNext.until': 'à', - 'collab.whatsNext.emptyHint': 'Les activités avec des horaires apparaîtront ici', - 'collab.chat.send': 'Envoyer', - 'collab.chat.placeholder': 'Écrire un message…', - 'collab.chat.empty': 'Commencez la conversation', - 'collab.chat.emptyHint': 'Les messages sont partagés avec tous les membres du voyage', - 'collab.chat.emptyDesc': 'Partagez des idées, des plans et des mises à jour avec votre groupe de voyage', - 'collab.chat.today': 'Aujourd\'hui', - 'collab.chat.yesterday': 'Hier', - 'collab.chat.deletedMessage': 'a supprimé un message', - 'collab.chat.reply': 'Répondre', - 'collab.chat.loadMore': 'Charger les messages précédents', - 'collab.chat.justNow': 'à l\'instant', - 'collab.chat.minutesAgo': 'il y a {n} min', - 'collab.chat.hoursAgo': 'il y a {n} h', - 'collab.notes.title': 'Notes', - 'collab.notes.new': 'Nouvelle note', - 'collab.notes.empty': 'Aucune note', - 'collab.notes.emptyHint': 'Commencez à capturer vos idées et plans', - 'collab.notes.all': 'Toutes', - 'collab.notes.titlePlaceholder': 'Titre de la note', - 'collab.notes.contentPlaceholder': 'Écrivez quelque chose…', - 'collab.notes.categoryPlaceholder': 'Catégorie', - 'collab.notes.newCategory': 'Nouvelle catégorie…', - 'collab.notes.category': 'Catégorie', - 'collab.notes.noCategory': 'Sans catégorie', - 'collab.notes.color': 'Couleur', - 'collab.notes.save': 'Enregistrer', - 'collab.notes.cancel': 'Annuler', - 'collab.notes.edit': 'Modifier', - 'collab.notes.delete': 'Supprimer', - 'collab.notes.pin': 'Épingler', - 'collab.notes.unpin': 'Désépingler', - 'collab.notes.daysAgo': 'il y a {n} j', - 'collab.notes.categorySettings': 'Gérer les catégories', - 'collab.notes.create': 'Créer', - 'collab.notes.website': 'Site web', - 'collab.notes.websitePlaceholder': 'https://…', - 'collab.notes.attachFiles': 'Joindre des fichiers', - 'collab.notes.noCategoriesYet': 'Aucune catégorie', - 'collab.notes.emptyDesc': 'Créez une note pour commencer', - 'collab.polls.title': 'Sondages', - 'collab.polls.new': 'Nouveau sondage', - 'collab.polls.empty': 'Aucun sondage', - 'collab.polls.emptyHint': 'Posez des questions au groupe et votez ensemble', - 'collab.polls.question': 'Question', - 'collab.polls.questionPlaceholder': 'Que devrait-on faire ?', - 'collab.polls.addOption': '+ Ajouter une option', - 'collab.polls.optionPlaceholder': 'Option {n}', - 'collab.polls.create': 'Créer le sondage', - 'collab.polls.close': 'Fermer', - 'collab.polls.closed': 'Fermé', - 'collab.polls.votes': '{n} votes', - 'collab.polls.vote': '{n} vote', - 'collab.polls.multipleChoice': 'Choix multiples', - 'collab.polls.multiChoice': 'Choix multiples', - 'collab.polls.deadline': 'Date limite', - 'collab.polls.option': 'Option', - 'collab.polls.options': 'Options', - 'collab.polls.delete': 'Supprimer', - 'collab.polls.closedSection': 'Fermés', - - // Permissions - 'admin.tabs.permissions': 'Permissions', - 'perm.title': 'Paramètres des permissions', - 'perm.subtitle': 'Contrôlez qui peut effectuer des actions dans l\'application', - 'perm.saved': 'Paramètres des permissions enregistrés', - 'perm.resetDefaults': 'Réinitialiser par défaut', - 'perm.customized': 'personnalisé', - 'perm.level.admin': 'Administrateur uniquement', - 'perm.level.tripOwner': 'Propriétaire du voyage', - 'perm.level.tripMember': 'Membres du voyage', - 'perm.level.everybody': 'Tout le monde', - 'perm.cat.trip': 'Gestion des voyages', - 'perm.cat.members': 'Gestion des membres', - 'perm.cat.files': 'Fichiers', - 'perm.cat.content': 'Contenu et planning', - 'perm.cat.extras': 'Budget, bagages et collaboration', - 'perm.action.trip_create': 'Créer des voyages', - 'perm.action.trip_edit': 'Modifier les détails du voyage', - 'perm.action.trip_delete': 'Supprimer des voyages', - 'perm.action.trip_archive': 'Archiver / désarchiver des voyages', - 'perm.action.trip_cover_upload': 'Télécharger l\'image de couverture', - 'perm.action.member_manage': 'Ajouter / supprimer des membres', - 'perm.action.file_upload': 'Télécharger des fichiers', - 'perm.action.file_edit': 'Modifier les métadonnées des fichiers', - 'perm.action.file_delete': 'Supprimer des fichiers', - 'perm.action.place_edit': 'Ajouter / modifier / supprimer des lieux', - 'perm.action.day_edit': 'Modifier les jours, notes et affectations', - 'perm.action.reservation_edit': 'Gérer les réservations', - 'perm.action.budget_edit': 'Gérer le budget', - 'perm.action.packing_edit': 'Gérer les listes de bagages', - 'perm.action.collab_edit': 'Collaboration (notes, sondages, chat)', - 'perm.action.share_manage': 'Gérer les liens de partage', - 'perm.actionHint.trip_create': 'Qui peut créer de nouveaux voyages', - 'perm.actionHint.trip_edit': 'Qui peut modifier le nom, les dates, la description et la devise du voyage', - 'perm.actionHint.trip_delete': 'Qui peut supprimer définitivement un voyage', - 'perm.actionHint.trip_archive': 'Qui peut archiver ou désarchiver un voyage', - 'perm.actionHint.trip_cover_upload': 'Qui peut télécharger ou modifier l\'image de couverture', - 'perm.actionHint.member_manage': 'Qui peut inviter ou supprimer des membres du voyage', - 'perm.actionHint.file_upload': 'Qui peut télécharger des fichiers vers un voyage', - 'perm.actionHint.file_edit': 'Qui peut modifier les descriptions et liens des fichiers', - 'perm.actionHint.file_delete': 'Qui peut déplacer des fichiers vers la corbeille ou les supprimer définitivement', - 'perm.actionHint.place_edit': 'Qui peut ajouter, modifier ou supprimer des lieux', - 'perm.actionHint.day_edit': 'Qui peut modifier les jours, notes de jours et affectations de lieux', - 'perm.actionHint.reservation_edit': 'Qui peut créer, modifier ou supprimer des réservations', - 'perm.actionHint.budget_edit': 'Qui peut créer, modifier ou supprimer des éléments de budget', - 'perm.actionHint.packing_edit': 'Qui peut gérer les articles de bagages et les sacs', - 'perm.actionHint.collab_edit': 'Qui peut créer des notes, des sondages et envoyer des messages', - 'perm.actionHint.share_manage': 'Qui peut créer ou supprimer des liens de partage publics', - // Undo - 'undo.button': 'Annuler', - 'undo.tooltip': 'Annuler : {action}', - 'undo.assignPlace': 'Lieu ajouté au jour', - 'undo.removeAssignment': 'Lieu retiré du jour', - 'undo.reorder': 'Lieux réorganisés', - 'undo.optimize': 'Itinéraire optimisé', - 'undo.deletePlace': 'Lieu supprimé', - 'undo.deletePlaces': 'Lieux supprimés', - 'undo.moveDay': 'Lieu déplacé vers un autre jour', - 'undo.lock': 'Verrouillage du lieu modifié', - 'undo.importGpx': 'Import GPX', - 'undo.importKeyholeMarkup': 'Import KMZ/KML', - 'undo.importGoogleList': 'Import Google Maps', - 'undo.importNaverList': 'Import Naver Maps', - - // Notifications - 'notifications.title': 'Notifications', - 'notifications.markAllRead': 'Tout marquer comme lu', - 'notifications.deleteAll': 'Tout supprimer', - 'notifications.showAll': 'Voir toutes les notifications', - 'notifications.empty': 'Aucune notification', - 'notifications.emptyDescription': 'Vous êtes à jour !', - 'notifications.all': 'Toutes', - 'notifications.unreadOnly': 'Non lues', - 'notifications.markRead': 'Marquer comme lu', - 'notifications.markUnread': 'Marquer comme non lu', - 'notifications.delete': 'Supprimer', - 'notifications.system': 'Système', - 'notifications.synologySessionCleared.title': 'Synology Photos déconnecté', - 'notifications.synologySessionCleared.text': 'Votre serveur ou compte a changé — allez dans Paramètres pour tester à nouveau votre connexion.', - 'memories.error.loadAlbums': 'Impossible de charger les albums', - 'memories.error.linkAlbum': 'Impossible de lier l\'album', - 'memories.error.unlinkAlbum': 'Impossible de dissocier l\'album', - 'memories.error.syncAlbum': 'Impossible de synchroniser l\'album', - 'memories.error.loadPhotos': 'Impossible de charger les photos', - 'memories.error.addPhotos': 'Impossible d\'ajouter les photos', - 'memories.error.removePhoto': 'Impossible de supprimer la photo', - 'memories.error.toggleSharing': 'Impossible de mettre à jour le partage', - 'undo.addPlace': 'Lieu ajouté', - 'undo.done': 'Annulé : {action}', - 'notifications.test.title': 'Notification test de {actor}', - 'notifications.test.text': 'Ceci est une simple notification de test.', - 'notifications.test.booleanTitle': '{actor} demande votre approbation', - 'notifications.test.booleanText': 'Notification de test booléenne.', - 'notifications.test.accept': 'Approuver', - 'notifications.test.decline': 'Refuser', - 'notifications.test.navigateTitle': 'Allez voir quelque chose', - 'notifications.test.navigateText': 'Notification de test de navigation.', - 'notifications.test.goThere': 'Y aller', - 'notifications.test.adminTitle': 'Diffusion admin', - 'notifications.test.adminText': '{actor} a envoyé une notification de test à tous les admins.', - 'notifications.test.tripTitle': '{actor} a publié dans votre voyage', - 'notifications.test.tripText': 'Notification de test pour le voyage "{trip}".', - - // Todo - 'todo.subtab.packing': 'Liste de bagages', - 'todo.subtab.todo': 'À faire', - 'todo.completed': 'terminé(s)', - 'todo.filter.all': 'Tout', - 'todo.filter.open': 'En cours', - 'todo.filter.done': 'Terminé', - 'todo.uncategorized': 'Sans catégorie', - 'todo.namePlaceholder': 'Nom de la tâche', - 'todo.descriptionPlaceholder': 'Description (facultative)', - 'todo.unassigned': 'Non assigné', - 'todo.noCategory': 'Aucune catégorie', - 'todo.hasDescription': 'Avec description', - 'todo.addItem': 'Nouvelle tâche', - 'todo.sidebar.sortBy': 'Trier par', - 'todo.priority': 'Priorité', - 'todo.newCategoryLabel': 'nouvelle', - 'budget.categoriesLabel': 'catégories', - 'todo.newCategory': 'Nom de la catégorie', - 'todo.addCategory': 'Ajouter une catégorie', - 'todo.newItem': 'Nouvelle tâche', - 'todo.empty': 'Aucune tâche pour l\'instant. Ajoutez une tâche pour commencer !', - 'todo.filter.my': 'Mes tâches', - 'todo.filter.overdue': 'En retard', - 'todo.sidebar.tasks': 'Tâches', - 'todo.sidebar.categories': 'Catégories', - 'todo.detail.title': 'Tâche', - 'todo.detail.description': 'Description', - 'todo.detail.category': 'Catégorie', - 'todo.detail.dueDate': 'Date d\'échéance', - 'todo.detail.assignedTo': 'Assigné à', - 'todo.detail.delete': 'Supprimer', - 'todo.detail.save': 'Enregistrer les modifications', - 'todo.detail.create': 'Créer la tâche', - 'todo.detail.priority': 'Priorité', - 'todo.detail.noPriority': 'Aucune', - 'todo.sortByPrio': 'Priorité', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nouvelle version disponible', - 'settings.notificationPreferences.noChannels': 'Aucun canal de notification n\'est configuré. Demandez à un administrateur de configurer les notifications par e-mail ou webhook.', - 'settings.webhookUrl.label': 'URL du webhook', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Entrez votre URL de webhook Discord, Slack ou personnalisée pour recevoir des notifications.', - 'settings.webhookUrl.saved': 'URL du webhook enregistrée', - 'settings.webhookUrl.test': 'Tester', - 'settings.webhookUrl.testSuccess': 'Webhook de test envoyé avec succès', - 'settings.webhookUrl.testFailed': 'Échec du webhook de test', - 'settings.ntfyUrl.topicLabel': 'Sujet Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': "URL du serveur Ntfy (optionnel)", - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': "Entrez votre sujet Ntfy pour recevoir des notifications push. Laissez le serveur vide pour utiliser la valeur par défaut configurée par votre administrateur.", - 'settings.ntfyUrl.tokenLabel': "Jeton d'accès (optionnel)", - 'settings.ntfyUrl.tokenHint': 'Requis pour les sujets protégés par mot de passe.', - 'settings.ntfyUrl.saved': 'Paramètres Ntfy enregistrés', - 'settings.ntfyUrl.test': 'Tester', - 'settings.ntfyUrl.testSuccess': 'Notification de test Ntfy envoyée avec succès', - 'settings.ntfyUrl.testFailed': 'Échec de la notification de test Ntfy', - 'settings.ntfyUrl.tokenCleared': "Jeton d'accès effacé", - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Les notifications in-app sont toujours actives et ne peuvent pas être désactivées globalement.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook admin', - 'admin.notifications.adminWebhookPanel.hint': 'Ce webhook est utilisé exclusivement pour les notifications admin (ex. alertes de version). Il est séparé des webhooks utilisateur et s\'active automatiquement si une URL est configurée.', - 'admin.notifications.adminWebhookPanel.saved': 'URL du webhook admin enregistrée', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook de test envoyé avec succès', - 'admin.notifications.adminWebhookPanel.testFailed': 'Échec du webhook de test', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Le webhook admin s\'active automatiquement si une URL est configurée', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Permet aux utilisateurs de configurer leurs propres sujets ntfy pour les notifications push. Définissez le serveur par défaut ci-dessous pour pré-remplir les paramètres utilisateur.', - 'admin.notifications.testNtfy': 'Envoyer un Ntfy de test', - 'admin.notifications.testNtfySuccess': 'Ntfy de test envoyé avec succès', - 'admin.notifications.testNtfyFailed': 'Échec de l\'envoi du Ntfy de test', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', - 'admin.notifications.adminNtfyPanel.hint': 'Ce sujet Ntfy est utilisé exclusivement pour les notifications admin (ex. alertes de version). Il est séparé des sujets par utilisateur et s\'active toujours lorsqu\'il est configuré.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL du serveur Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Utilisé également comme serveur par défaut pour les notifications ntfy des utilisateurs. Laisser vide pour utiliser ntfy.sh. Les utilisateurs peuvent le modifier dans leurs propres paramètres.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Sujet admin', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': "Jeton d'accès (optionnel)", - 'admin.notifications.adminNtfyPanel.tokenCleared': "Jeton d'accès admin effacé", - 'admin.notifications.adminNtfyPanel.saved': 'Paramètres Ntfy admin enregistrés', - 'admin.notifications.adminNtfyPanel.test': 'Envoyer un Ntfy de test', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy de test envoyé avec succès', - 'admin.notifications.adminNtfyPanel.testFailed': 'Échec de l\'envoi du Ntfy de test', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Le Ntfy admin s\'active toujours lorsqu\'un sujet est configuré', - 'admin.notifications.adminNotificationsHint': 'Configurez quels canaux envoient les notifications admin (ex. alertes de version). Le webhook s\'active automatiquement si une URL webhook admin est définie.', - 'admin.notifications.tripReminders.title': 'Rappels de voyage', - 'admin.notifications.tripReminders.hint': 'Envoie une notification de rappel avant le début d\'un voyage (nécessite des jours de rappel définis sur le voyage).', - 'admin.notifications.tripReminders.enabled': 'Rappels de voyage activés', - 'admin.notifications.tripReminders.disabled': 'Rappels de voyage désactivés', - 'admin.tabs.notifications': 'Notifications', - 'notifications.versionAvailable.title': 'Mise à jour disponible', - 'notifications.versionAvailable.text': 'TREK {version} est maintenant disponible.', - 'notifications.versionAvailable.button': 'Voir les détails', - 'notif.test.title': '[Test] Notification', - 'notif.test.simple.text': 'Ceci est une simple notification de test.', - 'notif.test.boolean.text': 'Acceptez-vous cette notification de test ?', - 'notif.test.navigate.text': 'Cliquez ci-dessous pour accéder au tableau de bord.', - - // Notifications - 'notif.trip_invite.title': 'Invitation au voyage', - 'notif.trip_invite.text': '{actor} vous a invité à {trip}', - 'notif.booking_change.title': 'Réservation mise à jour', - 'notif.booking_change.text': '{actor} a mis à jour une réservation dans {trip}', - 'notif.trip_reminder.title': 'Rappel de voyage', - 'notif.trip_reminder.text': 'Votre voyage {trip} approche !', - 'notif.todo_due.title': 'Tâche à échéance', - 'notif.todo_due.text': '{todo} dans {trip} est due le {due}', - 'notif.vacay_invite.title': 'Invitation Vacay Fusion', - 'notif.vacay_invite.text': '{actor} vous invite à fusionner les plans de vacances', - 'notif.photos_shared.title': 'Photos partagées', - 'notif.photos_shared.text': '{actor} a partagé {count} photo(s) dans {trip}', - 'notif.collab_message.title': 'Nouveau message', - 'notif.collab_message.text': '{actor} a envoyé un message dans {trip}', - 'notif.packing_tagged.title': 'Affectation bagages', - 'notif.packing_tagged.text': '{actor} vous a assigné à {category} dans {trip}', - 'notif.version_available.title': 'Nouvelle version disponible', - 'notif.version_available.text': 'TREK {version} est maintenant disponible', - 'notif.action.view_trip': 'Voir le voyage', - 'notif.action.view_collab': 'Voir les messages', - 'notif.action.view_packing': 'Voir les bagages', - 'notif.action.view_photos': 'Voir les photos', - 'notif.action.view_vacay': 'Voir Vacay', - 'notif.action.view_admin': 'Aller à l\'admin', - 'notif.action.view': 'Voir', - 'notif.action.accept': 'Accepter', - 'notif.action.decline': 'Refuser', - 'notif.generic.title': 'Notification', - 'notif.generic.text': 'Vous avez une nouvelle notification', - 'notif.dev.unknown_event.title': '[DEV] Événement inconnu', - 'notif.dev.unknown_event.text': 'Le type d\'événement "{event}" n\'est pas enregistré dans EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'à l\'instant', - 'common.hoursAgo': 'il y a {count}h', - 'common.daysAgo': 'il y a {count}j', - 'journey.search.placeholder': 'Rechercher des journaux…', - 'journey.search.noResults': 'Aucun journal ne correspond à « {query} »', - 'journey.title': 'Journal de voyage', - 'journey.subtitle': 'Suivez vos voyages en temps réel', - 'journey.new': 'Nouveau journal', - 'journey.create': 'Créer', - 'journey.titlePlaceholder': 'Où allez-vous ?', - 'journey.empty': 'Aucun journal pour le moment', - 'journey.emptyHint': 'Commencez à documenter votre prochain voyage', - 'journey.deleted': 'Journal supprimé', - 'journey.createError': 'Impossible de créer le journal', - 'journey.deleteError': 'Impossible de supprimer le journal', - 'journey.deleteConfirmTitle': 'Supprimer', - 'journey.deleteConfirmMessage': 'Supprimer « {title} » ? Cette action est irréversible.', - 'journey.deleteConfirmGeneric': 'Êtes-vous sûr de vouloir supprimer ceci ?', - 'journey.notFound': 'Journal introuvable', - 'journey.photos': 'Photos', - 'journey.timelineEmpty': 'Aucune étape pour le moment', - 'journey.timelineEmptyHint': 'Ajoutez un check-in ou écrivez une entrée de journal pour commencer', - 'journey.status.draft': 'Brouillon', - 'journey.status.active': 'Actif', - 'journey.status.completed': 'Terminé', - 'journey.status.upcoming': 'À venir', - 'journey.status.archived': 'Archivé', - 'journey.checkin.add': 'Check-in', - 'journey.checkin.namePlaceholder': 'Nom du lieu', - 'journey.checkin.notesPlaceholder': 'Notes (facultatif)', - 'journey.checkin.save': 'Enregistrer', - 'journey.checkin.error': 'Impossible d\'enregistrer le check-in', - 'journey.entry.add': 'Journal', - 'journey.entry.edit': 'Modifier l\'entrée', - 'journey.entry.titlePlaceholder': 'Titre (facultatif)', - 'journey.entry.bodyPlaceholder': 'Que s\'est-il passé aujourd\'hui ?', - 'journey.entry.save': 'Enregistrer', - 'journey.entry.error': 'Impossible d\'enregistrer l\'entrée', - 'journey.photo.add': 'Photo', - 'journey.photo.uploadError': 'Échec du téléversement', - 'journey.share.share': 'Partager', - 'journey.share.public': 'Public', - 'journey.share.linkCopied': 'Lien public copié', - 'journey.share.disabled': 'Partage public désactivé', - 'journey.editor.titlePlaceholder': 'Donnez un nom à ce moment...', - 'journey.editor.bodyPlaceholder': 'Racontez l\'histoire de cette journée...', - 'journey.editor.placePlaceholder': 'Lieu (facultatif)', - 'journey.editor.tagsPlaceholder': 'Tags : pépite cachée, meilleur repas, à revisiter...', - 'journey.visibility.private': 'Privé', - 'journey.visibility.shared': 'Partagé', - 'journey.visibility.public': 'Public', - 'journey.emptyState.title': 'Votre histoire commence ici', - 'journey.emptyState.subtitle': 'Faites un check-in ou écrivez votre première entrée de journal', - 'journey.frontpage.subtitle': 'Transformez vos voyages en histoires inoubliables', - 'journey.frontpage.createJourney': 'Créer un journal', - 'journey.frontpage.activeJourney': 'Journal actif', - 'journey.frontpage.allJourneys': 'Tous les journaux', - 'journey.frontpage.journeys': 'journaux', - 'journey.frontpage.createNew': 'Créer un nouveau journal', - 'journey.frontpage.createNewSub': 'Choisissez des voyages, écrivez des récits, partagez vos aventures', - 'journey.frontpage.live': 'En direct', - 'journey.frontpage.synced': 'Synchronisé', - 'journey.frontpage.continueWriting': 'Continuer à écrire', - 'journey.frontpage.updated': 'Mis à jour {time}', - 'journey.frontpage.suggestionLabel': 'Voyage terminé récemment', - 'journey.frontpage.suggestionText': 'Transformez {title} en journal de voyage', - 'journey.frontpage.dismiss': 'Ignorer', - 'journey.frontpage.journeyName': 'Nom du journal', - 'journey.frontpage.namePlaceholder': 'ex. Asie du Sud-Est 2026', - 'journey.frontpage.selectTrips': 'Sélectionner des voyages', - 'journey.frontpage.tripsSelected': 'voyages sélectionnés', - 'journey.frontpage.trips': 'voyages', - 'journey.frontpage.placesImported': 'lieux seront importés', - 'journey.frontpage.places': 'lieux', - 'journey.detail.backToJourney': 'Retour au journal', - 'journey.detail.syncedWithTrips': 'Synchronisé avec les voyages', - 'journey.detail.addEntry': 'Ajouter une entrée', - 'journey.detail.newEntry': 'Nouvelle entrée', - 'journey.detail.editEntry': 'Modifier l\'entrée', - 'journey.detail.noEntries': 'Aucune entrée pour le moment', - 'journey.detail.noEntriesHint': 'Ajoutez un voyage pour commencer avec des entrées préremplies', - 'journey.detail.noPhotos': 'Aucune photo pour le moment', - 'journey.detail.noPhotosHint': 'Téléversez des photos dans les entrées ou parcourez votre bibliothèque Immich/Synology', - 'journey.detail.journeyStats': 'Statistiques du journal', - 'journey.detail.syncedTrips': 'Voyages synchronisés', - 'journey.detail.noTripsLinked': 'Aucun voyage lié pour le moment', - 'journey.detail.contributors': 'Contributeurs', - 'journey.detail.readMore': 'Lire la suite', - 'journey.detail.prosCons': 'Pour et contre', - 'journey.detail.photos': 'photos', - 'journey.detail.day': 'Jour {number}', - 'journey.detail.places': 'lieux', - 'journey.stats.days': 'Jours', - 'journey.stats.cities': 'Villes', - 'journey.stats.entries': 'Entrées', - 'journey.stats.photos': 'Photos', - 'journey.stats.places': 'Lieux', - 'journey.skeletons.show': 'Afficher les suggestions', - 'journey.skeletons.hide': 'Masquer les suggestions', - 'journey.verdict.lovedIt': 'Adoré', - 'journey.verdict.couldBeBetter': 'Pourrait être mieux', - 'journey.synced.places': 'lieux', - 'journey.synced.synced': 'synchronisé', - 'journey.editor.discardChangesConfirm': 'Vous avez des modifications non enregistrées. Les ignorer ?', - 'journey.editor.uploadFailed': 'Échec du téléversement des photos', - 'journey.editor.uploadPhotos': 'Téléverser des photos', - 'journey.editor.uploading': 'Envoi...', - 'journey.editor.uploadingProgress': 'Téléversement {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} sur {total} photos ont échoué — sauvegardez à nouveau pour réessayer', - 'journey.editor.fromGallery': 'Depuis la galerie', - 'journey.editor.allPhotosAdded': 'Toutes les photos ont déjà été ajoutées', - 'journey.editor.writeStory': 'Écrivez votre histoire...', - 'journey.editor.prosCons': 'Pour et contre', - 'journey.editor.pros': 'Pour', - 'journey.editor.cons': 'Contre', - 'journey.editor.proPlaceholder': 'Quelque chose de génial...', - 'journey.editor.conPlaceholder': 'Pas si génial...', - 'journey.editor.addAnother': 'Ajouter un autre', - 'journey.editor.date': 'Date', - 'journey.editor.location': 'Lieu', - 'journey.editor.searchLocation': 'Rechercher un lieu...', - 'journey.editor.mood': 'Humeur', - 'journey.editor.weather': 'Météo', - 'journey.editor.photoFirst': '1er', - 'journey.editor.makeFirst': 'Mettre en 1er', - 'journey.editor.searching': 'Recherche...', - 'journey.mood.amazing': 'Incroyable', - 'journey.mood.good': 'Bien', - 'journey.mood.neutral': 'Neutre', - 'journey.mood.rough': 'Difficile', - 'journey.weather.sunny': 'Ensoleillé', - 'journey.weather.partly': 'Partiellement nuageux', - 'journey.weather.cloudy': 'Nuageux', - 'journey.weather.rainy': 'Pluvieux', - 'journey.weather.stormy': 'Orageux', - 'journey.weather.cold': 'Neigeux', - 'journey.trips.linkTrip': 'Lier un voyage', - 'journey.trips.searchTrip': 'Rechercher un voyage', - 'journey.trips.searchPlaceholder': 'Nom du voyage ou destination...', - 'journey.trips.noTripsAvailable': 'Aucun voyage disponible', - 'journey.trips.link': 'Lier', - 'journey.trips.tripLinked': 'Voyage lié', - 'journey.trips.linkFailed': 'Échec de la liaison du voyage', - 'journey.trips.addTrip': 'Ajouter un voyage', - 'journey.trips.unlinkTrip': 'Délier le voyage', - 'journey.trips.unlinkMessage': 'Délier « {title} » ? Toutes les entrées et photos synchronisées de ce voyage seront définitivement supprimées. Cette action est irréversible.', - 'journey.trips.unlink': 'Délier', - 'journey.trips.tripUnlinked': 'Voyage délié', - 'journey.trips.unlinkFailed': 'Échec de la suppression du lien', - 'journey.trips.noTripsLinkedSettings': 'Aucun voyage lié', - 'journey.contributors.invite': 'Inviter un contributeur', - 'journey.contributors.searchUser': 'Rechercher un utilisateur', - 'journey.contributors.searchPlaceholder': 'Nom d\'utilisateur ou e-mail...', - 'journey.contributors.noUsers': 'Aucun utilisateur trouvé', - 'journey.contributors.role': 'Rôle', - 'journey.contributors.added': 'Contributeur ajouté', - 'journey.contributors.addFailed': 'Échec de l\'ajout du contributeur', - 'journey.share.publicShare': 'Partage public', - 'journey.share.createLink': 'Créer un lien de partage', - 'journey.share.linkCreated': 'Lien de partage créé', - 'journey.share.createFailed': 'Échec de la création du lien', - 'journey.share.copy': 'Copier', - 'journey.share.copied': 'Copié !', - 'journey.share.timeline': 'Chronologie', - 'journey.share.gallery': 'Galerie', - 'journey.share.map': 'Carte', - 'journey.share.removeLink': 'Supprimer le lien de partage', - 'journey.share.linkDeleted': 'Lien de partage supprimé', - 'journey.share.deleteFailed': 'Échec de la suppression', - 'journey.share.updateFailed': 'Échec de la mise à jour', - - // Journey — Invite - 'journey.invite.role': 'Rôle', - 'journey.invite.viewer': 'Lecteur', - 'journey.invite.editor': 'Éditeur', - 'journey.invite.invite': 'Inviter', - 'journey.invite.inviting': 'Invitation...', - 'journey.settings.title': 'Paramètres du journal', - 'journey.settings.coverImage': 'Image de couverture', - 'journey.settings.changeCover': 'Changer la couverture', - 'journey.settings.addCover': 'Ajouter une image de couverture', - 'journey.settings.name': 'Nom', - 'journey.settings.subtitle': 'Sous-titre', - 'journey.settings.subtitlePlaceholder': 'ex. Thaïlande, Vietnam et Cambodge', - 'journey.settings.endJourney': 'Archiver le journal', - 'journey.settings.reopenJourney': 'Restaurer le journal', - 'journey.settings.archived': 'Journal archivé', - 'journey.settings.reopened': 'Journal rouvert', - 'journey.settings.endDescription': 'Masque l\'indicateur En direct. Vous pouvez rouvrir à tout moment.', - 'journey.settings.delete': 'Supprimer', - 'journey.settings.deleteJourney': 'Supprimer le journal', - 'journey.settings.deleteMessage': 'Supprimer « {title} » ? Toutes les entrées et photos seront perdues.', - 'journey.settings.saved': 'Paramètres enregistrés', - 'journey.settings.saveFailed': 'Échec de l\'enregistrement', - 'journey.settings.coverUpdated': 'Couverture mise à jour', - 'journey.settings.coverFailed': 'Échec du téléversement', - 'journey.settings.failedToDelete': 'Échec de la suppression', - 'journey.entries.deleteTitle': "Supprimer l'entrée", - 'journey.photosUploaded': '{count} photos téléversées', - 'journey.photosUploadFailed': "Certaines photos n'ont pas pu être téléversées", - 'journey.photosAdded': '{count} photos ajoutées', - 'journey.public.notFound': 'Introuvable', - 'journey.public.notFoundMessage': 'Ce journal n\'existe pas ou le lien a expiré.', - 'journey.public.readOnly': 'Lecture seule · Journal public', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Partagé via', - 'journey.public.madeWith': 'Créé avec', - 'journey.pdf.journeyBook': 'Carnet de voyage', - 'journey.pdf.madeWith': 'Créé avec TREK', - 'journey.pdf.day': 'Jour', - 'journey.pdf.theEnd': 'Fin', - 'journey.pdf.saveAsPdf': 'Enregistrer en PDF', - 'journey.pdf.pages': 'pages', - 'journey.picker.tripPeriod': 'Période du voyage', - 'journey.picker.dateRange': 'Plage de dates', - 'journey.picker.allPhotos': 'Toutes les photos', - 'journey.picker.albums': 'Albums', - 'journey.picker.selected': 'sélectionnés', - 'journey.picker.addTo': 'Ajouter à', - 'journey.picker.newGallery': 'Nouvelle galerie', - 'journey.picker.selectAll': 'Tout sélectionner', - 'journey.picker.deselectAll': 'Tout désélectionner', - 'journey.picker.noAlbums': 'Aucun album trouvé', - 'journey.picker.selectDate': 'Sélectionner une date', - 'journey.picker.search': 'Rechercher', - 'dashboard.greeting.morning': 'Bonjour,', - 'dashboard.greeting.afternoon': 'Bon après-midi,', - 'dashboard.greeting.evening': 'Bonsoir,', - 'dashboard.mobile.liveNow': 'En direct', - 'dashboard.mobile.tripProgress': 'Progression du voyage', - 'dashboard.mobile.daysLeft': '{count} jours restants', - 'dashboard.mobile.places': 'Lieux', - 'dashboard.mobile.buddies': 'Compagnons', - 'dashboard.mobile.newTrip': 'Nouveau voyage', - 'dashboard.mobile.currency': 'Devise', - 'dashboard.mobile.timezone': 'Fuseau horaire', - 'dashboard.mobile.upcomingTrips': 'Voyages à venir', - 'dashboard.mobile.yourTrips': 'Vos voyages', - 'dashboard.mobile.trips': 'voyages', - 'dashboard.mobile.starts': 'Début', - 'dashboard.mobile.duration': 'Durée', - 'dashboard.mobile.day': 'jour', - 'dashboard.mobile.days': 'jours', - 'dashboard.mobile.ongoing': 'En cours', - 'dashboard.mobile.startsToday': 'Commence aujourd\'hui', - 'dashboard.mobile.tomorrow': 'Demain', - 'dashboard.mobile.inDays': 'Dans {count} jours', - 'dashboard.mobile.inMonths': 'Dans {count} mois', - 'dashboard.mobile.completed': 'Terminé', - 'dashboard.mobile.currencyConverter': 'Convertisseur de devises', - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Paramètres', - 'nav.bottomAdmin': 'Administration', - 'nav.bottomLogout': 'Déconnexion', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Ajouter un lieu', - 'dayplan.mobile.searchPlaces': 'Rechercher des lieux...', - 'dayplan.mobile.allAssigned': 'Tous les lieux attribués', - 'dayplan.mobile.noMatch': 'Aucun résultat', - 'dayplan.mobile.createNew': 'Créer un nouveau lieu', - 'admin.addons.catalog.journey.name': 'Journal de voyage', - 'admin.addons.catalog.journey.description': 'Suivi de voyages et journal avec check-ins, photos et récits quotidiens', - // OAuth scope groups - 'oauth.scope.group.trips': 'Voyages', - 'oauth.scope.group.places': 'Lieux', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Bagages', - 'oauth.scope.group.todos': 'Tâches', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Réservations', - 'oauth.scope.group.collab': 'Collaboration', - 'oauth.scope.group.notifications': 'Notifications', - 'oauth.scope.group.vacay': 'Congés', - 'oauth.scope.group.geo': 'Géo', - 'oauth.scope.group.weather': 'Météo', - 'oauth.scope.group.journey': 'Journal de voyage', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Voir les voyages et itinéraires', - 'oauth.scope.trips:read.description': 'Lire les voyages, jours, notes et membres', - 'oauth.scope.trips:write.label': 'Modifier les voyages et itinéraires', - 'oauth.scope.trips:write.description': 'Créer et mettre à jour les voyages, jours, notes et gérer les membres', - 'oauth.scope.trips:delete.label': 'Supprimer des voyages', - 'oauth.scope.trips:delete.description': 'Supprimer définitivement des voyages entiers — cette action est irréversible', - 'oauth.scope.trips:share.label': 'Gérer les liens de partage', - 'oauth.scope.trips:share.description': 'Créer, modifier et révoquer des liens de partage publics', - 'oauth.scope.places:read.label': 'Voir les lieux et données cartographiques', - 'oauth.scope.places:read.description': 'Lire les lieux, affectations de jours, étiquettes et catégories', - 'oauth.scope.places:write.label': 'Gérer les lieux', - 'oauth.scope.places:write.description': 'Créer, modifier et supprimer des lieux, affectations et étiquettes', - 'oauth.scope.atlas:read.label': 'Voir l\'Atlas', - 'oauth.scope.atlas:read.description': 'Lire les pays visités, régions et liste de souhaits', - 'oauth.scope.atlas:write.label': 'Gérer l\'Atlas', - 'oauth.scope.atlas:write.description': 'Marquer des pays et régions visités, gérer la liste de souhaits', - 'oauth.scope.packing:read.label': 'Voir les listes de bagages', - 'oauth.scope.packing:read.description': 'Lire les articles, sacs et assignations de catégories', - 'oauth.scope.packing:write.label': 'Gérer les listes de bagages', - 'oauth.scope.packing:write.description': 'Ajouter, modifier, supprimer, cocher et réordonner les articles et sacs', - 'oauth.scope.todos:read.label': 'Voir les listes de tâches', - 'oauth.scope.todos:read.description': 'Lire les tâches et assignations de catégories', - 'oauth.scope.todos:write.label': 'Gérer les listes de tâches', - 'oauth.scope.todos:write.description': 'Créer, modifier, cocher, supprimer et réordonner les tâches', - 'oauth.scope.budget:read.label': 'Voir le budget', - 'oauth.scope.budget:read.description': 'Lire les dépenses et la répartition du budget', - 'oauth.scope.budget:write.label': 'Gérer le budget', - 'oauth.scope.budget:write.description': 'Créer, modifier et supprimer des dépenses', - 'oauth.scope.reservations:read.label': 'Voir les réservations', - 'oauth.scope.reservations:read.description': 'Lire les réservations et détails d\'hébergement', - 'oauth.scope.reservations:write.label': 'Gérer les réservations', - 'oauth.scope.reservations:write.description': 'Créer, modifier, supprimer et réordonner les réservations', - 'oauth.scope.collab:read.label': 'Voir la collaboration', - 'oauth.scope.collab:read.description': 'Lire les notes, sondages et messages collaboratifs', - 'oauth.scope.collab:write.label': 'Gérer la collaboration', - 'oauth.scope.collab:write.description': 'Créer, modifier et supprimer des notes, sondages et messages', - 'oauth.scope.notifications:read.label': 'Voir les notifications', - 'oauth.scope.notifications:read.description': 'Lire les notifications et le nombre de non-lus', - 'oauth.scope.notifications:write.label': 'Gérer les notifications', - 'oauth.scope.notifications:write.description': 'Marquer les notifications comme lues et y répondre', - 'oauth.scope.vacay:read.label': 'Voir les plans de congés', - 'oauth.scope.vacay:read.description': 'Lire les données, entrées et statistiques de congés', - 'oauth.scope.vacay:write.label': 'Gérer les plans de congés', - 'oauth.scope.vacay:write.description': 'Créer et gérer les entrées de congés, jours fériés et plans d\'équipe', - 'oauth.scope.geo:read.label': 'Cartes et géocodage', - 'oauth.scope.geo:read.description': 'Chercher des lieux, résoudre des URL cartographiques et géocoder des coordonnées', - 'oauth.scope.weather:read.label': 'Prévisions météo', - 'oauth.scope.weather:read.description': 'Obtenir les prévisions météo pour les lieux et dates de voyage', - 'oauth.scope.journey:read.label': 'Voir les journaux de voyage', - 'oauth.scope.journey:read.description': 'Lire les journaux de voyage, les entrées et la liste des contributeurs', - 'oauth.scope.journey:write.label': 'Gérer les journaux de voyage', - 'oauth.scope.journey:write.description': 'Créer, modifier et supprimer les journaux de voyage et leurs entrées', - 'oauth.scope.journey:share.label': 'Gérer les liens de journaux de voyage', - 'oauth.scope.journey:share.description': 'Créer, modifier et révoquer des liens de partage publics pour les journaux de voyage', - - // System notices - 'system_notice.welcome_v1.title': 'Bienvenue sur TREK', - 'system_notice.welcome_v1.body': 'Votre planificateur de voyage tout-en-un. Créez des itinéraires, partagez vos voyages et restez organisé — en ligne ou hors ligne.', - 'system_notice.welcome_v1.cta_label': 'Planifier un voyage', - 'system_notice.welcome_v1.hero_alt': 'Destination de voyage pittoresque avec l\'interface TREK', - 'system_notice.welcome_v1.highlight_plan': 'Itinéraires jour par jour', - 'system_notice.welcome_v1.highlight_share': 'Collaborez avec vos partenaires', - 'system_notice.welcome_v1.highlight_offline': 'Fonctionne hors ligne sur mobile', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Avis précédent', - 'system_notice.pager.next': 'Avis suivant', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': "Aller à l'avis {n}", - 'system_notice.pager.position': 'Avis {current} sur {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Les photos ont bougé dans 3.0', - 'system_notice.v3_photos.body': "**Photos** dans le planificateur ont été supprimées. Tes photos sont en sécurité — TREK n'a jamais modifié ta bibliothèque Immich ou Synology.\n\nLes photos vivent désormais dans l'addon **Journey**. Journey est optionnel — s'il n'est pas encore disponible, demande à ton admin de l'activer dans Admin → Modules.", - 'system_notice.v3_journey.title': 'Découvrez Journey — journal de voyage', - 'system_notice.v3_journey.body': 'Documente tes voyages sous forme de récits enrichis avec chronologies, galeries photos et cartes interactives.', - 'system_notice.v3_journey.cta_label': 'Ouvrir Journey', - 'system_notice.v3_journey.highlight_timeline': 'Chronologie et galerie par jour', - 'system_notice.v3_journey.highlight_photos': 'Import depuis Immich ou Synology', - 'system_notice.v3_journey.highlight_share': 'Partage public — sans connexion requise', - 'system_notice.v3_journey.highlight_export': 'Export en livre photo PDF', - 'system_notice.v3_features.title': 'Plus de nouveautés en 3.0', - 'system_notice.v3_features.body': 'Quelques autres choses à savoir sur cette version.', - 'system_notice.v3_features.highlight_dashboard': 'Tableau de bord repensé mobile-first', - 'system_notice.v3_features.highlight_offline': 'Mode hors ligne complet en PWA', - 'system_notice.v3_features.highlight_search': 'Autocomplétion des lieux en temps réel', - 'system_notice.v3_features.highlight_import': 'Importer des lieux depuis KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP : mise à niveau OAuth 2.1', - 'system_notice.v3_mcp.body': "L'intégration MCP a été entièrement repensée. OAuth 2.1 est désormais la méthode d'authentification recommandée. Les tokens statiques (trek_\u2026) sont dépréciés et seront supprimés dans une future version.", - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommandé (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 scopes de permissions granulaires', - 'system_notice.v3_mcp.highlight_deprecated': 'Tokens statiques trek_ dépréciés', - 'system_notice.v3_mcp.highlight_tools': 'Outils et prompts étendus', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Un mot personnel de ma part', - 'system_notice.v3_thankyou.body': 'Avant de continuer — je veux prendre un instant.\n\nTREK a commencé comme un projet perso que j\'ai construit pour mes propres voyages. Je n\'aurais jamais imaginé qu\'il grandirait au point que 4 000 d\'entre vous lui fassent confiance pour planifier vos aventures. Chaque étoile, chaque issue, chaque demande de fonctionnalité — je les lis toutes, et ce sont elles qui me font tenir pendant les nuits blanches entre un travail à temps plein et l\'université.\n\nJe veux que vous sachiez : TREK sera toujours open source, toujours auto-hébergé, toujours à vous. Pas de tracking, pas d\'abonnements, pas de conditions cachées. Juste un outil construit par quelqu\'un qui aime voyager autant que vous.\n\nUn merci tout particulier à [jubnl](https://github.com/jubnl) — tu es devenu un collaborateur incroyable. Une grande partie de ce qui rend la 3.0 géniale porte ton empreinte. Merci d\'avoir cru en ce projet quand il était encore brut.\n\nEt à chacun d\'entre vous qui a signalé un bug, traduit une chaîne, partagé TREK avec un ami ou simplement l\'a utilisé pour planifier un voyage — **merci**. Vous êtes la raison pour laquelle tout ceci existe.\n\nÀ de nombreuses autres aventures ensemble.\n\n— Maurice\n\n---\n\n[Rejoins la communauté sur Discord](https://discord.gg/7Q6M6jDwzf)\n\nSi TREK rend tes voyages meilleurs, un [petit café](https://ko-fi.com/mauriceboe) aide toujours à garder les lumières allumées.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': "Action requise : conflit de compte utilisateur", - 'system_notice.v3014_whitespace_collision.body': "La mise à niveau 3.0.14 a détecté un ou plusieurs conflits de nom d'utilisateur ou d'adresse e-mail causés par des espaces en début ou en fin de valeur dans les comptes enregistrés. Les comptes concernés ont été renommés automatiquement. Consultez les journaux du serveur pour les lignes commençant par **[migration] WHITESPACE COLLISION** afin d'identifier les comptes nécessitant une vérification.", - 'transport.addTransport': 'Ajouter un transport', - 'transport.modalTitle.create': 'Ajouter un transport', - 'transport.modalTitle.edit': 'Modifier le transport', - 'transport.title': 'Transports', - 'transport.addManual': 'Transport manuel', -} - -export default fr - diff --git a/client/src/i18n/translations/hu.ts b/client/src/i18n/translations/hu.ts deleted file mode 100644 index 9f365430..00000000 --- a/client/src/i18n/translations/hu.ts +++ /dev/null @@ -1,2368 +0,0 @@ -const hu: Record = { - // Általános - 'common.save': 'Mentés', - 'common.showMore': 'Továbbiak', - 'common.showLess': 'Kevesebb', - 'common.cancel': 'Mégse', - 'common.clear': 'Törlés', - 'common.delete': 'Törlés', - 'common.edit': 'Szerkesztés', - 'common.add': 'Hozzáadás', - 'common.loading': 'Betöltés...', - 'common.import': 'Importálás', - 'common.select': 'Kiválaszt', - 'common.selectAll': 'Mindet kiválaszt', - 'common.deselectAll': 'Összes kijelölés megszüntetése', - 'common.error': 'Hiba', - 'common.unknownError': 'Ismeretlen hiba', - 'common.tooManyAttempts': 'Túl sok próbálkozás. Kérjük, próbálja újra később.', - 'common.back': 'Vissza', - 'common.all': 'Összes', - 'common.close': 'Bezárás', - 'common.open': 'Megnyitás', - 'common.upload': 'Feltöltés', - 'common.search': 'Keresés', - 'common.confirm': 'Megerősítés', - 'common.ok': 'OK', - 'common.yes': 'Igen', - 'common.no': 'Nem', - 'common.or': 'vagy', - 'common.none': 'Nincs', - 'common.date': 'Dátum', - 'common.rename': 'Átnevezés', - 'common.discardChanges': 'Változtatások elvetése', - 'common.discard': 'Elveti', - 'common.name': 'Név', - 'common.email': 'E-mail', - 'common.password': 'Jelszó', - 'common.saving': 'Mentés...', - 'trips.memberRemoved': '{username} eltávolítva', - 'trips.memberRemoveError': 'Eltávolítás sikertelen', - 'trips.memberAdded': '{username} hozzáadva', - 'trips.memberAddError': 'Hozzáadás sikertelen', - 'common.expand': 'Kibontás', - 'common.collapse': 'Összecsukás', - 'common.saved': 'Mentve', - 'trips.reminder': 'Emlékeztető', - 'trips.reminderNone': 'Nincs', - 'trips.reminderDay': 'nap', - 'trips.reminderDays': 'nap', - 'trips.reminderCustom': 'Egyéni', - 'trips.reminderDaysBefore': 'nappal indulás előtt', - 'trips.reminderDisabledHint': 'Az utazási emlékeztetők ki vannak kapcsolva. Kapcsold be az Admin > Beállítások > Értesítések menüben.', - 'common.update': 'Frissítés', - 'common.change': 'Módosítás', - 'common.uploading': 'Feltöltés…', - 'common.backToPlanning': 'Vissza a tervezéshez', - 'common.reset': 'Visszaállítás', - - // Navbar - 'nav.trip': 'Utazás', - 'nav.share': 'Megosztás', - 'nav.settings': 'Beállítások', - 'nav.admin': 'Admin', - 'nav.logout': 'Kijelentkezés', - 'nav.lightMode': 'Világos mód', - 'nav.darkMode': 'Sötét mód', - 'nav.autoMode': 'Automatikus mód', - 'nav.administrator': 'Adminisztrátor', - - // Irányítópult - 'dashboard.title': 'Utazásaim', - 'dashboard.subtitle.loading': 'Utazások betöltése...', - 'dashboard.subtitle.trips': '{count} utazás ({archived} archivált)', - 'dashboard.subtitle.empty': 'Indítsd el az első utazásodat', - 'dashboard.subtitle.activeOne': '{count} aktív utazás', - 'dashboard.subtitle.activeMany': '{count} aktív utazás', - 'dashboard.subtitle.archivedSuffix': ' · {count} archivált', - 'dashboard.newTrip': 'Új utazás', - 'dashboard.gridView': 'Rácsnézet', - 'dashboard.listView': 'Listanézet', - 'dashboard.currency': 'Pénznem', - 'dashboard.timezone': 'Időzónák', - 'dashboard.localTime': 'Helyi', - 'dashboard.timezoneCustomTitle': 'Egyéni időzóna', - 'dashboard.timezoneCustomLabelPlaceholder': 'Címke (opcionális)', - 'dashboard.timezoneCustomTzPlaceholder': 'pl. America/New_York', - 'dashboard.timezoneCustomAdd': 'Hozzáadás', - 'dashboard.timezoneCustomErrorEmpty': 'Adj meg egy időzóna-azonosítót', - 'dashboard.timezoneCustomErrorInvalid': 'Érvénytelen időzóna. Használj Europe/Berlin formátumot', - 'dashboard.timezoneCustomErrorDuplicate': 'Már hozzáadva', - 'dashboard.emptyTitle': 'Még nincsenek utazások', - 'dashboard.emptyText': 'Hozd létre az első utazásodat, és kezdj el tervezni helyeket, napi programokat és csomagolási listákat.', - 'dashboard.emptyButton': 'Első utazás létrehozása', - 'dashboard.nextTrip': 'Következő utazás', - 'dashboard.shared': 'Megosztott', - 'dashboard.sharedBy': 'Megosztotta: {name}', - 'dashboard.days': 'nap', - 'dashboard.places': 'hely', - 'dashboard.members': 'Útitársak', - 'dashboard.archive': 'Archiválás', - 'dashboard.copyTrip': 'Másolás', - 'dashboard.copySuffix': 'másolat', - 'dashboard.restore': 'Visszaállítás', - 'dashboard.archived': 'Archivált', - 'dashboard.status.ongoing': 'Folyamatban', - 'dashboard.status.today': 'Ma', - 'dashboard.status.tomorrow': 'Holnap', - 'dashboard.status.past': 'Múlt', - 'dashboard.status.daysLeft': 'Még {count} nap', - 'dashboard.toast.loadError': 'Nem sikerült betölteni az utazásokat', - 'dashboard.toast.created': 'Utazás sikeresen létrehozva!', - 'dashboard.toast.createError': 'Nem sikerült létrehozni', - 'dashboard.toast.updated': 'Utazás frissítve!', - 'dashboard.toast.updateError': 'Nem sikerült frissíteni', - 'dashboard.toast.deleted': 'Utazás törölve', - 'dashboard.toast.deleteError': 'Nem sikerült törölni', - 'dashboard.toast.archived': 'Utazás archiválva', - 'dashboard.toast.archiveError': 'Nem sikerült archiválni', - 'dashboard.toast.restored': 'Utazás visszaállítva', - 'dashboard.toast.restoreError': 'Nem sikerült visszaállítani', - 'dashboard.toast.copied': 'Utazás másolva!', - 'dashboard.toast.copyError': 'Nem sikerült másolni az utazást', - 'dashboard.confirm.delete': '"{title}" utazás törlése? Minden hely és terv véglegesen törlődik.', - 'dashboard.editTrip': 'Utazás szerkesztése', - 'dashboard.createTrip': 'Új utazás létrehozása', - 'dashboard.tripTitle': 'Cím', - 'dashboard.tripTitlePlaceholder': 'pl. Nyár Japánban', - 'dashboard.tripDescription': 'Leírás', - 'dashboard.tripDescriptionPlaceholder': 'Miről szól ez az utazás?', - 'dashboard.startDate': 'Kezdő dátum', - 'dashboard.endDate': 'Záró dátum', - 'dashboard.dayCount': 'Napok száma', - 'dashboard.dayCountHint': 'Hány napot tervezzen, ha nincsenek utazási dátumok megadva.', - 'dashboard.noDateHint': 'Nincs dátum megadva — 7 alapértelmezett nap jön létre. Ezt bármikor módosíthatod.', - 'dashboard.coverImage': 'Borítókép', - 'dashboard.addCoverImage': 'Borítókép hozzáadása', - 'dashboard.addMembers': 'Útitársak', - 'dashboard.addMember': 'Tag hozzáadása', - 'dashboard.coverSaved': 'Borítókép mentve', - 'dashboard.coverUploadError': 'Feltöltés sikertelen', - 'dashboard.coverRemoveError': 'Eltávolítás sikertelen', - 'dashboard.titleRequired': 'A cím megadása kötelező', - 'dashboard.endDateError': 'A záró dátumnak a kezdő dátum után kell lennie', - - // Beállítások - 'settings.title': 'Beállítások', - 'settings.subtitle': 'Személyes beállítások konfigurálása', - 'settings.tabs.display': 'Megjelenés', - 'settings.tabs.map': 'Térkép', - 'settings.tabs.notifications': 'Értesítések', - 'settings.tabs.integrations': 'Integrációk', - 'settings.tabs.account': 'Fiók', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Névjegy', - 'settings.map': 'Térkép', - 'settings.mapTemplate': 'Térkép sablon', - 'settings.mapTemplatePlaceholder.select': 'Sablon kiválasztása...', - 'settings.mapDefaultHint': 'Hagyd üresen az OpenStreetMap használatához (alapértelmezett)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL sablon a térképcsempékhez', - 'settings.mapProvider': 'Térkép szolgáltató', - 'settings.mapProviderHint': 'A Trip Planner és Journey térképekre érvényes. Az Atlas mindig Leafletet használ.', - 'settings.mapLeafletSubtitle': 'Klasszikus 2D, bármilyen raszter csempe', - 'settings.mapMapboxSubtitle': 'Vektoros csempék, 3D épületek és terep', - 'settings.mapExperimental': 'Kísérleti', - 'settings.mapMapboxToken': 'Mapbox hozzáférési token', - 'settings.mapMapboxTokenHint': 'Publikus token (pk.*) innen:', - 'settings.mapMapboxTokenLink': 'mapbox.com → Hozzáférési tokenek', - 'settings.mapStyle': 'Térkép stílus', - 'settings.mapStylePlaceholder': 'Válassz Mapbox stílust', - 'settings.mapStyleHint': 'Preset vagy saját mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D épületek és terep', - 'settings.map3dHint': 'Dőlés + valódi 3D épület-kiemelés — minden stílussal működik, beleértve a műholdast.', - 'settings.mapHighQuality': 'Magas minőség mód', - 'settings.mapHighQualityHint': 'Antialiasing + földgömb-vetítés az élesebb kontúrokért és egy valósághű világnézethez.', - 'settings.mapHighQualityWarning': 'Gyengébb eszközökön befolyásolhatja a teljesítményt.', - 'settings.mapTipLabel': 'Tipp:', - 'settings.mapTip': 'Jobb klikk és húzás a térkép forgatásához/döntéséhez. Középső kattintás hely hozzáadásához (a jobb klikk a forgatáshoz van fenntartva).', - 'settings.latitude': 'Szélességi fok', - 'settings.longitude': 'Hosszúsági fok', - 'settings.saveMap': 'Térkép mentése', - 'settings.apiKeys': 'API kulcsok', - 'settings.mapsKey': 'Google Maps API kulcs', - 'settings.mapsKeyHint': 'Helykereséséhez. Places API (New) szükséges. Létrehozás: console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API kulcs', - 'settings.weatherKeyHint': 'Időjárás adatokhoz. Ingyenes: openweathermap.org/api', - 'settings.keyPlaceholder': 'Kulcs megadása...', - 'settings.configured': 'Konfigurálva', - 'settings.saveKeys': 'Kulcsok mentése', - 'settings.display': 'Megjelenítés', - 'settings.colorMode': 'Színmód', - 'settings.light': 'Világos', - 'settings.dark': 'Sötét', - 'settings.auto': 'Automatikus', - 'settings.language': 'Nyelv', - 'settings.temperature': 'Hőmérséklet egység', - 'settings.timeFormat': 'Időformátum', - 'settings.blurBookingCodes': 'Foglalási kódok elrejtése', - 'settings.notifications': 'Értesítések', - 'settings.notifyTripInvite': 'Utazási meghívók', - 'settings.notifyBookingChange': 'Foglalási változások', - 'settings.notifyTripReminder': 'Utazási emlékeztetők', - 'settings.notifyTodoDue': 'Teendő esedékes', - 'settings.notifyVacayInvite': 'Vacay összevonási meghívók', - 'settings.notifyPhotosShared': 'Megosztott fotók (Immich)', - 'settings.notifyCollabMessage': 'Csevegés üzenetek (Collab)', - 'settings.notifyPackingTagged': 'Csomagolási lista: hozzárendelések', - 'settings.notifyWebhook': 'Webhook értesítések', - 'settings.notificationsDisabled': 'Az értesítések nincsenek beállítva. Kérje meg a rendszergazdát, hogy engedélyezze az e-mail vagy webhook értesítéseket.', - 'settings.notificationsActive': 'Aktív csatorna', - 'settings.notificationsManagedByAdmin': 'Az értesítési eseményeket az adminisztrátor konfigurálja.', - 'settings.on': 'Be', - 'settings.off': 'Ki', - 'settings.mcp.title': 'MCP konfiguráció', - 'settings.mcp.endpoint': 'MCP végpont', - 'settings.mcp.clientConfig': 'Kliens konfiguráció', - 'settings.mcp.clientConfigHint': 'Cserélje ki a részt egy API tokenre az alábbi listából. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).', - 'settings.mcp.clientConfigHintOAuth': 'Cserélje ki a és részeket a fent létrehozott OAuth 2.1 kliens adataival. Az mcp-remote megnyitja a böngészőt az első csatlakozáskor az engedélyezés elvégzéséhez. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).', - 'settings.mcp.copy': 'Másolás', - 'settings.mcp.copied': 'Másolva!', - 'settings.mcp.apiTokens': 'API tokenek', - 'settings.mcp.createToken': 'Új token létrehozása', - 'settings.mcp.noTokens': 'Még nincsenek tokenek. Hozzon létre egyet MCP kliensek csatlakoztatásához.', - 'settings.mcp.tokenCreatedAt': 'Létrehozva', - 'settings.mcp.tokenUsedAt': 'Használva', - 'settings.mcp.deleteTokenTitle': 'Token törlése', - 'settings.mcp.deleteTokenMessage': 'Ez a token azonnal érvénytelenné válik. Minden MCP kliens, amely használja, elveszíti a hozzáférést.', - 'settings.mcp.modal.createTitle': 'API token létrehozása', - 'settings.mcp.modal.tokenName': 'Token neve', - 'settings.mcp.modal.tokenNamePlaceholder': 'pl. Claude Desktop, Munkahelyi laptop', - 'settings.mcp.modal.creating': 'Létrehozás…', - 'settings.mcp.modal.create': 'Token létrehozása', - 'settings.mcp.modal.createdTitle': 'Token létrehozva', - 'settings.mcp.modal.createdWarning': 'Ez a token csak egyszer jelenik meg. Másolja és mentse el most — nem lehet visszaállítani.', - 'settings.mcp.modal.done': 'Kész', - 'settings.mcp.toast.created': 'Token létrehozva', - 'settings.mcp.toast.createError': 'Nem sikerült létrehozni a tokent', - 'settings.mcp.toast.deleted': 'Token törölve', - 'settings.mcp.toast.deleteError': 'Nem sikerült törölni a tokent', - 'settings.mcp.apiTokensDeprecated': 'Az API tokenek elavultak és egy jövőbeli verzióban eltávolításra kerülnek. Kérjük, használjon helyettük OAuth 2.1 klienseket.', - 'settings.oauth.clients': 'OAuth 2.1 kliensek', - 'settings.oauth.clientsHint': 'Regisztráljon OAuth 2.1 klienseket, hogy a harmadik féltől származó MCP alkalmazások (Claude Web, Cursor stb.) statikus tokenek nélkül csatlakozhassanak.', - 'settings.oauth.createClient': 'Új kliens', - 'settings.oauth.noClients': 'Nincs regisztrált OAuth kliens.', - 'settings.oauth.clientId': 'Kliens azonosító', - 'settings.oauth.clientSecret': 'Kliens titok', - 'settings.oauth.deleteClient': 'Kliens törlése', - 'settings.oauth.deleteClientMessage': 'Ez a kliens és az összes aktív munkamenet véglegesen törlésre kerül. Minden alkalmazás, amely ezt használja, azonnal elveszíti a hozzáférést.', - 'settings.oauth.rotateSecret': 'Titok megújítása', - 'settings.oauth.rotateSecretMessage': 'Új kliens titok kerül generálásra és az összes meglévő munkamenet azonnal érvénytelenné válik. Frissítse alkalmazását a párbeszéd bezárása előtt.', - 'settings.oauth.rotateSecretConfirm': 'Megújítás', - 'settings.oauth.rotateSecretConfirming': 'Megújítás…', - 'settings.oauth.rotateSecretDoneTitle': 'Új titok generálva', - 'settings.oauth.rotateSecretDoneWarning': 'Ez a titok csak egyszer jelenik meg. Másolja most és frissítse alkalmazását — az összes korábbi munkamenet érvénytelenné vált.', - 'settings.oauth.activeSessions': 'Aktív OAuth munkamenetek', - 'settings.oauth.sessionScopes': 'Jogosultságok', - 'settings.oauth.sessionExpires': 'Lejár', - 'settings.oauth.revoke': 'Visszavonás', - 'settings.oauth.revokeSession': 'Munkamenet visszavonása', - 'settings.oauth.revokeSessionMessage': 'Ez azonnal visszavonja a hozzáférést ehhez az OAuth munkamenethez.', - 'settings.oauth.modal.createTitle': 'OAuth kliens regisztrálása', - 'settings.oauth.modal.presets': 'Gyors beállítások', - 'settings.oauth.modal.clientName': 'Alkalmazás neve', - 'settings.oauth.modal.clientNamePlaceholder': 'pl. Claude Web, Az én MCP appom', - 'settings.oauth.modal.redirectUris': 'Átirányítási URI-k', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Soronként egy URI. HTTPS szükséges (localhost kivételével). Pontos egyezés szükséges.', - 'settings.oauth.modal.scopes': 'Engedélyezett jogosultságok', - 'settings.oauth.modal.scopesHint': 'A list_trips és get_trip_summary mindig elérhető — jogosultság nélkül. Segítenek az AI-nak megtalálni az utazás azonosítókat.', - 'settings.oauth.modal.selectAll': 'Összes kijelölése', - 'settings.oauth.modal.deselectAll': 'Összes kijelölés törlése', - 'settings.oauth.modal.creating': 'Regisztrálás…', - 'settings.oauth.modal.create': 'Kliens regisztrálása', - 'settings.oauth.modal.createdTitle': 'Kliens regisztrálva', - 'settings.oauth.modal.createdWarning': 'A kliens titok csak egyszer jelenik meg. Másolja most — nem állítható helyre.', - 'settings.oauth.toast.createError': 'Az OAuth kliens regisztrálása sikertelen', - 'settings.oauth.toast.deleted': 'OAuth kliens törölve', - 'settings.oauth.toast.deleteError': 'Az OAuth kliens törlése sikertelen', - 'settings.oauth.toast.revoked': 'Munkamenet visszavonva', - 'settings.oauth.toast.revokeError': 'A munkamenet visszavonása sikertelen', - 'settings.oauth.toast.rotateError': 'A kliens titok megújítása sikertelen', - 'settings.oauth.modal.machineClient': 'Gépi kliens (böngészős bejelentkezés nélkül)', - 'settings.oauth.modal.machineClientHint': 'client_credentials grant használata — nincs szükség átirányítási URI-kra. A token közvetlenül client_id + client_secret segítségével kerül kiállításra, és a kiválasztott hatókörökön belül az Ön nevében jár el.', - 'settings.oauth.modal.machineClientUsage': 'Token lekérése: POST /oauth/token a grant_type=client_credentials, client_id és client_secret értékekkel. Böngésző és frissítési token nélkül.', - 'settings.oauth.badge.machine': 'gépi', - 'settings.account': 'Fiók', - 'settings.about': 'Névjegy', - 'settings.about.reportBug': 'Hiba bejelentése', - 'settings.about.reportBugHint': 'Problémát találtál? Jelezd nekünk', - 'settings.about.featureRequest': 'Funkció javaslat', - 'settings.about.featureRequestHint': 'Javasolj egy új funkciót', - 'settings.about.wikiHint': 'Dokumentáció és útmutatók', - 'settings.about.supporters.badge': 'Havi támogatók', - 'settings.about.supporters.title': 'Útitársak a TREK mellett', - 'settings.about.supporters.subtitle': 'Miközben te a következő útvonaladat tervezed, ők a TREK jövőjét tervezik velem együtt. Havi hozzájárulásuk közvetlenül fejlesztésre és valódi órákra fordítódik — hogy a TREK Open Source maradhasson.', - 'settings.about.supporters.since': 'támogató {date} óta', - 'settings.about.supporters.tierEmpty': 'Légy az első', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'A TREK egy saját szerveren üzemeltetett útitervező, amely segít az utazásaid megszervezésében az első ötlettől az utolsó emlékig. Napi tervezés, költségvetés, csomagolási listák, fotók és még sok más — minden egy helyen, a saját szervereden.', - 'settings.about.madeWith': 'Készítve', - 'settings.about.madeBy': 'Maurice és egy növekvő nyílt forráskódú közösség által.', - 'settings.username': 'Felhasználónév', - 'settings.email': 'E-mail', - 'settings.role': 'Szerepkör', - 'settings.roleAdmin': 'Adminisztrátor', - 'settings.oidcLinked': 'Összekapcsolva:', - 'settings.changePassword': 'Jelszó módosítása', - 'settings.currentPassword': 'Jelenlegi jelszó', - 'settings.newPassword': 'Új jelszó', - 'settings.confirmPassword': 'Új jelszó megerősítése', - 'settings.updatePassword': 'Jelszó frissítése', - 'settings.passwordRequired': 'Kérjük, add meg a jelenlegi és az új jelszót', - 'settings.currentPasswordRequired': 'A jelenlegi jelszó megadása kötelező', - 'settings.passwordTooShort': 'A jelszónak legalább 8 karakter hosszúnak kell lennie', - 'settings.passwordWeak': 'A jelszónak tartalmaznia kell nagybetűt, kisbetűt, számot és speciális karaktert', - 'settings.passwordMismatch': 'A jelszavak nem egyeznek', - 'settings.passwordChanged': 'Jelszó sikeresen módosítva', - 'settings.deleteAccount': 'Törlés', - 'settings.deleteAccountTitle': 'Biztosan törölni szeretnéd a fiókodat?', - 'settings.deleteAccountWarning': 'A fiókod és minden utazásod, helyed és fájlod véglegesen törlődik. Ez a művelet nem vonható vissza.', - 'settings.deleteAccountConfirm': 'Végleges törlés', - 'settings.deleteBlockedTitle': 'Törlés nem lehetséges', - 'settings.deleteBlockedMessage': 'Te vagy az egyetlen adminisztrátor. Nevezz ki egy másik felhasználót adminnak, mielőtt törölnéd a fiókodat.', - 'settings.roleUser': 'Felhasználó', - 'settings.saveProfile': 'Mentés', - 'settings.toast.mapSaved': 'Térképbeállítások mentve', - 'settings.toast.keysSaved': 'API kulcsok mentve', - 'settings.toast.displaySaved': 'Megjelenítési beállítások mentve', - 'settings.toast.profileSaved': 'Profil frissítve', - 'settings.uploadAvatar': 'Profilkép feltöltése', - 'settings.removeAvatar': 'Profilkép eltávolítása', - 'settings.avatarUploaded': 'Profilkép frissítve', - 'settings.avatarRemoved': 'Profilkép eltávolítva', - 'settings.avatarError': 'Feltöltés sikertelen', - 'settings.mfa.title': 'Kétfaktoros hitelesítés (2FA)', - 'settings.mfa.description': 'Egy második lépést ad a bejelentkezéshez e-mail és jelszó használatakor. Használj hitelesítő alkalmazást (Google Authenticator, Authy stb.).', - 'settings.mfa.requiredByPolicy': 'A rendszergazda kétlépcsős hitelesítést ír elő. Állíts be hitelesítő alkalmazást lent, mielőtt továbblépnél.', - 'settings.mfa.backupTitle': 'Tartalék kódok', - 'settings.mfa.backupDescription': 'Használd ezeket az egyszer használatos kódokat, ha elveszíted a hozzáférést a hitelesítő alkalmazásodhoz.', - 'settings.mfa.backupWarning': 'Mentsd el ezeket most. Minden kód csak egyszer használható.', - 'settings.mfa.backupCopy': 'Kódok másolása', - 'settings.mfa.backupDownload': 'TXT letöltése', - 'settings.mfa.backupPrint': 'Nyomtatás / PDF', - 'settings.mfa.backupCopied': 'Tartalék kódok másolva', - 'settings.mfa.enabled': '2FA engedélyezve van a fiókodban.', - 'settings.mfa.disabled': '2FA nincs engedélyezve.', - 'settings.mfa.setup': 'Hitelesítő beállítása', - 'settings.mfa.scanQr': 'Olvasd be ezt a QR-kódot az alkalmazásoddal, vagy add meg manuálisan a titkos kulcsot.', - 'settings.mfa.secretLabel': 'Titkos kulcs (kézi megadás)', - 'settings.mfa.codePlaceholder': '6 jegyű kód', - 'settings.mfa.enable': '2FA engedélyezése', - 'settings.mfa.cancelSetup': 'Mégse', - 'settings.mfa.disableTitle': '2FA kikapcsolása', - 'settings.mfa.disableHint': 'Add meg a fiókod jelszavát és a hitelesítő alkalmazás aktuális kódját.', - 'settings.mfa.disable': '2FA kikapcsolása', - 'settings.mfa.toastEnabled': 'Kétfaktoros hitelesítés engedélyezve', - 'settings.mfa.toastDisabled': 'Kétfaktoros hitelesítés kikapcsolva', - 'settings.mfa.demoBlocked': 'Demo módban nem érhető el', - 'settings.mustChangePassword': 'A folytatás előtt meg kell változtatnod a jelszavad. Kérjük, adj meg egy új jelszót alább.', - 'admin.notifications.title': 'Értesítések', - 'admin.notifications.hint': 'Válasszon értesítési csatornát. Egyszerre csak egy lehet aktív.', - 'admin.notifications.none': 'Kikapcsolva', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Értesítési beállítások mentése', - 'admin.notifications.saved': 'Értesítési beállítások mentve', - 'admin.notifications.testWebhook': 'Teszt webhook küldése', - 'admin.notifications.testWebhookSuccess': 'Teszt webhook sikeresen elküldve', - 'admin.notifications.testWebhookFailed': 'Teszt webhook küldése sikertelen', - 'admin.smtp.title': 'E-mail és értesítések', - 'admin.smtp.hint': 'SMTP konfiguráció e-mail értesítések küldéséhez.', - 'admin.smtp.testButton': 'Teszt e-mail küldése', - 'admin.webhook.hint': 'Értesítések küldése külső webhookra (Discord, Slack stb.).', - 'admin.smtp.testSuccess': 'Teszt e-mail sikeresen elküldve', - 'admin.smtp.testFailed': 'Teszt e-mail küldése sikertelen', - 'dayplan.icsTooltip': 'Naptár exportálása (ICS)', - 'share.linkTitle': 'Nyilvános link', - 'share.linkHint': 'Hozz létre egy linket, amellyel bárki megtekintheti ezt az utazást bejelentkezés nélkül. Csak olvasható — szerkesztés nem lehetséges.', - 'share.createLink': 'Link létrehozása', - 'share.deleteLink': 'Link törlése', - 'share.createError': 'Nem sikerült létrehozni a linket', - 'common.copy': 'Másolás', - 'common.copied': 'Másolva', - 'share.permMap': 'Térkép és terv', - 'share.permBookings': 'Foglalások', - 'share.permPacking': 'Csomagolás', - 'shared.expired': 'Link lejárt vagy érvénytelen', - 'shared.expiredHint': 'Ez a megosztott utazási link már nem aktív.', - 'shared.readOnly': 'Csak olvasható megosztott nézet', - 'shared.tabPlan': 'Terv', - 'shared.tabBookings': 'Foglalások', - 'shared.tabPacking': 'Csomagolás', - 'shared.tabBudget': 'Költségvetés', - 'shared.tabChat': 'Csevegés', - 'shared.days': 'nap', - 'shared.places': 'hely', - 'shared.other': 'Egyéb', - 'shared.totalBudget': 'Teljes költségvetés', - 'shared.messages': 'üzenet', - 'shared.sharedVia': 'Megosztva:', - 'shared.confirmed': 'Megerősítve', - 'shared.pending': 'Függőben', - 'share.permBudget': 'Költségvetés', - 'share.permCollab': 'Csevegés', - - // Bejelentkezés - 'login.error': 'Bejelentkezés sikertelen. Kérjük, ellenőrizd a megadott adatokat.', - 'login.tagline': 'Az utazásaid.\nA terved.', - 'login.description': 'Tervezz utazásokat közösen interaktív térképekkel, költségvetéssel és valós idejű szinkronizálással.', - 'login.features.maps': 'Interaktív térképek', - 'login.features.mapsDesc': 'Google Places, útvonalak és csoportosítás', - 'login.features.realtime': 'Valós idejű szinkron', - 'login.features.realtimeDesc': 'Közös tervezés WebSocket-en keresztül', - 'login.features.budget': 'Költségvetés-követés', - 'login.features.budgetDesc': 'Kategóriák, diagramok és személyenkénti költségek', - 'login.features.collab': 'Együttműködés', - 'login.features.collabDesc': 'Többfelhasználós, megosztott utazásokkal', - 'login.features.packing': 'Csomagolási listák', - 'login.features.packingDesc': 'Kategóriák és haladás', - 'login.features.bookings': 'Foglalások', - 'login.features.bookingsDesc': 'Repülők, szállodák, éttermek és még több', - 'login.features.files': 'Dokumentumok', - 'login.features.filesDesc': 'Fájlok feltöltése és kezelése', - 'login.features.routes': 'Útvonal-optimalizálás', - 'login.features.routesDesc': 'Automatikus optimalizálás és Google Maps export', - 'login.selfHosted': 'Saját üzemeltetés \u00B7 Nyílt forráskód \u00B7 Az adataid nálad maradnak', - 'login.title': 'Bejelentkezés', - 'login.subtitle': 'Üdv újra', - 'login.signingIn': 'Bejelentkezés…', - 'login.signIn': 'Bejelentkezés', - 'login.createAdmin': 'Admin fiók létrehozása', - 'login.createAdminHint': 'Hozd létre az első admin fiókot a TREK-hez.', - 'login.setNewPassword': 'Új jelszó beállítása', - 'login.setNewPasswordHint': 'A folytatás előtt meg kell változtatnia a jelszavát.', - 'login.createAccount': 'Fiók létrehozása', - 'login.createAccountHint': 'Új fiók regisztrálása.', - 'login.creating': 'Létrehozás…', - 'login.noAccount': 'Nincs még fiókod?', - 'login.hasAccount': 'Már van fiókod?', - 'login.register': 'Regisztráció', - 'login.emailPlaceholder': 'email@cimed.hu', - 'login.username': 'Felhasználónév', - 'login.oidc.registrationDisabled': 'A regisztráció le van tiltva. Lépj kapcsolatba az adminisztrátorral.', - 'login.oidc.noEmail': 'Nem érkezett e-mail a szolgáltatótól.', - 'login.oidc.tokenFailed': 'Hitelesítés sikertelen.', - 'login.oidc.invalidState': 'Érvénytelen munkamenet. Kérjük, próbáld újra.', - 'login.demoFailed': 'Demo bejelentkezés sikertelen', - 'login.oidcSignIn': 'Bejelentkezés ezzel: {name}', - 'login.oidcOnly': 'A jelszavas hitelesítés le van tiltva. Kérjük, jelentkezz be az SSO szolgáltatódon keresztül.', - 'login.oidcLoggedOut': 'Kijelentkeztél. Jelentkezz be újra az SSO szolgáltatódon keresztül.', - 'login.demoHint': 'Próbáld ki a demót — regisztráció nélkül', - 'login.mfaTitle': 'Kétfaktoros hitelesítés', - 'login.mfaSubtitle': 'Add meg a 6 jegyű kódot a hitelesítő alkalmazásból.', - 'login.mfaCodeLabel': 'Ellenőrző kód', - 'login.mfaCodeRequired': 'Add meg a kódot a hitelesítő alkalmazásból.', - 'login.mfaHint': 'Nyisd meg a Google Authenticator, Authy vagy más TOTP alkalmazást.', - 'login.mfaBack': '← Vissza a bejelentkezéshez', - 'login.mfaVerify': 'Ellenőrzés', - 'login.invalidInviteLink': 'Érvénytelen vagy lejárt meghívólink', - 'login.oidcFailed': 'OIDC bejelentkezés sikertelen', - 'login.usernameRequired': 'A felhasználónév kötelező', - 'login.passwordMinLength': 'A jelszónak legalább 8 karakter hosszúnak kell lennie', - 'login.forgotPassword': 'Elfelejtetted a jelszavad?', - 'login.forgotPasswordTitle': 'Jelszó visszaállítása', - 'login.forgotPasswordBody': 'Írd be a regisztrációnál használt e-mail-címet. Ha létezik fiók, küldünk egy visszaállítási linket.', - 'login.forgotPasswordSubmit': 'Link küldése', - 'login.forgotPasswordSentTitle': 'Nézd meg az e-mailjeidet', - 'login.forgotPasswordSentBody': 'Ha létezik fiók ehhez az e-mailhez, a visszaállítási link úton van. 60 perc után lejár.', - 'login.forgotPasswordSmtpHintOff': 'Megjegyzés: a rendszergazda nem konfigurálta az SMTP-t, ezért a visszaállítási link e-mail helyett a szerverkonzolba kerül.', - 'login.backToLogin': 'Vissza a bejelentkezéshez', - 'login.newPassword': 'Új jelszó', - 'login.confirmPassword': 'Új jelszó megerősítése', - 'login.passwordsDontMatch': 'A jelszavak nem egyeznek', - 'login.mfaCode': '2FA-kód', - 'login.resetPasswordTitle': 'Új jelszó beállítása', - 'login.resetPasswordBody': 'Válassz erős jelszót, amit itt még nem használtál. Minimum 8 karakter.', - 'login.resetPasswordMfaBody': 'Add meg a 2FA-kódodat vagy egy tartalék kódot a visszaállítás befejezéséhez.', - 'login.resetPasswordSubmit': 'Jelszó visszaállítása', - 'login.resetPasswordVerify': 'Ellenőrzés és visszaállítás', - 'login.resetPasswordSuccessTitle': 'Jelszó frissítve', - 'login.resetPasswordSuccessBody': 'Mostantól bejelentkezhetsz az új jelszavaddal.', - 'login.resetPasswordInvalidLink': 'Érvénytelen visszaállítási link', - 'login.resetPasswordInvalidLinkBody': 'A link hiányzik vagy sérült. A folytatáshoz kérj egy újat.', - 'login.resetPasswordFailed': 'A visszaállítás nem sikerült. A link lehet, hogy lejárt.', - - // Regisztráció - 'register.passwordMismatch': 'A jelszavak nem egyeznek', - 'register.passwordTooShort': 'A jelszónak legalább 8 karakter hosszúnak kell lennie', - 'register.failed': 'Regisztráció sikertelen', - 'register.getStarted': 'Kezdjük', - 'register.subtitle': 'Hozz létre egy fiókot, és kezdd el megtervezni álomutazásaidat.', - 'register.feature1': 'Korlátlan utazási tervek', - 'register.feature2': 'Interaktív térképnézet', - 'register.feature3': 'Helyek és kategóriák kezelése', - 'register.feature4': 'Foglalások nyomon követése', - 'register.feature5': 'Csomagolási listák készítése', - 'register.feature6': 'Fényképek és fájlok tárolása', - 'register.createAccount': 'Fiók létrehozása', - 'register.startPlanning': 'Kezdd el az utazástervezést', - 'register.minChars': 'Min. 6 karakter', - 'register.confirmPassword': 'Jelszó megerősítése', - 'register.repeatPassword': 'Jelszó ismétlése', - 'register.registering': 'Regisztráció...', - 'register.register': 'Regisztráció', - 'register.hasAccount': 'Már van fiókod?', - 'register.signIn': 'Bejelentkezés', - - // Admin - 'admin.title': 'Adminisztráció', - 'admin.subtitle': 'Felhasználókezelés és rendszerbeállítások', - 'admin.tabs.users': 'Felhasználók', - 'admin.tabs.categories': 'Kategóriák', - 'admin.tabs.backup': 'Biztonsági mentés', - 'admin.stats.users': 'Felhasználók', - 'admin.stats.trips': 'Utazások', - 'admin.stats.places': 'Helyek', - 'admin.stats.photos': 'Fotók', - 'admin.stats.files': 'Fájlok', - 'admin.table.user': 'Felhasználó', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Szerepkör', - 'admin.table.created': 'Létrehozva', - 'admin.table.lastLogin': 'Utolsó belépés', - 'admin.table.actions': 'Műveletek', - 'admin.you': '(Te)', - 'admin.editUser': 'Felhasználó szerkesztése', - 'admin.newPassword': 'Új jelszó', - 'admin.newPasswordHint': 'Hagyd üresen a jelenlegi jelszó megtartásához', - 'admin.deleteUser': '"{name}" felhasználó törlése? Minden utazás véglegesen törlődik.', - 'admin.deleteUserTitle': 'Felhasználó törlése', - 'admin.newPasswordPlaceholder': 'Új jelszó megadása…', - 'admin.toast.loadError': 'Nem sikerült betölteni az admin adatokat', - 'admin.toast.userUpdated': 'Felhasználó frissítve', - 'admin.toast.updateError': 'Nem sikerült frissíteni', - 'admin.toast.userDeleted': 'Felhasználó törölve', - 'admin.toast.deleteError': 'Nem sikerült törölni', - 'admin.toast.cannotDeleteSelf': 'Saját fiók nem törölhető', - 'admin.toast.userCreated': 'Felhasználó létrehozva', - 'admin.toast.createError': 'Nem sikerült létrehozni a felhasználót', - 'admin.toast.fieldsRequired': 'Felhasználónév, e-mail és jelszó megadása kötelező', - 'admin.createUser': 'Felhasználó létrehozása', - 'admin.invite.title': 'Meghívó linkek', - 'admin.invite.subtitle': 'Egyszer használatos regisztrációs linkek létrehozása', - 'admin.invite.create': 'Link létrehozása', - 'admin.invite.createAndCopy': 'Létrehozás és másolás', - 'admin.invite.empty': 'Még nincsenek meghívó linkek', - 'admin.invite.maxUses': 'Max. használat', - 'admin.invite.expiry': 'Lejárat', - 'admin.invite.uses': 'felhasználva', - 'admin.invite.expiresAt': 'lejár', - 'admin.invite.createdBy': 'készítette', - 'admin.invite.active': 'Aktív', - 'admin.invite.expired': 'Lejárt', - 'admin.invite.usedUp': 'Elhasználva', - 'admin.invite.copied': 'Meghívó link vágólapra másolva', - 'admin.invite.copyLink': 'Link másolása', - 'admin.invite.deleted': 'Meghívó link törölve', - 'admin.invite.createError': 'Nem sikerült létrehozni a meghívó linket', - 'admin.invite.deleteError': 'Nem sikerült törölni a meghívó linket', - 'admin.tabs.settings': 'Beállítások', - 'admin.allowRegistration': 'Regisztráció engedélyezése', - 'admin.allowRegistrationHint': 'Új felhasználók regisztrálhatják magukat', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Kétlépcsős hitelesítés (2FA) kötelezővé tétele', - 'admin.requireMfaHint': 'A 2FA nélküli felhasználóknak a Beállításokban kell befejezniük a beállítást az alkalmazás használata előtt.', - 'admin.apiKeys': 'API kulcsok', - 'admin.apiKeysHint': 'Opcionális. Bővített helyadatokat tesz lehetővé, például fotókat és időjárást.', - 'admin.mapsKey': 'Google Maps API kulcs', - 'admin.mapsKeyHint': 'Helykereséshez szükséges. Létrehozás: console.cloud.google.com', - 'admin.mapsKeyHintLong': 'API kulcs nélkül az OpenStreetMap szolgál helykeresésre. Google API kulccsal képek, értékelések és nyitvatartás is betölthetők. Létrehozás: console.cloud.google.com.', - 'admin.recommended': 'Ajánlott', - 'admin.weatherKey': 'OpenWeatherMap API kulcs', - 'admin.weatherKeyHint': 'Időjárás adatokhoz. Ingyenes: openweathermap.org', - 'admin.validateKey': 'Teszt', - 'admin.keyValid': 'Csatlakozva', - 'admin.keyInvalid': 'Érvénytelen', - 'admin.keySaved': 'API kulcsok mentve', - 'admin.oidcTitle': 'Egyszeri bejelentkezés (OIDC)', - 'admin.oidcSubtitle': 'Bejelentkezés külső szolgáltatókon keresztül, pl. Google, Apple, Authentik vagy Keycloak.', - 'admin.oidcDisplayName': 'Megjelenítendő név', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'A szolgáltató OpenID Connect Issuer URL-je. pl. https://accounts.google.com', - 'admin.oidcSaved': 'OIDC konfiguráció mentve', - 'admin.oidcOnlyMode': 'Jelszavas hitelesítés letiltása', - 'admin.oidcOnlyModeHint': 'Ha engedélyezve van, csak SSO bejelentkezés lehetséges. A jelszavas bejelentkezés és regisztráció le van tiltva.', - - // Fájltípusok - 'admin.fileTypes': 'Engedélyezett fájltípusok', - 'admin.fileTypesHint': 'Állítsd be, milyen fájltípusokat tölthetnek fel a felhasználók.', - 'admin.fileTypesFormat': 'Vesszővel elválasztott kiterjesztések (pl. jpg,png,pdf,doc). Használj *-ot az összes típus engedélyezéséhez.', - 'admin.fileTypesSaved': 'Fájltípus-beállítások mentve', - - // Csomagolási sablonok és poggyászkövetés - 'admin.placesPhotos.title': 'Helyfotók', - 'admin.placesPhotos.subtitle': 'Fotók lekérése a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához. A Wikimedia-fotók nem érintettek.', - 'admin.placesAutocomplete.title': 'Hely automatikus kiegészítése', - 'admin.placesAutocomplete.subtitle': 'A Google Places API használata keresési javaslatokhoz. Tiltsa le az API-kvóta megtakarításához.', - 'admin.placesDetails.title': 'Hely részletei', - 'admin.placesDetails.subtitle': 'Részletes helyinformációk lekérése (nyitvatartás, értékelés, weboldal) a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához.', - 'admin.bagTracking.title': 'Poggyászkövetés', - 'admin.bagTracking.subtitle': 'Súly- és táskahozzárendelés engedélyezése csomagolási tételeknél', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Valós idejű üzenetküldés az együttműködéshez', - 'admin.collab.notes.title': 'Jegyzetek', - 'admin.collab.notes.subtitle': 'Megosztott jegyzetek és dokumentumok', - 'admin.collab.polls.title': 'Szavazások', - 'admin.collab.polls.subtitle': 'Csoportos szavazások', - 'admin.collab.whatsnext.title': 'Mi következik', - 'admin.collab.whatsnext.subtitle': 'Tevékenységjavaslatok és következő lépések', - 'admin.tabs.config': 'Személyre szabás', - 'admin.tabs.defaults': 'Alapértelmezett beállítások', - 'admin.defaultSettings.title': 'Alapértelmezett felhasználói beállítások', - 'admin.defaultSettings.description': 'Állítson be alapértelmezett értékeket az egész példányra. Azok a felhasználók, akik nem módosítottak egy beállítást, ezeket az értékeket fogják látni. A saját módosításaik mindig elsőbbséget élveznek.', - 'admin.defaultSettings.saved': 'Alapértelmezett mentve', - 'admin.defaultSettings.reset': 'Visszaállítás a beépített alapértelmezésre', - 'admin.defaultSettings.resetToBuiltIn': 'visszaállítás', - 'admin.tabs.templates': 'Csomagolási sablonok', - 'admin.packingTemplates.title': 'Csomagolási sablonok', - 'admin.packingTemplates.subtitle': 'Újrafelhasználható csomagolási listák létrehozása utazásaidhoz', - 'admin.packingTemplates.create': 'Új sablon', - 'admin.packingTemplates.namePlaceholder': 'Sablon neve (pl. Tengerparti nyaralás)', - 'admin.packingTemplates.empty': 'Még nincsenek sablonok', - 'admin.packingTemplates.items': 'tétel', - 'admin.packingTemplates.categories': 'kategória', - 'admin.packingTemplates.itemName': 'Tétel neve', - 'admin.packingTemplates.itemCategory': 'Kategória', - 'admin.packingTemplates.categoryName': 'Kategória neve (pl. Ruházat)', - 'admin.packingTemplates.addCategory': 'Kategória hozzáadása', - 'admin.packingTemplates.created': 'Sablon létrehozva', - 'admin.packingTemplates.deleted': 'Sablon törölve', - 'admin.packingTemplates.loadError': 'Nem sikerült betölteni a sablonokat', - 'admin.packingTemplates.createError': 'Nem sikerült létrehozni a sablont', - 'admin.packingTemplates.deleteError': 'Nem sikerült törölni a sablont', - 'admin.packingTemplates.saveError': 'Nem sikerült menteni', - - // Bővítmények - 'admin.tabs.addons': 'Bővítmények', - 'admin.addons.title': 'Bővítmények', - 'admin.addons.subtitle': 'Funkciók engedélyezése vagy letiltása a TREK testreszabásához.', - 'admin.addons.catalog.packing.name': 'Listák', - 'admin.addons.catalog.packing.description': 'Csomagolási listák és teendők az utazásaidhoz', - 'admin.addons.catalog.budget.name': 'Költségvetés', - 'admin.addons.catalog.budget.description': 'Kiadások nyomon követése és az utazási költségvetés tervezése', - 'admin.addons.catalog.documents.name': 'Dokumentumok', - 'admin.addons.catalog.documents.description': 'Úti dokumentumok tárolása és kezelése', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Személyes szabadságtervező naptárnézettel', - 'admin.addons.catalog.atlas.name': 'Atlasz', - 'admin.addons.catalog.atlas.description': 'Világtérkép meglátogatott országokkal és utazási statisztikákkal', - 'admin.addons.catalog.collab.name': 'Együttműködés', - 'admin.addons.catalog.collab.description': 'Valós idejű jegyzetek, szavazások és csevegés az utazás tervezéséhez', - 'admin.addons.catalog.memories.name': 'Fotók (Immich)', - 'admin.addons.catalog.memories.description': 'Utazási fotók megosztása az Immich példányon keresztül', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol AI asszisztens integrációhoz', - 'admin.addons.subtitleBefore': 'Funkciók engedélyezése vagy letiltása a ', - 'admin.addons.subtitleAfter': ' testreszabásához.', - 'admin.addons.enabled': 'Engedélyezve', - 'admin.addons.disabled': 'Letiltva', - 'admin.addons.type.trip': 'Utazás', - 'admin.addons.type.global': 'Globális', - 'admin.addons.type.integration': 'Integráció', - 'admin.addons.tripHint': 'Fülként érhető el minden utazáson belül', - 'admin.addons.globalHint': 'Önálló szekcióként elérhető a fő navigációban', - 'admin.addons.integrationHint': 'Háttérszolgáltatások és API integrációk dedikált oldal nélkül', - 'admin.addons.toast.updated': 'Bővítmény frissítve', - 'admin.addons.toast.error': 'Nem sikerült frissíteni a bővítményt', - 'admin.addons.noAddons': 'Nincsenek elérhető bővítmények', - // Időjárás információ - 'admin.weather.title': 'Időjárás adatok', - 'admin.weather.badge': '2026. március 24. óta', - 'admin.weather.description': 'A TREK az Open-Meteo-t használja időjárás-adatforrásként. Az Open-Meteo egy ingyenes, nyílt forráskódú időjárás-szolgáltatás — nincs szükség API kulcsra.', - 'admin.weather.forecast': '16 napos előrejelzés', - 'admin.weather.forecastDesc': 'Korábban 5 nap volt (OpenWeatherMap)', - 'admin.weather.climate': 'Történelmi klímaadatok', - 'admin.weather.climateDesc': 'Az elmúlt 85 év átlagai a 16 napos előrejelzésen túli napokhoz', - 'admin.weather.requests': '10 000 kérés / nap', - 'admin.weather.requestsDesc': 'Ingyenes, nincs szükség API kulcsra', - 'admin.weather.locationHint': 'Az időjárás az adott nap első koordinátákkal rendelkező helye alapján készül. Ha nincs hely hozzárendelve a naphoz, a helylista bármelyik helye szolgál referenciául.', - - 'admin.tabs.audit': 'Audit', - - 'admin.audit.subtitle': 'Biztonsági és adminisztrációs események (mentések, felhasználók, 2FA, beállítások).', - 'admin.audit.empty': 'Még nincsenek audit bejegyzések.', - 'admin.audit.refresh': 'Frissítés', - 'admin.audit.loadMore': 'Továbbiak betöltése', - 'admin.audit.showing': '{count} betöltve · {total} összesen', - 'admin.audit.col.time': 'Időpont', - 'admin.audit.col.user': 'Felhasználó', - 'admin.audit.col.action': 'Művelet', - 'admin.audit.col.resource': 'Erőforrás', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Részletek', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP hozzáférés', - 'admin.mcpTokens.title': 'MCP hozzáférés', - 'admin.mcpTokens.subtitle': 'OAuth munkamenetek és API tokenek kezelése az összes felhasználó számára', - 'admin.mcpTokens.sectionTitle': 'API tokenek', - 'admin.mcpTokens.owner': 'Tulajdonos', - 'admin.mcpTokens.tokenName': 'Token neve', - 'admin.mcpTokens.created': 'Létrehozva', - 'admin.mcpTokens.lastUsed': 'Utoljára használva', - 'admin.mcpTokens.never': 'Soha', - 'admin.mcpTokens.empty': 'Még nem hoztak létre MCP tokeneket', - 'admin.mcpTokens.deleteTitle': 'Token törlése', - 'admin.mcpTokens.deleteMessage': 'Ez a token azonnal érvénytelenítésre kerül. A felhasználó elveszíti az MCP hozzáférést ezen a tokenen keresztül.', - 'admin.mcpTokens.deleteSuccess': 'Token törölve', - 'admin.mcpTokens.deleteError': 'Nem sikerült törölni a tokent', - 'admin.mcpTokens.loadError': 'Nem sikerült betölteni a tokeneket', - 'admin.oauthSessions.sectionTitle': 'OAuth munkamenetek', - 'admin.oauthSessions.clientName': 'Kliens', - 'admin.oauthSessions.owner': 'Tulajdonos', - 'admin.oauthSessions.scopes': 'Jogosultságok', - 'admin.oauthSessions.created': 'Létrehozva', - 'admin.oauthSessions.empty': 'Nincsenek aktív OAuth munkamenetek', - 'admin.oauthSessions.revokeTitle': 'Munkamenet visszavonása', - 'admin.oauthSessions.revokeMessage': 'Ez az OAuth munkamenet azonnal visszavonásra kerül. A kliens elveszíti az MCP hozzáférést.', - 'admin.oauthSessions.revokeSuccess': 'Munkamenet visszavonva', - 'admin.oauthSessions.revokeError': 'Nem sikerült visszavonni a munkamenetet', - 'admin.oauthSessions.loadError': 'Nem sikerült betölteni az OAuth munkameneteket', - - // GitHub - 'admin.tabs.github': 'GitHub', - 'admin.github.title': 'Frissítési előzmények', - 'admin.github.subtitle': 'Legújabb frissítések: {repo}', - 'admin.github.latest': 'Legújabb', - 'admin.github.prerelease': 'Előzetes kiadás', - 'admin.github.showDetails': 'Részletek megjelenítése', - 'admin.github.hideDetails': 'Részletek elrejtése', - 'admin.github.loadMore': 'Továbbiak betöltése', - 'admin.github.loading': 'Betöltés...', - 'admin.github.error': 'Nem sikerült betölteni a kiadásokat', - 'admin.github.by': 'készítette', - 'admin.github.support': 'Segít fenntartani a TREK fejlesztését', - - 'admin.update.available': 'Frissítés elérhető', - 'admin.update.text': 'A TREK {version} elérhető. Jelenleg a {current} verziót használod.', - 'admin.update.button': 'Megtekintés a GitHubon', - 'admin.update.install': 'Frissítés telepítése', - 'admin.update.confirmTitle': 'Frissítés telepítése?', - 'admin.update.confirmText': 'A TREK frissítésre kerül {current} verzióról {version} verzióra. A szerver ezután automatikusan újraindul.', - 'admin.update.dataInfo': 'Minden adat (utazások, felhasználók, API kulcsok, feltöltések, Vacay, Atlas, költségvetések) megmarad.', - 'admin.update.warning': 'Az alkalmazás az újraindítás alatt rövid ideig nem lesz elérhető.', - 'admin.update.confirm': 'Frissítés most', - 'admin.update.installing': 'Frissítés…', - 'admin.update.success': 'Frissítés telepítve! A szerver újraindul…', - 'admin.update.failed': 'Frissítés sikertelen', - 'admin.update.backupHint': 'Javasoljuk, hogy frissítés előtt készíts biztonsági mentést.', - 'admin.update.backupLink': 'Biztonsági mentéshez', - 'admin.update.howTo': 'Frissítési útmutató', - 'admin.update.dockerText': 'A TREK példányod Dockerben fut. A {version} verzióra frissítéshez futtasd a következő parancsokat a szervereden:', - 'admin.update.reloadHint': 'Kérjük, töltsd újra az oldalt néhány másodperc múlva.', - - // Vacay bővítmény - 'vacay.subtitle': 'Szabadságnapok tervezése és kezelése', - 'vacay.settings': 'Beállítások', - 'vacay.year': 'Év', - 'vacay.addYear': 'Következő év hozzáadása', - 'vacay.addPrevYear': 'Előző év hozzáadása', - 'vacay.removeYear': 'Év eltávolítása', - 'vacay.removeYearConfirm': '{year} eltávolítása?', - 'vacay.removeYearHint': 'Az adott év összes szabadság-bejegyzése és céges szabadnapja véglegesen törlődik.', - 'vacay.remove': 'Eltávolítás', - 'vacay.persons': 'Személyek', - 'vacay.noPersons': 'Nincsenek személyek hozzáadva', - 'vacay.addPerson': 'Személy hozzáadása', - 'vacay.editPerson': 'Személy szerkesztése', - 'vacay.removePerson': 'Személy eltávolítása', - 'vacay.removePersonConfirm': '{name} eltávolítása?', - 'vacay.removePersonHint': 'A személy összes szabadság-bejegyzése véglegesen törlődik.', - 'vacay.personName': 'Név', - 'vacay.personNamePlaceholder': 'Név megadása', - 'vacay.color': 'Szín', - 'vacay.add': 'Hozzáadás', - 'vacay.legend': 'Jelmagyarázat', - 'vacay.publicHoliday': 'Ünnepnap', - 'vacay.companyHoliday': 'Céges szabadnap', - 'vacay.weekend': 'Hétvége', - 'vacay.modeVacation': 'Szabadság', - 'vacay.modeCompany': 'Céges szabadnap', - 'vacay.entitlement': 'Szabadságkeret', - 'vacay.entitlementDays': 'nap', - 'vacay.used': 'Felhasznált', - 'vacay.remaining': 'Maradt', - 'vacay.carriedOver': '{year}-ból/ből', - 'vacay.blockWeekends': 'Hétvégék zárolása', - 'vacay.blockWeekendsHint': 'Szabadság-bejegyzések megakadályozása szombaton és vasárnap', - 'vacay.publicHolidays': 'Ünnepnapok', - 'vacay.publicHolidaysHint': 'Ünnepnapok megjelölése a naptárban', - 'vacay.selectCountry': 'Ország kiválasztása', - 'vacay.selectRegion': 'Régió kiválasztása (opcionális)', - 'vacay.addCalendar': 'Naptár hozzáadása', - 'vacay.calendarLabel': 'Címke (opcionális)', - 'vacay.calendarColor': 'Szín', - 'vacay.noCalendars': 'Még nincsenek ünnepnap-naptárak hozzáadva', - 'vacay.weekendDays': 'Hétvégi napok', - 'vacay.mon': 'Hé', - 'vacay.tue': 'Ke', - 'vacay.wed': 'Sze', - 'vacay.thu': 'Csü', - 'vacay.fri': 'Pé', - 'vacay.sat': 'Szo', - 'vacay.sun': 'Va', - 'vacay.companyHolidays': 'Céges szabadnapok', - 'vacay.companyHolidaysHint': 'Céges szintű szabadnapok megjelölésének engedélyezése', - 'vacay.companyHolidaysNoDeduct': 'A céges szabadnapok nem számítanak bele a szabadságkeretbe.', - 'vacay.weekStart': 'A hét kezdőnapja', - 'vacay.weekStartHint': 'Válaszd ki, hogy a hét hétfőn vagy vasárnap kezdődjön', - 'vacay.carryOver': 'Szabadság átvitele', - 'vacay.carryOverHint': 'Megmaradt szabadságnapok automatikus átvitele a következő évre', - 'vacay.sharing': 'Megosztás', - 'vacay.sharingHint': 'Szabadságterved megosztása más TREK felhasználókkal', - 'vacay.owner': 'Tulajdonos', - 'vacay.shareEmailPlaceholder': 'TREK felhasználó e-mail címe', - 'vacay.shareSuccess': 'Terv sikeresen megosztva', - 'vacay.shareError': 'Nem sikerült megosztani a tervet', - 'vacay.dissolve': 'Összevonás feloldása', - 'vacay.dissolveHint': 'Naptárak újbóli szétválasztása. A bejegyzéseid megmaradnak.', - 'vacay.dissolveAction': 'Feloldás', - 'vacay.dissolved': 'Naptár szétválasztva', - 'vacay.fusedWith': 'Összevonva:', - 'vacay.you': 'te', - 'vacay.noData': 'Nincs adat', - 'vacay.changeColor': 'Szín módosítása', - 'vacay.inviteUser': 'Felhasználó meghívása', - 'vacay.inviteHint': 'Hívj meg egy másik TREK felhasználót közös szabadságnaptár megosztásához.', - 'vacay.selectUser': 'Felhasználó kiválasztása', - 'vacay.sendInvite': 'Meghívó küldése', - 'vacay.inviteSent': 'Meghívó elküldve', - 'vacay.inviteError': 'Nem sikerült elküldeni a meghívót', - 'vacay.pending': 'függőben', - 'vacay.noUsersAvailable': 'Nincsenek elérhető felhasználók', - 'vacay.accept': 'Elfogadás', - 'vacay.decline': 'Elutasítás', - 'vacay.acceptFusion': 'Elfogadás és összevonás', - 'vacay.inviteTitle': 'Összevonási kérelem', - 'vacay.inviteWantsToFuse': 'szeretne megosztani veled egy szabadságnaptárat.', - 'vacay.fuseInfo1': 'Mindketten látjátok az összes szabadság-bejegyzést egy közös naptárban.', - 'vacay.fuseInfo2': 'Mindkét fél létrehozhat és szerkeszthet bejegyzéseket a másik számára.', - 'vacay.fuseInfo3': 'Mindkét fél törölhet bejegyzéseket és módosíthatja a szabadságkeretet.', - 'vacay.fuseInfo4': 'A beállítások, mint ünnepnapok és céges szabadnapok, közösen érvényesek.', - 'vacay.fuseInfo5': 'Az összevonás bármikor feloldható bármelyik fél által. A bejegyzések megmaradnak.', - 'nav.myTrips': 'Utazásaim', - - // Atlas bővítmény - 'atlas.subtitle': 'Utazási lábnyomod a világban', - 'atlas.countries': 'Országok', - 'atlas.trips': 'Utazások', - 'atlas.places': 'Helyek', - 'atlas.unmark': 'Eltávolítás', - 'atlas.confirmMark': 'Megjelölöd ezt az országot meglátogatottként?', - 'atlas.confirmUnmark': 'Eltávolítod ezt az országot a meglátogatottak listájáról?', - 'atlas.confirmUnmarkRegion': 'Eltávolítod ezt a régiót a meglátogatottak listájáról?', - 'atlas.markVisited': 'Megjelölés meglátogatottként', - 'atlas.markVisitedHint': 'Ország hozzáadása a meglátogatottak listájához', - 'atlas.markRegionVisitedHint': 'Régió hozzáadása a meglátogatottak listájához', - 'atlas.addToBucket': 'Hozzáadás a bakancslistához', - 'atlas.addPoi': 'Hely hozzáadása', - 'atlas.searchCountry': 'Ország keresése...', - 'atlas.bucketNamePlaceholder': 'Név (ország, város, hely...)', - 'atlas.month': 'Hónap', - 'atlas.addToBucketHint': 'Mentés meglátogatni kívánt helyként', - 'atlas.bucketWhen': 'Mikor tervezed meglátogatni?', - 'atlas.statsTab': 'Statisztikák', - 'atlas.bucketTab': 'Bakancslista', - 'atlas.addBucket': 'Hozzáadás a bakancslistához', - 'atlas.bucketNotesPlaceholder': 'Jegyzetek (opcionális)', - 'atlas.bucketEmpty': 'A bakancslistád üres', - 'atlas.bucketEmptyHint': 'Adj hozzá helyeket, ahová álmodsz eljutni', - 'atlas.days': 'Napok', - 'atlas.visitedCountries': 'Meglátogatott országok', - 'atlas.cities': 'Városok', - 'atlas.noData': 'Még nincsenek utazási adatok', - 'atlas.noDataHint': 'Hozz létre egy utazást és adj hozzá helyeket a világtérképhez', - 'atlas.lastTrip': 'Utolsó utazás', - 'atlas.nextTrip': 'Következő utazás', - 'atlas.daysLeft': 'nap van hátra', - 'atlas.streak': 'Sorozat', - 'atlas.year': 'év', - 'atlas.years': 'év', - 'atlas.yearInRow': 'egymást követő év', - 'atlas.yearsInRow': 'egymást követő év', - 'atlas.tripIn': 'utazás', - 'atlas.tripsIn': 'utazás', - 'atlas.since': 'óta', - 'atlas.europe': 'Európa', - 'atlas.asia': 'Ázsia', - 'atlas.northAmerica': 'É-Amerika', - 'atlas.southAmerica': 'D-Amerika', - 'atlas.africa': 'Afrika', - 'atlas.oceania': 'Óceánia', - 'atlas.other': 'Egyéb', - 'atlas.firstVisit': 'Első utazás', - 'atlas.lastVisitLabel': 'Utolsó utazás', - 'atlas.tripSingular': 'Utazás', - 'atlas.tripPlural': 'Utazások', - 'atlas.placeVisited': 'Meglátogatott hely', - 'atlas.placesVisited': 'Meglátogatott helyek', - - // Utazástervező - 'trip.tabs.plan': 'Terv', - 'trip.tabs.transports': 'Közlekedés', - 'trip.tabs.reservations': 'Foglalások', - 'trip.tabs.reservationsShort': 'Foglalás', - 'trip.tabs.packing': 'Csomagolási lista', - 'trip.tabs.packingShort': 'Csomag', - 'trip.tabs.lists': 'Listák', - 'trip.tabs.listsShort': 'Listák', - 'trip.tabs.budget': 'Költségvetés', - 'trip.tabs.files': 'Fájlok', - 'trip.loading': 'Utazás betöltése...', - 'trip.mobilePlan': 'Tervezés', - 'trip.mobilePlaces': 'Helyek', - 'trip.toast.placeUpdated': 'Hely frissítve', - 'trip.toast.placeAdded': 'Hely hozzáadva', - 'trip.toast.placeDeleted': 'Hely törölve', - 'trip.toast.selectDay': 'Kérjük, először válassz egy napot', - 'trip.toast.assignedToDay': 'Hely hozzárendelve a naphoz', - 'trip.toast.reorderError': 'Nem sikerült átrendezni', - 'trip.toast.reservationUpdated': 'Foglalás frissítve', - 'trip.toast.reservationAdded': 'Foglalás hozzáadva', - 'trip.toast.deleted': 'Törölve', - 'trip.confirm.deletePlace': 'Biztosan törölni szeretnéd ezt a helyet?', - 'trip.confirm.deletePlaces': '{count} helyet töröl?', - 'trip.toast.placesDeleted': '{count} hely törölve', - 'trip.loadingPhotos': 'Helyek fotóinak betöltése...', - - // Napi terv oldalsáv - 'dayplan.emptyDay': 'Nincs tervezett hely erre a napra', - 'dayplan.addNote': 'Jegyzet hozzáadása', - 'dayplan.editNote': 'Jegyzet szerkesztése', - 'dayplan.noteAdd': 'Jegyzet hozzáadása', - 'dayplan.noteEdit': 'Jegyzet szerkesztése', - 'dayplan.noteTitle': 'Jegyzet', - 'dayplan.noteSubtitle': 'Napi jegyzet', - 'dayplan.totalCost': 'Összköltség', - 'dayplan.days': 'nap', - 'dayplan.dayN': '{n}. nap', - 'dayplan.calculating': 'Számítás...', - 'dayplan.route': 'Útvonal', - 'dayplan.optimize': 'Optimalizálás', - 'dayplan.optimized': 'Útvonal optimalizálva', - 'dayplan.routeError': 'Nem sikerült kiszámítani az útvonalat', - 'dayplan.toast.needTwoPlaces': 'Legalább két hely szükséges az útvonal-optimalizáláshoz', - 'dayplan.toast.routeOptimized': 'Útvonal optimalizálva', - 'dayplan.toast.noGeoPlaces': 'Nem találhatók koordinátákkal rendelkező helyek az útvonalszámításhoz', - 'dayplan.confirmed': 'Megerősítve', - 'dayplan.pendingRes': 'Függőben', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Napi terv exportálása PDF-be', - 'dayplan.pdfError': 'Nem sikerült a PDF exportálás', - 'dayplan.cannotReorderTransport': 'A rögzített időpontú foglalások nem rendezhetők át', - 'dayplan.confirmRemoveTimeTitle': 'Időpont eltávolítása?', - 'dayplan.confirmRemoveTimeBody': 'Ennek a helynek rögzített időpontja van ({time}). Az áthelyezéssel az időpont eltávolítódik és szabad rendezés válik lehetővé.', - 'dayplan.confirmRemoveTimeAction': 'Időpont eltávolítása és áthelyezés', - 'dayplan.cannotDropOnTimed': 'Elemek nem helyezhetők rögzített időpontú bejegyzések közé', - 'dayplan.cannotBreakChronology': 'Ez megbontaná az időzített elemek és foglalások időrendi sorrendjét', - - // Helyek oldalsáv - 'places.addPlace': 'Hely/Tevékenység hozzáadása', - 'places.importFile': 'Fájl importálása', - 'places.sidebarDrop': 'Ejtse el az importáláshoz', - 'places.importFileHint': '.gpx, .kml vagy .kmz fájlok importálása olyan eszközökből, mint a Google My Maps, Google Earth vagy egy GPS tracker.', - 'places.importFileDropHere': 'Kattintson egy fájl kiválasztásához, vagy húzza ide', - 'places.importFileDropActive': 'Ejtse ide a fájlt a kiválasztáshoz', - 'places.importFileUnsupported': 'Nem támogatott fájltípus. Használjon .gpx, .kml vagy .kmz fájlt.', - 'places.importFileTooLarge': 'A fájl túl nagy. A maximális feltöltési méret {maxMb} MB.', - 'places.importFileError': 'Importálás sikertelen', - 'places.importAllSkipped': 'Minden hely már szerepel az utazásban.', - 'places.gpxImported': '{count} hely importálva GPX-ből', - 'places.gpxImportTypes': 'Mit szeretnél importálni?', - 'places.gpxImportWaypoints': 'Útpontok', - 'places.gpxImportRoutes': 'Útvonalak', - 'places.gpxImportTracks': 'Nyomvonalak (útvonalgeometriával)', - 'places.gpxImportNoneSelected': 'Válassz legalább egy típust az importáláshoz.', - 'places.kmlImportTypes': 'Mit szeretnél importálni?', - 'places.kmlImportPoints': 'Pontok (Placemarks)', - 'places.kmlImportPaths': 'Útvonalak (LineStrings)', - 'places.kmlImportNoneSelected': 'Válassz legalább egy típust.', - 'places.selectionCount': '{count} kiválasztva', - 'places.deleteSelected': 'Kijelöltek törlése', - 'places.kmlKmzImported': '{count} hely importálva KMZ/KML-ből', - 'places.urlResolved': 'Hely importálva URL-ből', - 'places.importList': 'Lista importálás', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importálva: {created} • Kihagyva: {skipped}', - 'places.importGoogleList': 'Google Lista', - 'places.importNaverList': 'Naver Lista', - 'places.googleListHint': 'Illessz be egy megosztott Google Maps lista linket az osszes hely importalasahoz.', - 'places.googleListImported': '{count} hely importalva a(z) "{list}" listabol', - 'places.googleListError': 'Google Maps lista importalasa sikertelen', - 'places.naverListHint': 'Illessz be egy megosztott Naver Maps lista linket az összes hely importálásához.', - 'places.naverListImported': '{count} hely importálva a(z) "{list}" listából', - 'places.naverListError': 'Naver Maps lista importálása sikertelen', - 'places.viewDetails': 'Részletek megtekintése', - 'places.assignToDay': 'Melyik naphoz adod?', - 'places.all': 'Összes', - 'places.unplanned': 'Nem tervezett', - 'places.filterTracks': 'Nyomvonalak', - 'places.search': 'Helyek keresése...', - 'places.allCategories': 'Összes kategória', - 'places.categoriesSelected': 'kategória', - 'places.clearFilter': 'Szűrő törlése', - 'places.count': '{count} hely', - 'places.countSingular': '1 hely', - 'places.allPlanned': 'Minden hely be van tervezve', - 'places.noneFound': 'Nem találhatók helyek', - 'places.editPlace': 'Hely szerkesztése', - 'places.formName': 'Név', - 'places.formNamePlaceholder': 'pl. Eiffel-torony', - 'places.formDescription': 'Leírás', - 'places.formDescriptionPlaceholder': 'Rövid leírás...', - 'places.formAddress': 'Cím', - 'places.formAddressPlaceholder': 'Utca, Város, Ország', - 'places.formLat': 'Szélességi fok (pl. 48.8566)', - 'places.formLng': 'Hosszúsági fok (pl. 2.3522)', - 'places.formCategory': 'Kategória', - 'places.noCategory': 'Nincs kategória', - 'places.categoryNamePlaceholder': 'Kategória neve', - 'places.formTime': 'Időpont', - 'places.startTime': 'Kezdés', - 'places.endTime': 'Befejezés', - 'places.endTimeBeforeStart': 'A befejezési idő a kezdési idő előtt van', - 'places.timeCollision': 'Időbeli átfedés:', - 'places.formWebsite': 'Weboldal', - 'places.formNotes': 'Jegyzetek', - 'places.formNotesPlaceholder': 'Személyes jegyzetek...', - 'places.formReservation': 'Foglalás', - 'places.reservationNotesPlaceholder': 'Foglalási jegyzetek, visszaigazolási szám...', - 'places.mapsSearchPlaceholder': 'Helyek keresése...', - 'places.mapsSearchError': 'Helykeresés sikertelen.', - 'places.loadingDetails': 'Hely adatainak betöltése…', - 'places.osmHint': 'OpenStreetMap keresés aktív (képek, nyitvatartás és értékelések nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', - 'places.osmActive': 'Keresés OpenStreetMap-en keresztül (képek, értékelések és nyitvatartás nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', - 'places.categoryCreateError': 'Nem sikerült létrehozni a kategóriát', - 'places.nameRequired': 'Kérjük, adj meg egy nevet', - 'places.saveError': 'Nem sikerült menteni', - // Hely részletek - 'inspector.opened': 'Nyitva', - 'inspector.closed': 'Zárva', - 'inspector.openingHours': 'Nyitvatartás', - 'inspector.showHours': 'Nyitvatartás megjelenítése', - 'inspector.files': 'Fájlok', - 'inspector.filesCount': '{count} fájl', - 'inspector.removeFromDay': 'Eltávolítás a napról', - 'inspector.remove': 'Eltávolítás', - 'inspector.addToDay': 'Hozzáadás a naphoz', - 'inspector.confirmedRes': 'Megerősített foglalás', - 'inspector.pendingRes': 'Függőben lévő foglalás', - 'inspector.google': 'Megnyitás a Google Térképben', - 'inspector.website': 'Weboldal megnyitása', - 'inspector.addRes': 'Foglalás', - 'inspector.editRes': 'Foglalás szerkesztése', - 'inspector.participants': 'Résztvevők', - 'inspector.trackStats': 'Útvonal adatok', - - // Foglalások - 'reservations.title': 'Foglalások', - 'reservations.empty': 'Még nincsenek foglalások', - 'reservations.emptyHint': 'Adj hozzá foglalásokat repülőkhöz, szállodákhoz és egyebekhez', - 'reservations.add': 'Foglalás hozzáadása', - 'reservations.addManual': 'Kézi foglalás', - 'reservations.placeHint': 'Tipp: A foglalásokat legjobb közvetlenül egy helyről létrehozni, hogy összekapcsolódjon a napi tervvel.', - 'reservations.confirmed': 'Megerősítve', - 'reservations.pending': 'Függőben', - 'reservations.summary': '{confirmed} megerősítve, {pending} függőben', - 'reservations.fromPlan': 'Tervből', - 'reservations.showFiles': 'Fájlok megjelenítése', - 'reservations.editTitle': 'Foglalás szerkesztése', - 'reservations.status': 'Állapot', - 'reservations.datetime': 'Dátum és idő', - 'reservations.startTime': 'Kezdési idő', - 'reservations.endTime': 'Befejezési idő', - 'reservations.date': 'Dátum', - 'reservations.time': 'Időpont', - 'reservations.timeAlt': 'Időpont (alternatív, pl. 19:30)', - 'reservations.linkExisting': 'Meglévő fájl csatolása', - 'reservations.notes': 'Jegyzetek', - 'reservations.notesPlaceholder': 'További jegyzetek...', - 'reservations.meta.airline': 'Légitársaság', - 'reservations.meta.flightNumber': 'Járatszám', - 'reservations.meta.from': 'Honnan', - 'reservations.meta.to': 'Hová', - 'reservations.needsReview': 'Ellenőrzés', - 'reservations.needsReviewHint': 'A repülőteret nem sikerült automatikusan azonosítani — erősítsd meg a helyet.', - 'reservations.searchLocation': 'Állomás, kikötő, cím keresése...', - 'airport.searchPlaceholder': 'Repülőtér kódja vagy város (pl. FRA)', - 'map.connections': 'Kapcsolatok', - 'map.showConnections': 'Foglalási útvonalak megjelenítése', - 'map.hideConnections': 'Foglalási útvonalak elrejtése', - 'settings.bookingLabels': 'Útvonal-címkék a foglalásokhoz', - 'settings.bookingLabelsHint': 'Állomás- / repülőtér-nevek megjelenítése a térképen. Ha ki van kapcsolva, csak az ikon látszik.', - 'reservations.meta.trainNumber': 'Vonatszám', - 'reservations.meta.platform': 'Vágány', - 'reservations.meta.seat': 'Ülés', - 'reservations.meta.checkIn': 'Bejelentkezés', - 'reservations.meta.checkInUntil': 'Bejelentkezés eddig', - 'reservations.meta.checkOut': 'Kijelentkezés', - 'reservations.meta.linkAccommodation': 'Szállás', - 'reservations.meta.pickAccommodation': 'Szállás hozzárendelése', - 'reservations.meta.noAccommodation': 'Nincs', - 'reservations.meta.hotelPlace': 'Szálloda', - 'reservations.meta.pickHotel': 'Szálloda kiválasztása', - 'reservations.meta.fromDay': 'Ettől', - 'reservations.meta.toDay': 'Eddig', - 'reservations.meta.selectDay': 'Nap kiválasztása', - 'reservations.type.flight': 'Repülő', - 'reservations.type.hotel': 'Szálloda', - 'reservations.type.restaurant': 'Étterem', - 'reservations.type.train': 'Vonat', - 'reservations.type.car': 'Autó', - 'reservations.type.cruise': 'Hajóút', - 'reservations.type.event': 'Esemény', - 'reservations.type.tour': 'Túra', - 'reservations.type.other': 'Egyéb', - 'reservations.confirm.delete': 'Biztosan törölni szeretnéd a(z) "{name}" foglalást?', - 'reservations.confirm.deleteTitle': 'Foglalás törlése?', - 'reservations.confirm.deleteBody': '"{name}" véglegesen törlődik.', - 'reservations.toast.updated': 'Foglalás frissítve', - 'reservations.toast.removed': 'Foglalás törölve', - 'reservations.toast.fileUploaded': 'Fájl feltöltve', - 'reservations.toast.uploadError': 'Feltöltés sikertelen', - 'reservations.newTitle': 'Új foglalás', - 'reservations.bookingType': 'Foglalás típusa', - 'reservations.titleLabel': 'Cím', - 'reservations.titlePlaceholder': 'pl. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Helyszín / Cím', - 'reservations.locationPlaceholder': 'Cím, Repülőtér, Szálloda...', - 'reservations.confirmationCode': 'Foglalási kód', - 'reservations.confirmationPlaceholder': 'pl. ABC12345', - 'reservations.day': 'Nap', - 'reservations.noDay': 'Nincs nap', - 'reservations.place': 'Hely', - 'reservations.noPlace': 'Nincs hely', - 'reservations.pendingSave': 'mentés…', - 'reservations.uploading': 'Feltöltés...', - 'reservations.attachFile': 'Fájl csatolása', - 'reservations.toast.saveError': 'Nem sikerült menteni', - 'reservations.toast.updateError': 'Nem sikerült frissíteni', - 'reservations.toast.deleteError': 'Nem sikerült törölni', - 'reservations.confirm.remove': '"{name}" foglalás eltávolítása?', - 'reservations.linkAssignment': 'Összekapcsolás napi tervvel', - 'reservations.pickAssignment': 'Válassz hozzárendelést a tervedből...', - 'reservations.noAssignment': 'Nincs összekapcsolás (önálló)', - 'reservations.price': 'Ár', - 'reservations.budgetCategory': 'Költségvetési kategória', - 'reservations.budgetCategoryPlaceholder': 'pl. Közlekedés, Szállás', - 'reservations.budgetCategoryAuto': 'Automatikus (foglalás típusa alapján)', - 'reservations.budgetHint': 'Mentéskor automatikusan létrejön egy költségvetési tétel.', - 'reservations.departureDate': 'Indulás', - 'reservations.arrivalDate': 'Érkezés', - 'reservations.departureTime': 'Indulási idő', - 'reservations.arrivalTime': 'Érkezési idő', - 'reservations.pickupDate': 'Felvétel', - 'reservations.returnDate': 'Visszaadás', - 'reservations.pickupTime': 'Felvétel ideje', - 'reservations.returnTime': 'Visszaadás ideje', - 'reservations.endDate': 'Befejezés dátuma', - 'reservations.meta.departureTimezone': 'TZ indulás', - 'reservations.meta.arrivalTimezone': 'TZ érkezés', - 'reservations.span.departure': 'Indulás', - 'reservations.span.arrival': 'Érkezés', - 'reservations.span.inTransit': 'Úton', - 'reservations.span.pickup': 'Felvétel', - 'reservations.span.return': 'Visszaadás', - 'reservations.span.active': 'Aktív', - 'reservations.span.start': 'Kezdés', - 'reservations.span.end': 'Vége', - 'reservations.span.ongoing': 'Folyamatban', - 'reservations.validation.endBeforeStart': 'A befejezés dátuma/időpontja a kezdés utáni kell legyen', - 'reservations.addBooking': 'Foglalás hozzáadása', - - // Költségvetés - 'budget.title': 'Költségvetés', - 'budget.exportCsv': 'CSV exportálás', - 'budget.emptyTitle': 'Még nincs költségvetés létrehozva', - 'budget.emptyText': 'Hozz létre kategóriákat és bejegyzéseket az utazási költségvetés tervezéséhez', - 'budget.emptyPlaceholder': 'Kategória neve...', - 'budget.createCategory': 'Kategória létrehozása', - 'budget.category': 'Kategória', - 'budget.categoryName': 'Kategória neve', - 'budget.table.name': 'Név', - 'budget.table.total': 'Összesen', - 'budget.table.persons': 'Személyek', - 'budget.table.days': 'nap', - 'budget.table.perPerson': 'Személyenként', - 'budget.table.perDay': 'Naponta', - 'budget.table.perPersonDay': 'Fő / Nap', - 'budget.table.note': 'Megjegyzés', - 'budget.table.date': 'Dátum', - 'budget.newEntry': 'Új bejegyzés', - 'budget.defaultEntry': 'Új bejegyzés', - 'budget.defaultCategory': 'Új kategória', - 'budget.total': 'Összesen', - 'budget.totalBudget': 'Teljes költségvetés', - 'budget.byCategory': 'Kategóriánként', - 'budget.editTooltip': 'Kattints a szerkesztéshez', - 'budget.linkedToReservation': 'Foglaláshoz kapcsolva — ott szerkessze a nevet', - 'budget.confirm.deleteCategory': 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} bejegyzéssel?', - 'budget.deleteCategory': 'Kategória törlése', - 'budget.perPerson': 'Személyenként', - 'budget.paid': 'Fizetve', - 'budget.open': 'Nyitott', - 'budget.noMembers': 'Nincsenek résztvevők hozzárendelve', - 'budget.settlement': 'Elszámolás', - 'budget.settlementInfo': 'Kattints egy tag avatárjára egy költségvetési tételen a zöld jelöléshez — ez azt jelenti, hogy fizetett. Az elszámolás ezután mutatja, ki kinek mennyivel tartozik.', - 'budget.netBalances': 'Nettó egyenlegek', - - // Fájlok - 'files.title': 'Fájlok', - 'files.pageTitle': 'Fájlok és dokumentumok', - 'files.subtitle': '{count} fájl a következőhöz: {trip}', - 'files.download': 'Letöltés', - 'files.openError': 'A fájl megnyitása sikertelen', - 'files.downloadPdf': 'PDF letöltése', - 'files.count': '{count} fájl', - 'files.countSingular': '1 fájl', - 'files.uploaded': '{count} feltöltve', - 'files.uploadError': 'Feltöltés sikertelen', - 'files.dropzone': 'Húzd ide a fájlokat', - 'files.dropzoneHint': 'vagy kattints a böngészéshez', - 'files.allowedTypes': 'Képek, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Feltöltés...', - 'files.filterAll': 'Összes', - 'files.filterPdf': 'PDF-ek', - 'files.filterImages': 'Képek', - 'files.filterDocs': 'Dokumentumok', - 'files.filterCollab': 'Közös jegyzetek', - 'files.sourceCollab': 'Közös jegyzetekből', - 'files.empty': 'Még nincsenek fájlok', - 'files.emptyHint': 'Tölts fel fájlokat az utazásodhoz', - 'files.openTab': 'Megnyitás új lapon', - 'files.confirm.delete': 'Biztosan törölni szeretnéd ezt a fájlt?', - 'files.toast.deleted': 'Fájl törölve', - 'files.toast.deleteError': 'Nem sikerült törölni a fájlt', - 'files.sourcePlan': 'Napi terv', - 'files.sourceBooking': 'Foglalás', - 'files.sourceTransport': 'Közlekedés', - 'files.attach': 'Csatolás', - 'files.pasteHint': 'Képeket a vágólapról is beillesztheted (Ctrl+V)', - 'files.trash': 'Kuka', - 'files.trashEmpty': 'A kuka üres', - 'files.emptyTrash': 'Kuka ürítése', - 'files.restore': 'Visszaállítás', - 'files.star': 'Csillag', - 'files.unstar': 'Csillag eltávolítása', - 'files.assign': 'Hozzárendelés', - 'files.assignTitle': 'Fájl hozzárendelése', - 'files.assignPlace': 'Hely', - 'files.assignBooking': 'Foglalás', - 'files.assignTransport': 'Közlekedés', - 'files.unassigned': 'Nincs hozzárendelve', - 'files.unlink': 'Kapcsolat eltávolítása', - 'files.toast.trashed': 'Kukába helyezve', - 'files.toast.restored': 'Fájl visszaállítva', - 'files.toast.trashEmptied': 'Kuka kiürítve', - 'files.toast.assigned': 'Fájl hozzárendelve', - 'files.toast.assignError': 'Hozzárendelés sikertelen', - 'files.toast.restoreError': 'Visszaállítás sikertelen', - 'files.confirm.permanentDelete': 'Véglegesen törlöd ezt a fájlt? Ez nem vonható vissza.', - 'files.confirm.emptyTrash': 'Véglegesen törlöd az összes kukába helyezett fájlt? Ez nem vonható vissza.', - 'files.noteLabel': 'Megjegyzés', - 'files.notePlaceholder': 'Megjegyzés hozzáadása...', - - // Csomagolás - 'packing.title': 'Csomagolási lista', - 'packing.empty': 'A csomagolási lista üres', - 'packing.import': 'Importálás', - 'packing.importTitle': 'Csomagolási lista importálása', - 'packing.importHint': 'Soronként egy tétel. Formátum: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális)', - 'packing.importPlaceholder': 'Tisztálkodás, Fogkefe\nRuházat, Pólók, 200\nDokumentumok, Útlevél, , Kézipoggyász\nElektronika, Töltő, 50, Bőrönd, checked', - 'packing.importCsv': 'CSV/TXT betöltése', - 'packing.importAction': '{count} importálása', - 'packing.importSuccess': '{count} tétel importálva', - 'packing.importError': 'Importálás sikertelen', - 'packing.importEmpty': 'Nincsenek importálható tételek', - 'packing.progress': '{packed} / {total} becsomagolva ({percent}%)', - 'packing.clearChecked': '{count} kipipált eltávolítása', - 'packing.clearCheckedShort': '{count} eltávolítása', - 'packing.suggestions': 'Javaslatok', - 'packing.suggestionsTitle': 'Javaslatok hozzáadása', - 'packing.allSuggested': 'Minden javaslat hozzáadva', - 'packing.allPacked': 'Minden be van csomagolva!', - 'packing.addPlaceholder': 'Új tárgy hozzáadása...', - 'packing.categoryPlaceholder': 'Kategória...', - 'packing.filterAll': 'Összes', - 'packing.filterOpen': 'Nyitott', - 'packing.filterDone': 'Kész', - 'packing.emptyTitle': 'A csomagolási lista üres', - 'packing.emptyHint': 'Adj hozzá tárgyakat vagy használd a javaslatokat', - 'packing.emptyFiltered': 'Nincs elem ebben a szűrőben', - 'packing.menuRename': 'Átnevezés', - 'packing.menuCheckAll': 'Összes kipipálása', - 'packing.menuUncheckAll': 'Összes jelölés törlése', - 'packing.menuDeleteCat': 'Kategória törlése', - 'packing.noMembers': 'Nincsenek utazási tagok', - 'packing.addItem': 'Tétel hozzáadása', - 'packing.addItemPlaceholder': 'Tétel neve...', - 'packing.addCategory': 'Kategória hozzáadása', - 'packing.newCategoryPlaceholder': 'Kategória neve (pl. Ruházat)', - 'packing.applyTemplate': 'Sablon alkalmazása', - 'packing.template': 'Sablon', - 'packing.templateApplied': '{count} tétel hozzáadva a sablonból', - 'packing.templateError': 'Nem sikerült alkalmazni a sablont', - 'packing.saveAsTemplate': 'Mentés sablonként', - 'packing.templateName': 'Sablon neve', - 'packing.templateSaved': 'Csomaglista elmentve sablonként', - 'packing.bags': 'Táskák', - 'packing.noBag': 'Nincs hozzárendelve', - 'packing.totalWeight': 'Összsúly', - 'packing.bagName': 'Táska neve...', - 'packing.addBag': 'Táska hozzáadása', - 'packing.changeCategory': 'Kategória módosítása', - 'packing.confirm.clearChecked': 'Biztosan el szeretnéd távolítani a(z) {count} kipipált tárgyat?', - 'packing.confirm.deleteCat': 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} tárggyal?', - 'packing.defaultCategory': 'Egyéb', - 'packing.toast.saveError': 'Nem sikerült menteni', - 'packing.toast.deleteError': 'Nem sikerült törölni', - 'packing.toast.renameError': 'Nem sikerült átnevezni', - 'packing.toast.addError': 'Nem sikerült hozzáadni', - - // Csomagolási javaslatok - 'packing.suggestions.items': [ - { name: 'Útlevél', category: 'Dokumentumok' }, - { name: 'Személyi igazolvány', category: 'Dokumentumok' }, - { name: 'Utazási biztosítás', category: 'Dokumentumok' }, - { name: 'Repülőjegyek', category: 'Dokumentumok' }, - { name: 'Bankkártya', category: 'Pénzügyek' }, - { name: 'Készpénz', category: 'Pénzügyek' }, - { name: 'Vízum', category: 'Dokumentumok' }, - { name: 'Pólók', category: 'Ruházat' }, - { name: 'Nadrágok', category: 'Ruházat' }, - { name: 'Fehérnemű', category: 'Ruházat' }, - { name: 'Zoknik', category: 'Ruházat' }, - { name: 'Kabát', category: 'Ruházat' }, - { name: 'Hálóruha', category: 'Ruházat' }, - { name: 'Fürdőruha', category: 'Ruházat' }, - { name: 'Esőkabát', category: 'Ruházat' }, - { name: 'Kényelmes cipő', category: 'Ruházat' }, - { name: 'Fogkefe', category: 'Tisztálkodás' }, - { name: 'Fogkrém', category: 'Tisztálkodás' }, - { name: 'Sampon', category: 'Tisztálkodás' }, - { name: 'Dezodor', category: 'Tisztálkodás' }, - { name: 'Naptej', category: 'Tisztálkodás' }, - { name: 'Borotva', category: 'Tisztálkodás' }, - { name: 'Töltő', category: 'Elektronika' }, - { name: 'Powerbank', category: 'Elektronika' }, - { name: 'Fejhallgató', category: 'Elektronika' }, - { name: 'Úti adapter', category: 'Elektronika' }, - { name: 'Fényképezőgép', category: 'Elektronika' }, - { name: 'Fájdalomcsillapító', category: 'Egészség' }, - { name: 'Ragtapasz', category: 'Egészség' }, - { name: 'Fertőtlenítőszer', category: 'Egészség' }, - ], - - // Tagok / Megosztás - 'members.shareTrip': 'Utazás megosztása', - 'members.inviteUser': 'Felhasználó meghívása', - 'members.selectUser': 'Felhasználó kiválasztása…', - 'members.invite': 'Meghívás', - 'members.allHaveAccess': 'Minden felhasználónak már van hozzáférése.', - 'members.access': 'Hozzáférés', - 'members.person': 'személy', - 'members.persons': 'személy', - 'members.you': 'te', - 'members.owner': 'Tulajdonos', - 'members.leaveTrip': 'Utazás elhagyása', - 'members.removeAccess': 'Hozzáférés eltávolítása', - 'members.confirmLeave': 'Elhagyod az utazást? Elveszíted a hozzáférést.', - 'members.confirmRemove': 'Eltávolítod a hozzáférést ettől a felhasználótól?', - 'members.loadError': 'Nem sikerült betölteni a tagokat', - 'members.added': 'hozzáadva', - 'members.addError': 'Nem sikerült hozzáadni', - 'members.removed': 'Tag eltávolítva', - 'members.removeError': 'Nem sikerült eltávolítani', - - // Kategóriák (Admin) - 'categories.title': 'Kategóriák', - 'categories.subtitle': 'Helyek kategóriáinak kezelése', - 'categories.new': 'Új kategória', - 'categories.empty': 'Még nincsenek kategóriák', - 'categories.namePlaceholder': 'Kategória neve', - 'categories.icon': 'Ikon', - 'categories.color': 'Szín', - 'categories.customColor': 'Egyéni szín kiválasztása', - 'categories.preview': 'Előnézet', - 'categories.defaultName': 'Kategória', - 'categories.update': 'Frissítés', - 'categories.create': 'Létrehozás', - 'categories.confirm.delete': 'Kategória törlése? Az ebben a kategóriában lévő helyek nem törlődnek.', - 'categories.toast.loadError': 'Nem sikerült betölteni a kategóriákat', - 'categories.toast.nameRequired': 'Kérjük, adj meg egy nevet', - 'categories.toast.updated': 'Kategória frissítve', - 'categories.toast.created': 'Kategória létrehozva', - 'categories.toast.saveError': 'Nem sikerült menteni', - 'categories.toast.deleted': 'Kategória törölve', - 'categories.toast.deleteError': 'Nem sikerült törölni', - - // Biztonsági mentés (Admin) - 'backup.title': 'Adatmentés', - 'backup.subtitle': 'Adatbázis és minden feltöltött fájl', - 'backup.refresh': 'Frissítés', - 'backup.upload': 'Mentés feltöltése', - 'backup.uploading': 'Feltöltés…', - 'backup.create': 'Mentés készítése', - 'backup.creating': 'Készítés…', - 'backup.empty': 'Még nincsenek mentések', - 'backup.createFirst': 'Első mentés készítése', - 'backup.download': 'Letöltés', - 'backup.restore': 'Visszaállítás', - 'backup.confirm.restore': '"{name}" mentés visszaállítása?\n\nMinden jelenlegi adat a mentéssel lesz helyettesítve.', - 'backup.confirm.uploadRestore': '"{name}" mentésfájl feltöltése és visszaállítása?\n\nMinden jelenlegi adat felülíródik.', - 'backup.confirm.delete': '"{name}" mentés törlése?', - 'backup.toast.loadError': 'Nem sikerült betölteni a mentéseket', - 'backup.toast.created': 'Mentés sikeresen létrehozva', - 'backup.toast.createError': 'Nem sikerült létrehozni a mentést', - 'backup.toast.restored': 'Mentés visszaállítva. Az oldal újratöltődik…', - 'backup.toast.restoreError': 'Nem sikerült visszaállítani', - 'backup.toast.uploadError': 'Nem sikerült feltölteni', - 'backup.toast.deleted': 'Mentés törölve', - 'backup.toast.deleteError': 'Nem sikerült törölni', - 'backup.toast.downloadError': 'Letöltés sikertelen', - 'backup.toast.settingsSaved': 'Automatikus mentés beállításai mentve', - 'backup.toast.settingsError': 'Nem sikerült menteni a beállításokat', - 'backup.auto.title': 'Automatikus mentés', - 'backup.auto.subtitle': 'Automatikus mentés ütemezés szerint', - 'backup.auto.enable': 'Automatikus mentés engedélyezése', - 'backup.auto.enableHint': 'A mentések automatikusan készülnek a választott ütemezés szerint', - 'backup.auto.interval': 'Időköz', - 'backup.auto.hour': 'Futtatás időpontja', - 'backup.auto.hourHint': 'Szerver helyi ideje ({format} formátum)', - 'backup.auto.dayOfWeek': 'A hét napja', - 'backup.auto.dayOfMonth': 'A hónap napja', - 'backup.auto.dayOfMonthHint': '1–28-ra korlátozva az összes hónappal való kompatibilitás érdekében', - 'backup.auto.scheduleSummary': 'Ütemezés', - 'backup.auto.summaryDaily': 'Minden nap {hour}:00-kor', - 'backup.auto.summaryWeekly': 'Minden {day} {hour}:00-kor', - 'backup.auto.summaryMonthly': 'Minden hónap {day}. napján {hour}:00-kor', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Az automatikus mentés Docker környezeti változókon keresztül van konfigurálva. A beállítások módosításához frissítsd a docker-compose.yml fájlt és indítsd újra a konténert.', - 'backup.auto.copyEnv': 'Docker env változók másolása', - 'backup.auto.envCopied': 'Docker env változók vágólapra másolva', - 'backup.auto.keepLabel': 'Régi mentések törlése ennyi idő után', - 'backup.dow.sunday': 'Va', - 'backup.dow.monday': 'Hé', - 'backup.dow.tuesday': 'Ke', - 'backup.dow.wednesday': 'Sze', - 'backup.dow.thursday': 'Csü', - 'backup.dow.friday': 'Pé', - 'backup.dow.saturday': 'Szo', - 'backup.interval.hourly': 'Óránként', - 'backup.interval.daily': 'Naponta', - 'backup.interval.weekly': 'Hetente', - 'backup.interval.monthly': 'Havonta', - 'backup.keep.1day': '1 nap', - 'backup.keep.3days': '3 nap', - 'backup.keep.7days': '7 nap', - 'backup.keep.14days': '14 nap', - 'backup.keep.30days': '30 nap', - 'backup.keep.forever': 'Örökre megőrzés', - - // Fotók - 'photos.title': 'Fotók', - 'photos.subtitle': '{count} fotó a következőhöz: {trip}', - 'photos.dropHere': 'Húzza ide a fényképeket...', - 'photos.dropHereActive': 'Húzza ide a fényképeket', - 'photos.captionForAll': 'Felirat (mindenkinek)', - 'photos.captionPlaceholder': 'Opcionális felirat...', - 'photos.addCaption': 'Felirat hozzáadása...', - 'photos.allDays': 'Minden nap', - 'photos.noPhotos': 'Még nincsenek fotók', - 'photos.uploadHint': 'Töltsd fel az úti fotóidat', - 'photos.clickToSelect': 'vagy kattints a kiválasztáshoz', - 'photos.linkPlace': 'Hely társítása', - 'photos.noPlace': 'Nincs hely', - 'photos.uploadN': '{n} fotó feltöltése', - 'photos.linkDay': 'Nap csatolása', - 'photos.noDay': 'Nincs nap', - 'photos.dayLabel': '{number}. nap', - 'photos.photoSelected': 'Fotó kiválasztva', - 'photos.photosSelected': 'Fotók kiválasztva', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · legfeljebb 30 fotó', - - // Mentés visszaállítása modal - 'backup.restoreConfirmTitle': 'Mentés visszaállítása?', - 'backup.restoreWarning': 'Minden jelenlegi adat (utazások, helyek, felhasználók, feltöltések) véglegesen lecserélődik a mentéssel. Ez a művelet nem vonható vissza.', - 'backup.restoreTip': 'Tipp: Készíts mentést a jelenlegi állapotról a visszaállítás előtt.', - 'backup.restoreConfirm': 'Igen, visszaállítás', - - // PDF - 'pdf.travelPlan': 'Utazási terv', - 'pdf.planned': 'Tervezett', - 'pdf.costLabel': 'Költség', - 'pdf.preview': 'PDF előnézet', - 'pdf.saveAsPdf': 'Mentés PDF-ként', - - // Tervező - 'planner.places': 'Helyek', - 'planner.bookings': 'Foglalások', - 'planner.packingList': 'Csomagolási lista', - 'planner.documents': 'Dokumentumok', - 'planner.dayPlan': 'Napi terv', - 'planner.reservations': 'Foglalások', - 'planner.minTwoPlaces': 'Legalább 2 koordinátákkal rendelkező hely szükséges', - 'planner.noGeoPlaces': 'Nincsenek koordinátákkal rendelkező helyek', - 'planner.routeCalculated': 'Útvonal kiszámítva', - 'planner.routeCalcFailed': 'Nem sikerült kiszámítani az útvonalat', - 'planner.routeError': 'Hiba az útvonalszámítás során', - 'planner.icsExportFailed': 'Az ICS-exportálás sikertelen', - 'planner.routeOptimized': 'Útvonal optimalizálva', - 'planner.reservationUpdated': 'Foglalás frissítve', - 'planner.reservationAdded': 'Foglalás hozzáadva', - 'planner.confirmDeleteReservation': 'Foglalás törlése?', - 'planner.reservationDeleted': 'Foglalás törölve', - 'planner.days': 'nap', - 'planner.allPlaces': 'Összes hely', - 'planner.totalPlaces': 'Összesen {n} hely', - 'planner.noDaysPlanned': 'Még nincsenek napok tervezve', - 'planner.editTrip': 'Utazás szerkesztése \u2192', - 'planner.placeOne': '1 hely', - 'planner.placeN': '{n} hely', - 'planner.addNote': 'Jegyzet hozzáadása', - 'planner.noEntries': 'Nincsenek bejegyzések erre a napra', - 'planner.addPlace': 'Hely/tevékenység hozzáadása', - 'planner.addPlaceShort': '+ Hely/tevékenység hozzáadása', - 'planner.resPending': 'Foglalás függőben · ', - 'planner.resConfirmed': 'Foglalás megerősítve · ', - 'planner.notePlaceholder': 'Jegyzet\u2026', - 'planner.noteTimePlaceholder': 'Időpont (opcionális)', - 'planner.noteExamplePlaceholder': 'pl. S3 14:30-kor a főpályaudvarról, komp a 7. mólóról, ebédszünet\u2026', - 'planner.totalCost': 'Összköltség', - 'planner.searchPlaces': 'Helyek keresése\u2026', - 'planner.allCategories': 'Összes kategória', - 'planner.noPlacesFound': 'Nem találhatók helyek', - 'planner.addFirstPlace': 'Első hely hozzáadása', - 'planner.noReservations': 'Nincsenek foglalások', - 'planner.addFirstReservation': 'Első foglalás hozzáadása', - 'planner.new': 'Új', - 'planner.addToDay': '+ Nap', - 'planner.calculating': 'Számítás\u2026', - 'planner.route': 'Útvonal', - 'planner.optimize': 'Optimalizálás', - 'planner.openGoogleMaps': 'Megnyitás a Google Térképben', - 'planner.selectDayHint': 'Válassz egy napot a bal oldali listából a napi terv megtekintéséhez', - 'planner.noPlacesForDay': 'Még nincsenek helyek erre a napra', - 'planner.addPlacesLink': 'Helyek hozzáadása \u2192', - 'planner.minTotal': 'perc összesen', - 'planner.noReservation': 'Nincs foglalás', - 'planner.removeFromDay': 'Eltávolítás a napról', - 'planner.addToThisDay': 'Hozzáadás a naphoz', - 'planner.overview': 'Áttekintés', - 'planner.noDays': 'Még nincsenek napok', - 'planner.editTripToAddDays': 'Szerkeszd az utazást napok hozzáadásához', - 'planner.dayCount': '{n} nap', - 'planner.clickToUnlock': 'Kattints a feloldáshoz', - 'planner.keepPosition': 'Pozíció megtartása útvonal-optimalizálás során', - 'planner.dayDetails': 'Nap részletei', - 'planner.dayN': '{n}. nap', - - // Irányítópult statisztikák - 'stats.countries': 'Országok', - 'stats.cities': 'Városok', - 'stats.trips': 'Utazások', - 'stats.places': 'Helyek', - 'stats.worldProgress': 'Világ felfedezése', - 'stats.visited': 'meglátogatott', - 'stats.remaining': 'hátralévő', - 'stats.visitedCountries': 'Meglátogatott országok', - - // Nap részletei panel - 'day.precipProb': 'Csapadékvalószínűség', - 'day.precipitation': 'Csapadék', - 'day.wind': 'Szél', - 'day.sunrise': 'Napkelte', - 'day.sunset': 'Napnyugta', - 'day.hourlyForecast': 'Óránkénti előrejelzés', - 'day.climateHint': 'Történelmi átlagok — valós előrejelzés a dátum előtti 16 napon belül érhető el.', - 'day.noWeather': 'Nem állnak rendelkezésre időjárási adatok. Adj hozzá egy helyet koordinátákkal.', - 'day.overview': 'Napi áttekintés', - 'day.accommodation': 'Szállás', - 'day.addAccommodation': 'Szállás hozzáadása', - 'day.hotelDayRange': 'Alkalmazás napokra', - 'day.noPlacesForHotel': 'Először adj hozzá helyeket az utazásodhoz', - 'day.allDays': 'Összes', - 'day.checkIn': 'Bejelentkezés', - 'day.checkInUntil': 'Eddig', - 'day.checkOut': 'Kijelentkezés', - 'day.confirmation': 'Visszaigazolás', - 'day.editAccommodation': 'Szállás szerkesztése', - 'day.reservations': 'Foglalások', - - // Collab bővítmény - 'collab.tabs.chat': 'Csevegés', - 'collab.tabs.notes': 'Jegyzetek', - 'collab.tabs.polls': 'Szavazások', - 'collab.whatsNext.title': 'Mi következik', - 'collab.whatsNext.today': 'Ma', - 'collab.whatsNext.tomorrow': 'Holnap', - 'collab.whatsNext.empty': 'Nincsenek közelgő tevékenységek', - 'collab.whatsNext.until': '-ig', - 'collab.whatsNext.emptyHint': 'Az időponttal rendelkező tevékenységek itt jelennek meg', - 'collab.chat.send': 'Küldés', - 'collab.chat.placeholder': 'Üzenet írása...', - 'collab.chat.empty': 'Kezdd el a beszélgetést', - 'collab.chat.emptyHint': 'Az üzenetek az utazás minden tagjával meg vannak osztva', - 'collab.chat.emptyDesc': 'Oszd meg ötleteidet, terveidet és híreidet az utazócsoportoddal', - 'collab.chat.today': 'Ma', - 'collab.chat.yesterday': 'Tegnap', - 'collab.chat.deletedMessage': 'törölt egy üzenetet', - 'collab.chat.reply': 'Válasz', - 'collab.chat.loadMore': 'Korábbi üzenetek betöltése', - 'collab.chat.justNow': 'éppen most', - 'collab.chat.minutesAgo': '{n} perce', - 'collab.chat.hoursAgo': '{n} órája', - 'collab.notes.title': 'Jegyzetek', - 'collab.notes.new': 'Új jegyzet', - 'collab.notes.empty': 'Még nincsenek jegyzetek', - 'collab.notes.emptyHint': 'Rögzítsd az ötleteidet és terveidet', - 'collab.notes.all': 'Összes', - 'collab.notes.titlePlaceholder': 'Jegyzet címe', - 'collab.notes.contentPlaceholder': 'Írj valamit...', - 'collab.notes.categoryPlaceholder': 'Kategória', - 'collab.notes.newCategory': 'Új kategória...', - 'collab.notes.category': 'Kategória', - 'collab.notes.noCategory': 'Nincs kategória', - 'collab.notes.color': 'Szín', - 'collab.notes.save': 'Mentés', - 'collab.notes.cancel': 'Mégse', - 'collab.notes.edit': 'Szerkesztés', - 'collab.notes.delete': 'Törlés', - 'collab.notes.pin': 'Kitűzés', - 'collab.notes.unpin': 'Kitűzés eltávolítása', - 'collab.notes.daysAgo': '{n} napja', - 'collab.notes.categorySettings': 'Kategóriák kezelése', - 'collab.notes.create': 'Létrehozás', - 'collab.notes.website': 'Weboldal', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Fájlok csatolása', - 'collab.notes.noCategoriesYet': 'Még nincsenek kategóriák', - 'collab.notes.emptyDesc': 'Hozz létre egy jegyzetet a kezdéshez', - 'collab.polls.title': 'Szavazások', - 'collab.polls.new': 'Új szavazás', - 'collab.polls.empty': 'Még nincsenek szavazások', - 'collab.polls.emptyHint': 'Kérdezd meg a csoportot és szavazzatok együtt', - 'collab.polls.question': 'Kérdés', - 'collab.polls.questionPlaceholder': 'Mit csináljunk?', - 'collab.polls.addOption': 'Opció hozzáadása', - 'collab.polls.optionPlaceholder': '{n}. opció', - 'collab.polls.create': 'Szavazás létrehozása', - 'collab.polls.close': 'Lezárás', - 'collab.polls.closed': 'Lezárva', - 'collab.polls.votes': '{n} szavazat', - 'collab.polls.vote': '{n} szavazat', - 'collab.polls.multipleChoice': 'Többszörös választás', - 'collab.polls.multiChoice': 'Többszörös választás', - 'collab.polls.deadline': 'Határidő', - 'collab.polls.option': 'Opció', - 'collab.polls.options': 'Opciók', - 'collab.polls.delete': 'Törlés', - 'collab.polls.closedSection': 'Lezárva', - - // Emlékek / Immich - 'memories.title': 'Fotók', - 'memories.notConnected': 'Immich nincs csatlakoztatva', - 'memories.notConnectedHint': 'Csatlakoztasd az Immich példányodat a Beállításokban, hogy itt lásd az utazási fotóidat.', - 'memories.notConnectedMultipleHint': 'A fényképek hozzáadásához csatlakoztasson egyet a következő fényképszolgáltatók közül a Beállításokban: {provider_names}.', - 'memories.noDates': 'Adj hozzá dátumokat az utazáshoz a fotók betöltéséhez.', - 'memories.noPhotos': 'Nem találhatók fotók', - 'memories.noPhotosHint': 'Nem találhatók fotók az Immichben erre az utazási időszakra.', - 'memories.photosFound': 'fotó', - 'memories.fromOthers': 'másoktól', - 'memories.sharePhotos': 'Fotók megosztása', - 'memories.sharing': 'Megosztás', - 'memories.reviewTitle': 'Nézd át a fotóidat', - 'memories.reviewHint': 'Kattints a fotókra a megosztásból való kizáráshoz.', - 'memories.shareCount': '{count} fotó megosztása', - 'memories.providerUrl': 'Szerver URL', - 'memories.providerApiKey': 'API kulcs', - 'memories.providerUsername': 'Felhasználónév', - 'memories.providerPassword': 'Jelszó', - 'memories.providerOTP': 'MFA kód (ha engedélyezve van)', - 'memories.skipSSLVerification': 'SSL tanúsítvány ellenőrzésének kihagyása', - 'memories.immichAutoUpload': 'Journey-fotók feltöltésekor másolat Immich-be is', - 'memories.providerUrlHintSynology': 'Adja meg a Photos alkalmazás elérési útját az URL-ben, pl. https://nas:5001/photo', - 'memories.testConnection': 'Kapcsolat tesztelése', - 'memories.testShort': 'Teszt', - 'memories.testFirst': 'Először teszteld a kapcsolatot', - 'memories.connected': 'Csatlakoztatva', - 'memories.disconnected': 'Nincs csatlakoztatva', - 'memories.connectionSuccess': 'Csatlakozva az Immichhez', - 'memories.connectionError': 'Nem sikerült csatlakozni az Immichhez', - 'memories.saved': '{provider_name} beállítások mentve', - 'memories.providerDisconnectedBanner': 'A {provider_name} kapcsolat megszakadt. Csatlakozzon újra a Beállításokban a fényképek megtekintéséhez.', - 'memories.saveError': 'Nem sikerült menteni a(z) {provider_name} beállításait', - 'memories.addPhotos': 'Fotók hozzáadása', - 'memories.linkAlbum': 'Album csatolása', - 'memories.selectAlbum': 'Immich album kiválasztása', - 'memories.selectAlbumMultiple': 'Album kiválasztása', - 'memories.noAlbums': 'Nem található album', - 'memories.syncAlbum': 'Album szinkronizálása', - 'memories.unlinkAlbum': 'Leválasztás', - 'memories.photos': 'fotó', - 'memories.selectPhotos': 'Fotók kiválasztása az Immichből', - 'memories.selectPhotosMultiple': 'Fényképek kiválasztása', - 'memories.selectHint': 'Koppints a fotókra a kijelölésükhöz.', - 'memories.selected': 'kijelölve', - 'memories.addSelected': '{count} fotó hozzáadása', - 'memories.alreadyAdded': 'Hozzáadva', - 'memories.private': 'Privát', - 'memories.stopSharing': 'Megosztás leállítása', - 'memories.oldest': 'Legrégebbi elöl', - 'memories.newest': 'Legújabb elöl', - 'memories.allLocations': 'Összes helyszín', - 'memories.tripDates': 'Utazás dátumai', - 'memories.allPhotos': 'Összes fotó', - 'memories.confirmShareTitle': 'Megosztás az utazótársakkal?', - 'memories.confirmShareHint': '{count} fotó lesz látható az utazás összes tagja számára. Később egyenként is priváttá teheted őket.', - 'memories.confirmShareButton': 'Fotók megosztása', - - // Permissions - 'admin.tabs.permissions': 'Jogosultságok', - 'perm.title': 'Jogosultsági beállítások', - 'perm.subtitle': 'Szabályozd, ki milyen műveleteket végezhet az alkalmazásban', - 'perm.saved': 'Jogosultsági beállítások mentve', - 'perm.resetDefaults': 'Alapértelmezések visszaállítása', - 'perm.customized': 'testreszabott', - 'perm.level.admin': 'Csak adminisztrátor', - 'perm.level.tripOwner': 'Utazás tulajdonosa', - 'perm.level.tripMember': 'Utazás tagjai', - 'perm.level.everybody': 'Mindenki', - 'perm.cat.trip': 'Utazáskezelés', - 'perm.cat.members': 'Tagkezelés', - 'perm.cat.files': 'Fájlok', - 'perm.cat.content': 'Tartalom és menetrend', - 'perm.cat.extras': 'Költségvetés, csomagolás és együttműködés', - 'perm.action.trip_create': 'Utazások létrehozása', - 'perm.action.trip_edit': 'Utazás részleteinek szerkesztése', - 'perm.action.trip_delete': 'Utazások törlése', - 'perm.action.trip_archive': 'Utazások archiválása / visszaállítása', - 'perm.action.trip_cover_upload': 'Borítókép feltöltése', - 'perm.action.member_manage': 'Tagok hozzáadása / eltávolítása', - 'perm.action.file_upload': 'Fájlok feltöltése', - 'perm.action.file_edit': 'Fájl metaadatok szerkesztése', - 'perm.action.file_delete': 'Fájlok törlése', - 'perm.action.place_edit': 'Helyek hozzáadása / szerkesztése / törlése', - 'perm.action.day_edit': 'Napok, jegyzetek és hozzárendelések szerkesztése', - 'perm.action.reservation_edit': 'Foglalások kezelése', - 'perm.action.budget_edit': 'Költségvetés kezelése', - 'perm.action.packing_edit': 'Csomagolási listák kezelése', - 'perm.action.collab_edit': 'Együttműködés (jegyzetek, szavazások, chat)', - 'perm.action.share_manage': 'Megosztási linkek kezelése', - 'perm.actionHint.trip_create': 'Ki hozhat létre új utazásokat', - 'perm.actionHint.trip_edit': 'Ki módosíthatja az utazás nevét, dátumait, leírását és pénznemét', - 'perm.actionHint.trip_delete': 'Ki törölhet véglegesen egy utazást', - 'perm.actionHint.trip_archive': 'Ki archiválhat vagy állíthat vissza egy utazást', - 'perm.actionHint.trip_cover_upload': 'Ki tölthet fel vagy módosíthat borítóképet', - 'perm.actionHint.member_manage': 'Ki hívhat meg vagy távolíthat el utazás tagokat', - 'perm.actionHint.file_upload': 'Ki tölthet fel fájlokat egy utazáshoz', - 'perm.actionHint.file_edit': 'Ki szerkesztheti a fájlok leírásait és linkjeit', - 'perm.actionHint.file_delete': 'Ki helyezhet fájlokat a kukába vagy törölheti véglegesen', - 'perm.actionHint.place_edit': 'Ki adhat hozzá, szerkeszthet vagy törölhet helyeket', - 'perm.actionHint.day_edit': 'Ki szerkesztheti a napokat, napi jegyzeteket és hely-hozzárendeléseket', - 'perm.actionHint.reservation_edit': 'Ki hozhat létre, szerkeszthet vagy törölhet foglalásokat', - 'perm.actionHint.budget_edit': 'Ki hozhat létre, szerkeszthet vagy törölhet költségvetési tételeket', - 'perm.actionHint.packing_edit': 'Ki kezelheti a csomagolási tételeket és táskákat', - 'perm.actionHint.collab_edit': 'Ki hozhat létre jegyzeteket, szavazásokat és küldhet üzeneteket', - 'perm.actionHint.share_manage': 'Ki hozhat létre vagy törölhet nyilvános megosztási linkeket', - // Undo - 'undo.button': 'Visszavonás', - 'undo.tooltip': 'Visszavonás: {action}', - 'undo.assignPlace': 'Hely naphoz rendelve', - 'undo.removeAssignment': 'Hely eltávolítva a napról', - 'undo.reorder': 'Helyek átrendezve', - 'undo.optimize': 'Útvonal optimalizálva', - 'undo.deletePlace': 'Hely törölve', - 'undo.deletePlaces': 'Helyek törölve', - 'undo.moveDay': 'Hely áthelyezve másik napra', - 'undo.lock': 'Hely zárolása váltva', - 'undo.importGpx': 'GPX importálás', - 'undo.importKeyholeMarkup': 'KMZ/KML importálás', - 'undo.importGoogleList': 'Google Maps importálás', - 'undo.importNaverList': 'Naver Maps importálás', - - // Notifications - 'notifications.title': 'Értesítések', - 'notifications.markAllRead': 'Összes olvasottnak jelölése', - 'notifications.deleteAll': 'Összes törlése', - 'notifications.showAll': 'Összes értesítés megtekintése', - 'notifications.empty': 'Nincsenek értesítések', - 'notifications.emptyDescription': 'Mindennel naprakész vagy!', - 'notifications.all': 'Összes', - 'notifications.unreadOnly': 'Olvasatlan', - 'notifications.markRead': 'Olvasottnak jelölés', - 'notifications.markUnread': 'Olvasatlannak jelölés', - 'notifications.delete': 'Törlés', - 'notifications.system': 'Rendszer', - 'notifications.synologySessionCleared.title': 'Synology Photos leválasztva', - 'notifications.synologySessionCleared.text': 'A szerver vagy a fiók megváltozott — lépjen a Beállításokba a kapcsolat újrateszteléséhez.', - 'memories.error.loadAlbums': 'Az albumok betöltése sikertelen', - 'memories.error.linkAlbum': 'Az album csatolása sikertelen', - 'memories.error.unlinkAlbum': 'Az album leválasztása sikertelen', - 'memories.error.syncAlbum': 'Az album szinkronizálása sikertelen', - 'memories.error.loadPhotos': 'A fotók betöltése sikertelen', - 'memories.error.addPhotos': 'A fotók hozzáadása sikertelen', - 'memories.error.removePhoto': 'A fotó eltávolítása sikertelen', - 'memories.error.toggleSharing': 'A megosztás frissítése sikertelen', - 'undo.addPlace': 'Hely hozzáadva', - 'undo.done': 'Visszavonva: {action}', - 'notifications.test.title': 'Teszt értesítés {actor} részéről', - 'notifications.test.text': 'Ez egy egyszerű teszt értesítés.', - 'notifications.test.booleanTitle': '{actor} jóváhagyásodat kéri', - 'notifications.test.booleanText': 'Teszt igen/nem értesítés.', - 'notifications.test.accept': 'Jóváhagyás', - 'notifications.test.decline': 'Elutasítás', - 'notifications.test.navigateTitle': 'Nézz meg valamit', - 'notifications.test.navigateText': 'Teszt navigációs értesítés.', - 'notifications.test.goThere': 'Odamegyek', - 'notifications.test.adminTitle': 'Adminisztrátor üzenet', - 'notifications.test.adminText': '{actor} teszt értesítést küldött az összes adminisztrátornak.', - 'notifications.test.tripTitle': '{actor} üzenetet küldött az utazásodba', - 'notifications.test.tripText': 'Teszt értesítés a(z) "{trip}" utazáshoz.', - - // Todo - 'todo.subtab.packing': 'Csomagolási lista', - 'todo.subtab.todo': 'Teendők', - 'todo.completed': 'kész', - 'todo.filter.all': 'Mind', - 'todo.filter.open': 'Nyitott', - 'todo.filter.done': 'Kész', - 'todo.uncategorized': 'Kategória nélküli', - 'todo.namePlaceholder': 'Feladat neve', - 'todo.descriptionPlaceholder': 'Leírás (opcionális)', - 'todo.unassigned': 'Nem hozzárendelt', - 'todo.noCategory': 'Nincs kategória', - 'todo.hasDescription': 'Van leírás', - 'todo.addItem': 'Új feladat', - 'todo.sidebar.sortBy': 'Rendezés', - 'todo.priority': 'Prioritás', - 'todo.newCategoryLabel': 'új', - 'budget.categoriesLabel': 'kategóriák', - 'todo.newCategory': 'Kategória neve', - 'todo.addCategory': 'Kategória hozzáadása', - 'todo.newItem': 'Új feladat', - 'todo.empty': 'Még nincsenek feladatok. Adj hozzá egyet a kezdéshez!', - 'todo.filter.my': 'Saját feladataim', - 'todo.filter.overdue': 'Lejárt', - 'todo.sidebar.tasks': 'Feladatok', - 'todo.sidebar.categories': 'Kategóriák', - 'todo.detail.title': 'Feladat', - 'todo.detail.description': 'Leírás', - 'todo.detail.category': 'Kategória', - 'todo.detail.dueDate': 'Határidő', - 'todo.detail.assignedTo': 'Hozzárendelve', - 'todo.detail.delete': 'Törlés', - 'todo.detail.save': 'Módosítások mentése', - 'todo.detail.create': 'Feladat létrehozása', - 'todo.detail.priority': 'Prioritás', - 'todo.detail.noPriority': 'Nincs', - 'todo.sortByPrio': 'Prioritás', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Új verzió elérhető', - 'settings.notificationPreferences.noChannels': 'Nincsenek értesítési csatornák beállítva. Kérd meg a rendszergazdát, hogy állítson be e-mail vagy webhook értesítéseket.', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Adja meg a Discord, Slack vagy egyéni webhook URL-jét az értesítések fogadásához.', - 'settings.webhookUrl.saved': 'Webhook URL mentve', - 'settings.webhookUrl.test': 'Teszt', - 'settings.webhookUrl.testSuccess': 'Teszt webhook sikeresen elküldve', - 'settings.webhookUrl.testFailed': 'Teszt webhook sikertelen', - 'settings.ntfyUrl.topicLabel': 'Ntfy téma', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy szerver URL (opcionális)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Add meg az Ntfy témádat push értesítések fogadásához. Hagyd üresen a szervert a rendszergazda által beállított alapértelmezett használatához.', - 'settings.ntfyUrl.tokenLabel': 'Hozzáférési token (opcionális)', - 'settings.ntfyUrl.tokenHint': 'Jelszóval védett témákhoz szükséges.', - 'settings.ntfyUrl.saved': 'Ntfy beállítások mentve', - 'settings.ntfyUrl.test': 'Teszt', - 'settings.ntfyUrl.testSuccess': 'Teszt Ntfy értesítés sikeresen elküldve', - 'settings.ntfyUrl.testFailed': 'Teszt Ntfy értesítés sikertelen', - 'settings.ntfyUrl.tokenCleared': 'Hozzáférési token törölve', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Az alkalmazáson belüli értesítések mindig aktívak, és globálisan nem kapcsolhatók ki.', - 'admin.notifications.adminWebhookPanel.title': 'Admin webhook', - 'admin.notifications.adminWebhookPanel.hint': 'Ez a webhook kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói webhookoktól, és automatikusan küld, ha URL van beállítva.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL mentve', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Teszt webhook sikeresen elküldve', - 'admin.notifications.adminWebhookPanel.testFailed': 'Teszt webhook sikertelen', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Az admin webhook automatikusan küld, ha URL van beállítva', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Lehetővé teszi a felhasználóknak, hogy saját ntfy-témáikat konfigurálják push értesítésekhez. Állítsa be az alapértelmezett szervert alább a felhasználói beállítások előre kitöltéséhez.', - 'admin.notifications.testNtfy': 'Teszt Ntfy küldése', - 'admin.notifications.testNtfySuccess': 'Teszt Ntfy sikeresen elküldve', - 'admin.notifications.testNtfyFailed': 'Teszt Ntfy sikertelen', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Ez az Ntfy téma kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói témáktól, és mindig küld, ha konfigurálva van.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy szerver URL', - 'admin.notifications.adminNtfyPanel.serverHint': 'Alapértelmezett szerverként is szolgál a felhasználói ntfy értesítésekhez. Üresen hagyva ntfy.sh-t használ. A felhasználók felülírhatják saját beállításaikban.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Hozzáférési token (opcionális)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Admin hozzáférési token törölve', - 'admin.notifications.adminNtfyPanel.saved': 'Admin Ntfy beállítások mentve', - 'admin.notifications.adminNtfyPanel.test': 'Teszt Ntfy küldése', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Teszt Ntfy sikeresen elküldve', - 'admin.notifications.adminNtfyPanel.testFailed': 'Teszt Ntfy sikertelen', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Az admin Ntfy mindig küld, ha egy téma konfigurálva van', - 'admin.notifications.adminNotificationsHint': 'Állítsa be, hogy mely csatornák szállítsák az admin értesítéseket (pl. verziófrissítési figyelmeztetések). A webhook automatikusan küld, ha admin webhook URL van megadva.', - 'admin.notifications.tripReminders.title': 'Utazási emlékeztetők', - 'admin.notifications.tripReminders.hint': 'Emlékeztető értesítést küld az utazás kezdete előtt (az utazásnál megadott emlékeztető napok szükségesek).', - 'admin.notifications.tripReminders.enabled': 'Utazási emlékeztetők engedélyezve', - 'admin.notifications.tripReminders.disabled': 'Utazási emlékeztetők letiltva', - 'admin.tabs.notifications': 'Értesítések', - 'notifications.versionAvailable.title': 'Elérhető frissítés', - 'notifications.versionAvailable.text': 'A TREK {version} már elérhető.', - 'notifications.versionAvailable.button': 'Részletek megtekintése', - 'notif.test.title': '[Teszt] Értesítés', - 'notif.test.simple.text': 'Ez egy egyszerű teszt értesítés.', - 'notif.test.boolean.text': 'Elfogadod ezt a teszt értesítést?', - 'notif.test.navigate.text': 'Kattints alább az irányítópultra navigáláshoz.', - - // Notifications - 'notif.trip_invite.title': 'Utazásra meghívó', - 'notif.trip_invite.text': '{actor} meghívott a(z) {trip} utazásra', - 'notif.booking_change.title': 'Foglalás frissítve', - 'notif.booking_change.text': '{actor} frissített egy foglalást a(z) {trip} utazásban', - 'notif.trip_reminder.title': 'Utazás emlékeztető', - 'notif.trip_reminder.text': 'A(z) {trip} utazás hamarosan kezdődik!', - 'notif.todo_due.title': 'Teendő esedékes', - 'notif.todo_due.text': '{todo} ({trip}) határideje: {due}', - 'notif.vacay_invite.title': 'Vacay Fusion meghívó', - 'notif.vacay_invite.text': '{actor} meghívott a nyaralási tervek összevonásához', - 'notif.photos_shared.title': 'Fotók megosztva', - 'notif.photos_shared.text': '{actor} {count} fotót osztott meg a(z) {trip} utazásban', - 'notif.collab_message.title': 'Új üzenet', - 'notif.collab_message.text': '{actor} üzenetet küldött a(z) {trip} utazásban', - 'notif.packing_tagged.title': 'Csomagolási feladat', - 'notif.packing_tagged.text': '{actor} hozzárendelte Önt a {category} kategóriához a(z) {trip} utazásban', - 'notif.version_available.title': 'Új verzió elérhető', - 'notif.version_available.text': 'A TREK {version} elérhető', - 'notif.action.view_trip': 'Utazás megtekintése', - 'notif.action.view_collab': 'Üzenetek megtekintése', - 'notif.action.view_packing': 'Csomagolás megtekintése', - 'notif.action.view_photos': 'Fotók megtekintése', - 'notif.action.view_vacay': 'Vacay megtekintése', - 'notif.action.view_admin': 'Admin megnyitása', - 'notif.action.view': 'Megtekintés', - 'notif.action.accept': 'Elfogadás', - 'notif.action.decline': 'Elutasítás', - 'notif.generic.title': 'Értesítés', - 'notif.generic.text': 'Új értesítésed érkezett', - 'notif.dev.unknown_event.title': '[DEV] Ismeretlen esemény', - 'notif.dev.unknown_event.text': 'A(z) "{event}" eseménytípus nincs regisztrálva az EVENT_NOTIFICATION_CONFIG-ban', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'az imént', - 'common.hoursAgo': '{count} órája', - 'common.daysAgo': '{count} napja', - 'memories.saveRouteNotConfigured': 'A mentési útvonal nincs konfigurálva ehhez a szolgáltatóhoz', - 'memories.testRouteNotConfigured': 'A tesztútvonal nincs konfigurálva ehhez a szolgáltatóhoz', - 'memories.fillRequiredFields': 'Kérjük töltse ki az összes kötelező mezőt', - 'journey.search.placeholder': 'Utak keresése…', - 'journey.search.noResults': 'Nincs „{query}" kifejezéssel egyező út', - 'journey.title': 'Útinaplók', - 'journey.subtitle': 'Kövesse nyomon utazásait valós időben', - 'journey.new': 'Új útinapló', - 'journey.create': 'Létrehozás', - 'journey.titlePlaceholder': 'Hová utazol?', - 'journey.empty': 'Még nincsenek útinaplók', - 'journey.emptyHint': 'Kezdd el dokumentálni a következő utazásod', - 'journey.deleted': 'Útinapló törölve', - 'journey.createError': 'Nem sikerült létrehozni az útinaplót', - 'journey.deleteError': 'Nem sikerült törölni az útinaplót', - 'journey.deleteConfirmTitle': 'Törlés', - 'journey.deleteConfirmMessage': 'Törlöd a(z) „{title}" útinaplót? Ez nem vonható vissza.', - 'journey.deleteConfirmGeneric': 'Biztosan törölni szeretnéd?', - 'journey.notFound': 'Útinapló nem található', - 'journey.photos': 'Fotók', - 'journey.timelineEmpty': 'Még nincsenek megállók', - 'journey.timelineEmptyHint': 'Adj hozzá egy bejelentkezést vagy írj naplóbejegyzést a kezdéshez', - 'journey.status.draft': 'Vázlat', - 'journey.status.active': 'Aktív', - 'journey.status.completed': 'Befejezett', - 'journey.status.upcoming': 'Közelgő', - 'journey.status.archived': 'Archivált', - 'journey.checkin.add': 'Bejelentkezés', - 'journey.checkin.namePlaceholder': 'Helyszín neve', - 'journey.checkin.notesPlaceholder': 'Jegyzetek (opcionális)', - 'journey.checkin.save': 'Mentés', - 'journey.checkin.error': 'Nem sikerült menteni a bejelentkezést', - 'journey.entry.add': 'Napló', - 'journey.entry.edit': 'Bejegyzés szerkesztése', - 'journey.entry.titlePlaceholder': 'Cím (opcionális)', - 'journey.entry.bodyPlaceholder': 'Mi történt ma?', - 'journey.entry.save': 'Mentés', - 'journey.entry.error': 'Nem sikerült menteni a bejegyzést', - 'journey.photo.add': 'Fotó', - 'journey.photo.uploadError': 'A feltöltés sikertelen', - 'journey.share.share': 'Megosztás', - 'journey.share.public': 'Nyilvános', - 'journey.share.linkCopied': 'Nyilvános link másolva', - 'journey.share.disabled': 'Nyilvános megosztás letiltva', - 'journey.editor.titlePlaceholder': 'Adj nevet ennek a pillanatnak...', - 'journey.editor.bodyPlaceholder': 'Meséld el ennek a napnak a történetét...', - 'journey.editor.placePlaceholder': 'Helyszín (opcionális)', - 'journey.editor.tagsPlaceholder': 'Címkék: rejtett kincs, legjobb étel, újra meglátogatandó...', - 'journey.visibility.private': 'Privát', - 'journey.visibility.shared': 'Megosztott', - 'journey.visibility.public': 'Nyilvános', - 'journey.emptyState.title': 'Itt kezdődik a történeted', - 'journey.emptyState.subtitle': 'Jelentkezz be egy helyszínen vagy írd meg az első naplóbejegyzésed', - 'journey.frontpage.subtitle': 'Alakítsd utazásaidat történetekké, amelyeket soha nem felejtesz el', - 'journey.frontpage.createJourney': 'Útinapló létrehozása', - 'journey.frontpage.activeJourney': 'Aktív útinapló', - 'journey.frontpage.allJourneys': 'Összes útinapló', - 'journey.frontpage.journeys': 'útinapló', - 'journey.frontpage.createNew': 'Új útinapló létrehozása', - 'journey.frontpage.createNewSub': 'Válassz utakat, írj történeteket, oszd meg kalandjaidat', - 'journey.frontpage.live': 'Élő', - 'journey.frontpage.synced': 'Szinkronizálva', - 'journey.frontpage.continueWriting': 'Írás folytatása', - 'journey.frontpage.updated': 'Frissítve: {time}', - 'journey.frontpage.suggestionLabel': 'Az út épp véget ért', - 'journey.frontpage.suggestionText': 'Alakítsd a(z) {title} útinaplóvá', - 'journey.frontpage.dismiss': 'Elvetés', - 'journey.frontpage.journeyName': 'Útinapló neve', - 'journey.frontpage.namePlaceholder': 'pl. Délkelet-Ázsia 2026', - 'journey.frontpage.selectTrips': 'Utak kiválasztása', - 'journey.frontpage.tripsSelected': 'út kiválasztva', - 'journey.frontpage.trips': 'út', - 'journey.frontpage.placesImported': 'helyszín importálásra kerül', - 'journey.frontpage.places': 'helyszín', - 'journey.detail.backToJourney': 'Vissza az útinaplóhoz', - 'journey.detail.syncedWithTrips': 'Szinkronizálva az utakkal', - 'journey.detail.addEntry': 'Bejegyzés hozzáadása', - 'journey.detail.newEntry': 'Új bejegyzés', - 'journey.detail.editEntry': 'Bejegyzés szerkesztése', - 'journey.detail.noEntries': 'Még nincsenek bejegyzések', - 'journey.detail.noEntriesHint': 'Adj hozzá egy utat a vázlatos bejegyzések elkészítéséhez', - 'journey.detail.noPhotos': 'Még nincsenek fotók', - 'journey.detail.noPhotosHint': 'Tölts fel fotókat a bejegyzésekhez vagy böngészd az Immich/Synology könyvtárat', - 'journey.detail.journeyStats': 'Útinapló statisztika', - 'journey.detail.syncedTrips': 'Szinkronizált utak', - 'journey.detail.noTripsLinked': 'Még nincsenek kapcsolt utak', - 'journey.detail.contributors': 'Közreműködők', - 'journey.detail.readMore': 'Tovább olvasás', - 'journey.detail.prosCons': 'Előnyök és hátrányok', - 'journey.detail.photos': 'fotók', - 'journey.detail.day': '{number}. nap', - 'journey.detail.places': 'helyek', - 'journey.stats.days': 'Napok', - 'journey.stats.cities': 'Városok', - 'journey.stats.entries': 'Bejegyzések', - 'journey.stats.photos': 'Fotók', - 'journey.stats.places': 'Helyszínek', - 'journey.skeletons.show': 'Javaslatok megjelenítése', - 'journey.skeletons.hide': 'Javaslatok elrejtése', - 'journey.verdict.lovedIt': 'Imádtam', - 'journey.verdict.couldBeBetter': 'Lehetne jobb', - 'journey.synced.places': 'helyszín', - 'journey.synced.synced': 'szinkronizálva', - 'journey.editor.discardChangesConfirm': 'Mentetlen módosításaid vannak. Elveted?', - 'journey.editor.uploadFailed': 'A fotók feltöltése sikertelen', - 'journey.editor.uploadPhotos': 'Fotók feltöltése', - 'journey.editor.uploading': 'Feltöltés...', - 'journey.editor.uploadingProgress': 'Feltöltés {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} / {total} fotó sikertelen — mentsd el újra a próbálkozáshoz', - 'journey.editor.fromGallery': 'Galériából', - 'journey.editor.allPhotosAdded': 'Minden fotó már hozzáadva', - 'journey.editor.writeStory': 'Írd meg a történeted...', - 'journey.editor.prosCons': 'Előnyök és hátrányok', - 'journey.editor.pros': 'Előnyök', - 'journey.editor.cons': 'Hátrányok', - 'journey.editor.proPlaceholder': 'Valami remek...', - 'journey.editor.conPlaceholder': 'Nem annyira jó...', - 'journey.editor.addAnother': 'Még egy hozzáadása', - 'journey.editor.date': 'Dátum', - 'journey.editor.location': 'Helyszín', - 'journey.editor.searchLocation': 'Helyszín keresése...', - 'journey.editor.mood': 'Hangulat', - 'journey.editor.weather': 'Időjárás', - 'journey.editor.photoFirst': '1.', - 'journey.editor.makeFirst': 'Legyen az 1.', - 'journey.editor.searching': 'Keresés...', - 'journey.mood.amazing': 'Fantasztikus', - 'journey.mood.good': 'Jó', - 'journey.mood.neutral': 'Semleges', - 'journey.mood.rough': 'Nehéz', - 'journey.weather.sunny': 'Napos', - 'journey.weather.partly': 'Részben felhős', - 'journey.weather.cloudy': 'Felhős', - 'journey.weather.rainy': 'Esős', - 'journey.weather.stormy': 'Viharos', - 'journey.weather.cold': 'Havas', - 'journey.trips.linkTrip': 'Út kapcsolása', - 'journey.trips.searchTrip': 'Út keresése', - 'journey.trips.searchPlaceholder': 'Út neve vagy úti cél...', - 'journey.trips.noTripsAvailable': 'Nincsenek elérhető utak', - 'journey.trips.link': 'Kapcsolás', - 'journey.trips.tripLinked': 'Út kapcsolva', - 'journey.trips.linkFailed': 'Nem sikerült az utat kapcsolni', - 'journey.trips.addTrip': 'Út hozzáadása', - 'journey.trips.unlinkTrip': 'Út leválasztása', - 'journey.trips.unlinkMessage': 'Leválasztod a(z) „{title}" utat? Az összes szinkronizált bejegyzés és fotó véglegesen törlődik. Ez nem vonható vissza.', - 'journey.trips.unlink': 'Leválasztás', - 'journey.trips.tripUnlinked': 'Út leválasztva', - 'journey.trips.unlinkFailed': 'Nem sikerült az utat leválasztani', - 'journey.trips.noTripsLinkedSettings': 'Nincsenek kapcsolt utak', - 'journey.contributors.invite': 'Közreműködő meghívása', - 'journey.contributors.searchUser': 'Felhasználó keresése', - 'journey.contributors.searchPlaceholder': 'Felhasználónév vagy e-mail...', - 'journey.contributors.noUsers': 'Nem található felhasználó', - 'journey.contributors.role': 'Szerep', - 'journey.contributors.added': 'Közreműködő hozzáadva', - 'journey.contributors.addFailed': 'Nem sikerült hozzáadni a közreműködőt', - 'journey.share.publicShare': 'Nyilvános megosztás', - 'journey.share.createLink': 'Megosztó link létrehozása', - 'journey.share.linkCreated': 'Megosztó link létrehozva', - 'journey.share.createFailed': 'Nem sikerült létrehozni a linket', - 'journey.share.copy': 'Másolás', - 'journey.share.copied': 'Másolva!', - 'journey.share.timeline': 'Idővonal', - 'journey.share.gallery': 'Galéria', - 'journey.share.map': 'Térkép', - 'journey.share.removeLink': 'Megosztó link eltávolítása', - 'journey.share.linkDeleted': 'Megosztó link törölve', - 'journey.share.deleteFailed': 'Nem sikerült törölni', - 'journey.share.updateFailed': 'Nem sikerült frissíteni', - - // Journey — Invite - 'journey.invite.role': 'Szerepkör', - 'journey.invite.viewer': 'Megtekintő', - 'journey.invite.editor': 'Szerkesztő', - 'journey.invite.invite': 'Meghívás', - 'journey.invite.inviting': 'Meghívás...', - 'journey.settings.title': 'Útinapló beállításai', - 'journey.settings.coverImage': 'Borítókép', - 'journey.settings.changeCover': 'Borító módosítása', - 'journey.settings.addCover': 'Borítókép hozzáadása', - 'journey.settings.name': 'Név', - 'journey.settings.subtitle': 'Alcím', - 'journey.settings.subtitlePlaceholder': 'pl. Thaiföld, Vietnam és Kambodzsa', - 'journey.settings.endJourney': 'Út archiválása', - 'journey.settings.reopenJourney': 'Út visszaállítása', - 'journey.settings.archived': 'Út archiválva', - 'journey.settings.reopened': 'Út újranyitva', - 'journey.settings.endDescription': 'Elrejti az Élő jelzést. Bármikor újranyitható.', - 'journey.settings.delete': 'Törlés', - 'journey.settings.deleteJourney': 'Útinapló törlése', - 'journey.settings.deleteMessage': 'Törlöd a(z) „{title}" útinaplót? Minden bejegyzés és fotó elveszik.', - 'journey.settings.saved': 'Beállítások mentve', - 'journey.settings.saveFailed': 'Nem sikerült menteni', - 'journey.settings.coverUpdated': 'Borítókép frissítve', - 'journey.settings.coverFailed': 'A feltöltés sikertelen', - 'journey.settings.failedToDelete': 'Törlés sikertelen', - 'journey.entries.deleteTitle': 'Bejegyzés törlése', - 'journey.photosUploaded': '{count} fotó feltöltve', - 'journey.photosUploadFailed': 'Néhány fotót nem sikerült feltölteni', - 'journey.photosAdded': '{count} fotó hozzáadva', - 'journey.public.notFound': 'Nem található', - 'journey.public.notFoundMessage': 'Ez az útinapló nem létezik vagy a link lejárt.', - 'journey.public.readOnly': 'Csak olvasható · Nyilvános útinapló', - 'journey.public.tagline': 'Utazástervező és felfedező eszköz', - 'journey.public.sharedVia': 'Megosztva a következőn keresztül:', - 'journey.public.madeWith': 'Készítve a következővel:', - 'journey.pdf.journeyBook': 'Útinaplókönyv', - 'journey.pdf.madeWith': 'Készítve a TREK segítségével', - 'journey.pdf.day': 'Nap', - 'journey.pdf.theEnd': 'Vége', - 'journey.pdf.saveAsPdf': 'Mentés PDF-ként', - 'journey.pdf.pages': 'oldal', - 'journey.picker.tripPeriod': 'Utazási időszak', - 'journey.picker.dateRange': 'Időszak', - 'journey.picker.allPhotos': 'Összes fotó', - 'journey.picker.albums': 'Albumok', - 'journey.picker.selected': 'kiválasztva', - 'journey.picker.addTo': 'Hozzáadás', - 'journey.picker.newGallery': 'Új galéria', - 'journey.picker.selectAll': 'Összes kijelölése', - 'journey.picker.deselectAll': 'Összes kijelölés törlése', - 'journey.picker.noAlbums': 'Nem található album', - 'journey.picker.selectDate': 'Dátum választása', - 'journey.picker.search': 'Keresés', - 'dashboard.greeting.morning': 'Jó reggelt,', - 'dashboard.greeting.afternoon': 'Jó napot,', - 'dashboard.greeting.evening': 'Jó estét,', - 'dashboard.mobile.liveNow': 'Most élőben', - 'dashboard.mobile.tripProgress': 'Út előrehaladása', - 'dashboard.mobile.daysLeft': 'még {count} nap', - 'dashboard.mobile.places': 'Helyszínek', - 'dashboard.mobile.buddies': 'Útitársak', - 'dashboard.mobile.newTrip': 'Új út', - 'dashboard.mobile.currency': 'Pénznem', - 'dashboard.mobile.timezone': 'Időzóna', - 'dashboard.mobile.upcomingTrips': 'Közelgő utak', - 'dashboard.mobile.yourTrips': 'Utaid', - 'dashboard.mobile.trips': 'út', - 'dashboard.mobile.starts': 'Kezdés', - 'dashboard.mobile.duration': 'Időtartam', - 'dashboard.mobile.day': 'nap', - 'dashboard.mobile.days': 'nap', - 'dashboard.mobile.ongoing': 'Folyamatban', - 'dashboard.mobile.startsToday': 'Ma kezdődik', - 'dashboard.mobile.tomorrow': 'Holnap', - 'dashboard.mobile.inDays': '{count} nap múlva', - 'dashboard.mobile.inMonths': '{count} hónap múlva', - 'dashboard.mobile.completed': 'Befejezett', - 'dashboard.mobile.currencyConverter': 'Pénznemváltó', - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Beállítások', - 'nav.bottomAdmin': 'Adminisztráció', - 'nav.bottomLogout': 'Kijelentkezés', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Helyszín hozzáadása', - 'dayplan.mobile.searchPlaces': 'Helyszínek keresése...', - 'dayplan.mobile.allAssigned': 'Minden helyszín kiosztva', - 'dayplan.mobile.noMatch': 'Nincs találat', - 'dayplan.mobile.createNew': 'Új helyszín létrehozása', - 'admin.addons.catalog.journey.name': 'Útinaplók', - 'admin.addons.catalog.journey.description': 'Utazáskövetés és útinapló bejelentkezésekkel, fotókkal és napi történetekkel', - // OAuth scope groups - 'oauth.scope.group.trips': 'Utazások', - 'oauth.scope.group.places': 'Helyek', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Csomagolás', - 'oauth.scope.group.todos': 'Feladatok', - 'oauth.scope.group.budget': 'Költségvetés', - 'oauth.scope.group.reservations': 'Foglalások', - 'oauth.scope.group.collab': 'Együttműködés', - 'oauth.scope.group.notifications': 'Értesítések', - 'oauth.scope.group.vacay': 'Szabadság', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Időjárás', - 'oauth.scope.group.journey': 'Útinaplók', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Utazások és útvonalak megtekintése', - 'oauth.scope.trips:read.description': 'Utazások, napok, napi feljegyzések és tagok olvasása', - 'oauth.scope.trips:write.label': 'Utazások és útvonalak szerkesztése', - 'oauth.scope.trips:write.description': 'Utazások, napok és feljegyzések létrehozása, frissítése és tagok kezelése', - 'oauth.scope.trips:delete.label': 'Utazások törlése', - 'oauth.scope.trips:delete.description': 'Teljes utazások végleges törlése — ez a művelet visszafordíthatatlan', - 'oauth.scope.trips:share.label': 'Megosztási linkek kezelése', - 'oauth.scope.trips:share.description': 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása', - 'oauth.scope.places:read.label': 'Helyek és térképadatok megtekintése', - 'oauth.scope.places:read.description': 'Helyek, napi hozzárendelések, címkék és kategóriák olvasása', - 'oauth.scope.places:write.label': 'Helyek kezelése', - 'oauth.scope.places:write.description': 'Helyek, hozzárendelések és címkék létrehozása, frissítése és törlése', - 'oauth.scope.atlas:read.label': 'Atlas megtekintése', - 'oauth.scope.atlas:read.description': 'Meglátogatott országok, régiók és bakancslisták olvasása', - 'oauth.scope.atlas:write.label': 'Atlas kezelése', - 'oauth.scope.atlas:write.description': 'Országok és régiók meglátogatottként jelölése, bakancslisták kezelése', - 'oauth.scope.packing:read.label': 'Csomaglisták megtekintése', - 'oauth.scope.packing:read.description': 'Csomagolási tételek, táskák és kategória-hozzárendelések olvasása', - 'oauth.scope.packing:write.label': 'Csomaglisták kezelése', - 'oauth.scope.packing:write.description': 'Csomagolási tételek és táskák hozzáadása, frissítése, törlése, jelölése és átrendezése', - 'oauth.scope.todos:read.label': 'Feladatlisták megtekintése', - 'oauth.scope.todos:read.description': 'Utazás feladatai és kategória-hozzárendelések olvasása', - 'oauth.scope.todos:write.label': 'Feladatlisták kezelése', - 'oauth.scope.todos:write.description': 'Feladatok létrehozása, frissítése, jelölése, törlése és átrendezése', - 'oauth.scope.budget:read.label': 'Költségvetés megtekintése', - 'oauth.scope.budget:read.description': 'Költségvetési tételek és kiadások részletezésének olvasása', - 'oauth.scope.budget:write.label': 'Költségvetés kezelése', - 'oauth.scope.budget:write.description': 'Költségvetési tételek létrehozása, frissítése és törlése', - 'oauth.scope.reservations:read.label': 'Foglalások megtekintése', - 'oauth.scope.reservations:read.description': 'Foglalások és szállásadatok olvasása', - 'oauth.scope.reservations:write.label': 'Foglalások kezelése', - 'oauth.scope.reservations:write.description': 'Foglalások létrehozása, frissítése, törlése és átrendezése', - 'oauth.scope.collab:read.label': 'Együttműködés megtekintése', - 'oauth.scope.collab:read.description': 'Együttműködési feljegyzések, szavazások és üzenetek olvasása', - 'oauth.scope.collab:write.label': 'Együttműködés kezelése', - 'oauth.scope.collab:write.description': 'Együttműködési feljegyzések, szavazások és üzenetek létrehozása, frissítése és törlése', - 'oauth.scope.notifications:read.label': 'Értesítések megtekintése', - 'oauth.scope.notifications:read.description': 'Alkalmazáson belüli értesítések és olvasatlan számok olvasása', - 'oauth.scope.notifications:write.label': 'Értesítések kezelése', - 'oauth.scope.notifications:write.description': 'Értesítések olvasottként jelölése és válaszadás rájuk', - 'oauth.scope.vacay:read.label': 'Szabadságtervek megtekintése', - 'oauth.scope.vacay:read.description': 'Szabadságtervezési adatok, bejegyzések és statisztikák olvasása', - 'oauth.scope.vacay:write.label': 'Szabadságtervek kezelése', - 'oauth.scope.vacay:write.description': 'Szabadságbejegyzések, ünnepnapok és csapattervek létrehozása és kezelése', - 'oauth.scope.geo:read.label': 'Térképek és geokódolás', - 'oauth.scope.geo:read.description': 'Helyek keresése, térkép URL-ek feloldása és koordináták fordított geokódolása', - 'oauth.scope.weather:read.label': 'Időjárás-előrejelzések', - 'oauth.scope.weather:read.description': 'Időjárás-előrejelzések lekérése az utazási helyszínekre és dátumokra', - 'oauth.scope.journey:read.label': 'Útinaplók megtekintése', - 'oauth.scope.journey:read.description': 'Útinaplók, bejegyzések és közreműködők listájának olvasása', - 'oauth.scope.journey:write.label': 'Útinaplók kezelése', - 'oauth.scope.journey:write.description': 'Útinaplók és bejegyzéseik létrehozása, frissítése és törlése', - 'oauth.scope.journey:share.label': 'Útinapló-linkek kezelése', - 'oauth.scope.journey:share.description': 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása útinaplókhoz', - - // System notices - 'system_notice.welcome_v1.title': 'Üdvözöl a TREK', - 'system_notice.welcome_v1.body': 'Az összes az egyben utazástervező. Készítsen útvonalakat, ossza meg az utakat barátaival, és maradjon szervezett — online és offline.', - 'system_notice.welcome_v1.cta_label': 'Utazás tervezése', - 'system_notice.welcome_v1.hero_alt': 'Festői úticél TREK tervező felülettel', - 'system_notice.welcome_v1.highlight_plan': 'Napi útvonalak minden utazáshoz', - 'system_notice.welcome_v1.highlight_share': 'Együttműködés utazótársakkal', - 'system_notice.welcome_v1.highlight_offline': 'Mobilon offline is működik', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Előző értesítés', - 'system_notice.pager.next': 'Következő értesítés', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': '{n}. értesítésre ugrás', - 'system_notice.pager.position': '{current}/{total}. értesítés', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'A fotók helye megváltozott 3.0-ban', - 'system_notice.v3_photos.body': 'Az útiterv-tervező **Fényképek** lapja eltávolításra került. Fényképeid biztonságban vannak — TREK soha nem módosította Immich vagy Synology könyvtáradat.\n\nA fényképek mostantól a **Journey** bővítményben élnek. A Journey opcionális — ha még nem elérhető, kérd meg a rendszergazdát, hogy engedélyezze Admin → Bővítmények alatt.', - 'system_notice.v3_journey.title': 'Ismerje meg a Journey-t — útinnapló', - 'system_notice.v3_journey.body': 'Dokumentáld utazazsaid gazdag történetekként idővonalakkal, fotgáriákkal és interaktív térképekkel.', - 'system_notice.v3_journey.cta_label': 'Journey megnyitása', - 'system_notice.v3_journey.highlight_timeline': 'Napi idővonal és galéria', - 'system_notice.v3_journey.highlight_photos': 'Import Immich-ből vagy Synology-ból', - 'system_notice.v3_journey.highlight_share': 'Nyilvános megosztás — bejelentkezés nélkül', - 'system_notice.v3_journey.highlight_export': 'Exportálás PDF fotkönyvként', - 'system_notice.v3_features.title': 'További újdonságok a 3.0-ban', - 'system_notice.v3_features.body': 'Néhány további dolog, amit érdemes tudni erről a kiadásról.', - 'system_notice.v3_features.highlight_dashboard': 'Mobile-first irmütébla újratervezve', - 'system_notice.v3_features.highlight_offline': 'Teljes offline mód PWA-ként', - 'system_notice.v3_features.highlight_search': 'Valós idejű helykeresés-kiegészítés', - 'system_notice.v3_features.highlight_import': 'Helyek importálása KMZ/KML fájlokból', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 frissítés', - 'system_notice.v3_mcp.body': 'Az MCP integráció teljesen megújult. Az OAuth 2.1 mostantól az ajánlott hitelesítési módszer. A statikus tokenek (trek_…) elavultak és egy jövőbeli kiadásban eltávolításra kerülnek.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 ajánlott (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 részletes engedélyezési hatókör', - 'system_notice.v3_mcp.highlight_deprecated': 'Statikus trek_ tokenek elavultak', - 'system_notice.v3_mcp.highlight_tools': 'Bővített eszközkészlet és promptok', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Egy személyes gondolat tőlem', - 'system_notice.v3_thankyou.body': 'Mielőtt továbbmennél — szeretnék egy pillanatra megállni.\n\nA TREK egy hobbiprojektként indult, amit a saját utazásaimhoz építettem. Sosem gondoltam volna, hogy valami olyanná nő, amire 4000-en bízzátok a kalandjaitok tervezését. Minden csillagot, minden issue-t, minden funkciókérést — mindet elolvasom, és ezek tartanak életben a késő éjszakákon a teljes állás és az egyetem között.\n\nSzeretnétek, ha tudnátok: a TREK mindig nyílt forráskódú marad, mindig self-hosted, mindig a tiétek. Nincs nyomkövetés, nincs előfizetés, nincsenek rejtett feltételek. Csak egy eszköz, amit valaki épített, aki ugyanúgy szereti az utazást, mint ti.\n\nKülönleges köszönet [jubnl](https://github.com/jubnl)-nek — hihetetlen társsá váltál. A 3.0 nagyszerűségének nagy része a te kézjegyedet viseli. Köszönöm, hogy hittél ebben a projektben, amikor még nyers volt.\n\nÉs mindannyiótoknak, akik hibát jelentettetek, szöveget fordítottatok, megosztottátok a TREK-et egy baráttal, vagy egyszerűen csak egy utazást terveztetek vele — **köszönöm**. Ti vagytok az ok, amiért ez létezik.\n\nSok további közös kalandért.\n\n— Maurice\n\n---\n\n[Csatlakozz a közösséghez a Discordon](https://discord.gg/7Q6M6jDwzf)\n\nHa a TREK jobbá teszi az utazásaidat, egy [kis kávé](https://ko-fi.com/mauriceboe) mindig segít, hogy égve maradjanak a fények.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Szükséges beavatkozás: felhasználói fiókütközés', - 'system_notice.v3014_whitespace_collision.body': 'A 3.0.14-es frissítés egy vagy több felhasználónév- vagy e-mail-ütközést észlelt, amelyeket a tárolt értékek elején vagy végén lévő szóközök okoztak. Az érintett fiókok automatikusan át lettek nevezve. Ellenőrizze a szervernaplókat a **[migration] WHITESPACE COLLISION** kezdetű soroknál a felülvizsgálatot igénylő fiókok azonosításához.', - 'transport.addTransport': 'Közlekedés hozzáadása', - 'transport.modalTitle.create': 'Közlekedés hozzáadása', - 'transport.modalTitle.edit': 'Közlekedés szerkesztése', - 'transport.title': 'Közlekedés', - 'transport.addManual': 'Kézi közlekedés', -} - -export default hu - diff --git a/client/src/i18n/translations/id.ts b/client/src/i18n/translations/id.ts deleted file mode 100644 index f1807dc0..00000000 --- a/client/src/i18n/translations/id.ts +++ /dev/null @@ -1,2408 +0,0 @@ -const id: Record = { - // Common - 'common.save': 'Simpan', - 'common.showMore': 'Tampilkan lebih banyak', - 'common.showLess': 'Tampilkan lebih sedikit', - 'common.cancel': 'Batal', - 'common.clear': 'Hapus', - 'common.delete': 'Hapus', - 'common.edit': 'Sunting', - 'common.add': 'Tambah', - 'common.loading': 'Memuat...', - 'common.import': 'Impor', - 'common.select': 'Pilih', - 'common.selectAll': 'Pilih semua', - 'common.deselectAll': 'Batalkan semua pilihan', - 'common.error': 'Kesalahan', - 'common.unknownError': 'Kesalahan tidak diketahui', - 'common.tooManyAttempts': 'Terlalu banyak percobaan. Coba lagi nanti.', - 'common.back': 'Kembali', - 'common.all': 'Semua', - 'common.close': 'Tutup', - 'common.open': 'Buka', - 'common.upload': 'Unggah', - 'common.search': 'Cari', - 'common.confirm': 'Konfirmasi', - 'common.ok': 'OK', - 'common.yes': 'Ya', - 'common.no': 'Tidak', - 'common.or': 'atau', - 'common.none': 'Tidak ada', - 'common.date': 'Tanggal', - 'common.rename': 'Ganti nama', - 'common.discardChanges': 'Buang perubahan', - 'common.discard': 'Buang', - 'common.name': 'Nama', - 'common.email': 'Email', - 'common.password': 'Kata sandi', - 'common.saving': 'Menyimpan...', - 'common.justNow': 'baru saja', - 'common.hoursAgo': '{count}j lalu', - 'common.daysAgo': '{count}h lalu', - 'common.saved': 'Tersimpan', - 'trips.memberRemoved': '{username} dihapus', - 'trips.memberRemoveError': 'Gagal menghapus', - 'trips.memberAdded': '{username} ditambahkan', - 'trips.memberAddError': 'Gagal menambahkan', - 'trips.reminder': 'Pengingat', - 'trips.reminderNone': 'Tidak ada', - 'trips.reminderDay': 'hari', - 'trips.reminderDays': 'hari', - 'trips.reminderCustom': 'Kustom', - 'trips.reminderDaysBefore': 'hari sebelum keberangkatan', - 'trips.reminderDisabledHint': 'Pengingat perjalanan dinonaktifkan. Aktifkan di Admin > Pengaturan > Notifikasi.', - 'common.update': 'Perbarui', - 'common.change': 'Ubah', - 'common.uploading': 'Mengunggah…', - 'common.backToPlanning': 'Kembali ke Perencanaan', - 'common.reset': 'Atur ulang', - 'common.expand': 'Perluas', - 'common.collapse': 'Ciutkan', - - // Navbar - 'nav.trip': 'Perjalanan', - 'nav.share': 'Bagikan', - 'nav.settings': 'Pengaturan', - 'nav.admin': 'Admin', - 'nav.logout': 'Keluar', - 'nav.lightMode': 'Mode Terang', - 'nav.darkMode': 'Mode Gelap', - 'nav.autoMode': 'Mode Otomatis', - 'nav.administrator': 'Administrator', - - // Dashboard - 'dashboard.title': 'Perjalananku', - 'dashboard.subtitle.loading': 'Memuat perjalanan...', - 'dashboard.subtitle.trips': '{count} perjalanan ({archived} diarsipkan)', - 'dashboard.subtitle.empty': 'Mulai perjalanan pertamamu', - 'dashboard.subtitle.activeOne': '{count} perjalanan aktif', - 'dashboard.subtitle.activeMany': '{count} perjalanan aktif', - 'dashboard.subtitle.archivedSuffix': ' · {count} diarsipkan', - 'dashboard.newTrip': 'Perjalanan Baru', - 'dashboard.gridView': 'Tampilan grid', - 'dashboard.listView': 'Tampilan daftar', - 'dashboard.currency': 'Mata uang', - 'dashboard.timezone': 'Zona waktu', - 'dashboard.localTime': 'Lokal', - 'dashboard.timezoneCustomTitle': 'Zona Waktu Kustom', - 'dashboard.timezoneCustomLabelPlaceholder': 'Label (opsional)', - 'dashboard.timezoneCustomTzPlaceholder': 'mis. America/New_York', - 'dashboard.timezoneCustomAdd': 'Tambah', - 'dashboard.timezoneCustomErrorEmpty': 'Masukkan pengenal zona waktu', - 'dashboard.timezoneCustomErrorInvalid': 'Zona waktu tidak valid. Gunakan format seperti Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Sudah ditambahkan', - 'dashboard.emptyTitle': 'Belum ada perjalanan', - 'dashboard.emptyText': 'Buat perjalanan pertamamu dan mulai merencanakan!', - 'dashboard.emptyButton': 'Buat Perjalanan Pertama', - 'dashboard.nextTrip': 'Perjalanan Berikutnya', - 'dashboard.shared': 'Dibagikan', - 'dashboard.sharedBy': 'Dibagikan oleh {name}', - 'dashboard.days': 'Hari', - 'dashboard.places': 'Tempat', - 'dashboard.members': 'Teman perjalanan', - 'dashboard.archive': 'Arsipkan', - 'dashboard.copyTrip': 'Salin', - 'dashboard.copySuffix': 'salinan', - 'dashboard.restore': 'Pulihkan', - 'dashboard.archived': 'Diarsipkan', - 'dashboard.status.ongoing': 'Sedang berlangsung', - 'dashboard.status.today': 'Hari ini', - 'dashboard.status.tomorrow': 'Besok', - 'dashboard.status.past': 'Sudah lewat', - 'dashboard.status.daysLeft': '{count} hari lagi', - 'dashboard.toast.loadError': 'Gagal memuat perjalanan', - 'dashboard.toast.created': 'Perjalanan berhasil dibuat!', - 'dashboard.toast.createError': 'Gagal membuat perjalanan', - 'dashboard.toast.updated': 'Perjalanan diperbarui!', - 'dashboard.toast.updateError': 'Gagal memperbarui perjalanan', - 'dashboard.toast.deleted': 'Perjalanan dihapus', - 'dashboard.toast.deleteError': 'Gagal menghapus perjalanan', - 'dashboard.toast.archived': 'Perjalanan diarsipkan', - 'dashboard.toast.archiveError': 'Gagal mengarsipkan perjalanan', - 'dashboard.toast.restored': 'Perjalanan dipulihkan', - 'dashboard.toast.restoreError': 'Gagal memulihkan perjalanan', - 'dashboard.toast.copied': 'Perjalanan disalin!', - 'dashboard.toast.copyError': 'Gagal menyalin perjalanan', - 'dashboard.confirm.delete': 'Hapus perjalanan "{title}"? Semua tempat dan rencana akan dihapus permanen.', - 'dashboard.editTrip': 'Edit Perjalanan', - 'dashboard.createTrip': 'Buat Perjalanan Baru', - 'dashboard.tripTitle': 'Judul', - 'dashboard.tripTitlePlaceholder': 'mis. Musim Panas di Jepang', - 'dashboard.tripDescription': 'Deskripsi', - 'dashboard.tripDescriptionPlaceholder': 'Perjalanan ini tentang apa?', - 'dashboard.startDate': 'Tanggal Mulai', - 'dashboard.endDate': 'Tanggal Selesai', - 'dashboard.dayCount': 'Jumlah Hari', - 'dashboard.dayCountHint': 'Berapa hari yang ingin direncanakan jika tanggal perjalanan belum diatur.', - 'dashboard.noDateHint': 'Belum ada tanggal — 7 hari default akan dibuat. Bisa diubah kapan saja.', - 'dashboard.coverImage': 'Gambar Sampul', - 'dashboard.addCoverImage': 'Tambah gambar sampul (atau seret & lepas)', - 'dashboard.addMembers': 'Teman perjalanan', - 'dashboard.addMember': 'Tambah anggota', - 'dashboard.coverSaved': 'Gambar sampul tersimpan', - 'dashboard.coverUploadError': 'Gagal mengunggah', - 'dashboard.coverRemoveError': 'Gagal menghapus', - 'dashboard.titleRequired': 'Judul wajib diisi', - 'dashboard.endDateError': 'Tanggal selesai harus setelah tanggal mulai', - - // Settings - 'settings.title': 'Pengaturan', - 'settings.subtitle': 'Atur pengaturan pribadimu', - 'settings.tabs.display': 'Tampilan', - 'settings.tabs.map': 'Peta', - 'settings.tabs.notifications': 'Notifikasi', - 'settings.tabs.integrations': 'Integrasi', - 'settings.tabs.account': 'Akun', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Tentang', - 'settings.map': 'Peta', - 'settings.mapTemplate': 'Template Peta', - 'settings.mapTemplatePlaceholder.select': 'Pilih template...', - 'settings.mapDefaultHint': 'Kosongkan untuk OpenStreetMap (default)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'Template URL untuk tile peta', - 'settings.mapProvider': 'Penyedia peta', - 'settings.mapProviderHint': 'Berlaku untuk peta Trip Planner dan Journey. Atlas selalu menggunakan Leaflet.', - 'settings.mapLeafletSubtitle': 'Klasik 2D, tile raster apa pun', - 'settings.mapMapboxSubtitle': 'Tile vektor, bangunan 3D & medan', - 'settings.mapExperimental': 'Eksperimental', - 'settings.mapMapboxToken': 'Token akses Mapbox', - 'settings.mapMapboxTokenHint': 'Token publik (pk.*) dari', - 'settings.mapMapboxTokenLink': 'mapbox.com → Token akses', - 'settings.mapStyle': 'Gaya peta', - 'settings.mapStylePlaceholder': 'Pilih gaya Mapbox', - 'settings.mapStyleHint': 'Preset atau URL mapbox://styles/USER/ID milikmu', - 'settings.map3dBuildings': 'Bangunan 3D & medan', - 'settings.map3dHint': 'Kemiringan + ekstrusi bangunan 3D nyata — bekerja di semua gaya, termasuk satelit.', - 'settings.mapHighQuality': 'Mode kualitas tinggi', - 'settings.mapHighQualityHint': 'Antialiasing + proyeksi globe untuk tepi yang lebih tajam dan tampilan dunia realistis.', - 'settings.mapHighQualityWarning': 'Dapat memengaruhi performa pada perangkat kelas bawah.', - 'settings.mapTipLabel': 'Tip:', - 'settings.mapTip': 'Klik kanan dan seret untuk memutar/memiringkan peta. Klik tengah untuk menambah tempat (klik kanan untuk rotasi).', - 'settings.latitude': 'Lintang', - 'settings.longitude': 'Bujur', - 'settings.saveMap': 'Simpan Peta', - 'settings.apiKeys': 'API Keys', - 'settings.mapsKey': 'Google Maps API Key', - 'settings.mapsKeyHint': 'Untuk pencarian tempat. Memerlukan Places API (New). Dapatkan di console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API Key', - 'settings.weatherKeyHint': 'Untuk data cuaca. Gratis di openweathermap.org/api', - 'settings.keyPlaceholder': 'Masukkan key...', - 'settings.configured': 'Sudah dikonfigurasi', - 'settings.saveKeys': 'Simpan Keys', - 'settings.display': 'Tampilan', - 'settings.colorMode': 'Mode Warna', - 'settings.light': 'Terang', - 'settings.dark': 'Gelap', - 'settings.auto': 'Otomatis', - 'settings.language': 'Bahasa', - 'settings.temperature': 'Satuan Suhu', - 'settings.timeFormat': 'Format Waktu', - 'settings.blurBookingCodes': 'Sembunyikan Kode Pemesanan', - 'settings.notifications': 'Notifikasi', - 'settings.notifyTripInvite': 'Undangan perjalanan', - 'settings.notifyBookingChange': 'Perubahan pemesanan', - 'settings.notifyTripReminder': 'Pengingat perjalanan', - 'settings.notifyTodoDue': 'Tugas jatuh tempo', - 'settings.notifyVacayInvite': 'Undangan Vacay fusion', - 'settings.notifyPhotosShared': 'Foto dibagikan (Immich)', - 'settings.notifyCollabMessage': 'Pesan chat (Collab)', - 'settings.notifyPackingTagged': 'Daftar bawaan: penugasan', - 'settings.notifyWebhook': 'Notifikasi webhook', - 'settings.notifyVersionAvailable': 'Versi baru tersedia', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': 'Belum ada saluran notifikasi yang dikonfigurasi. Minta admin untuk mengatur notifikasi email atau webhook.', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Masukkan URL webhook Discord, Slack, atau kustom untuk menerima notifikasi.', - 'settings.webhookUrl.saved': 'Webhook URL tersimpan', - 'settings.webhookUrl.test': 'Uji', - 'settings.webhookUrl.testSuccess': 'Test webhook berhasil dikirim', - 'settings.webhookUrl.testFailed': 'Test webhook gagal', - 'settings.ntfyUrl.topicLabel': 'Topik Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL Server Ntfy (opsional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Masukkan topik Ntfy Anda untuk menerima notifikasi push. Kosongkan bidang server untuk menggunakan default yang dikonfigurasi oleh admin Anda.', - 'settings.ntfyUrl.tokenLabel': 'Token Akses (opsional)', - 'settings.ntfyUrl.tokenHint': 'Diperlukan untuk topik yang dilindungi kata sandi.', - 'settings.ntfyUrl.saved': 'Pengaturan Ntfy tersimpan', - 'settings.ntfyUrl.test': 'Uji', - 'settings.ntfyUrl.testSuccess': 'Notifikasi uji Ntfy berhasil dikirim', - 'settings.ntfyUrl.testFailed': 'Notifikasi uji Ntfy gagal', - 'settings.ntfyUrl.tokenCleared': 'Token akses dihapus', - 'admin.notifications.title': 'Notifikasi', - 'admin.notifications.hint': 'Pilih satu saluran notifikasi. Hanya satu yang bisa aktif sekaligus.', - 'admin.notifications.none': 'Dinonaktifkan', - 'admin.notifications.email': 'Email (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Simpan pengaturan notifikasi', - 'admin.notifications.saved': 'Pengaturan notifikasi tersimpan', - 'admin.notifications.testWebhook': 'Kirim test webhook', - 'admin.notifications.testWebhookSuccess': 'Test webhook berhasil dikirim', - 'admin.notifications.testWebhookFailed': 'Test webhook gagal', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Notifikasi in-app selalu aktif dan tidak bisa dinonaktifkan secara global.', - 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', - 'admin.notifications.adminWebhookPanel.hint': 'Webhook ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari webhook per pengguna dan selalu berjalan jika diatur.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL tersimpan', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook berhasil dikirim', - 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook gagal', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook selalu berjalan jika URL dikonfigurasi', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Memungkinkan pengguna mengonfigurasi topik ntfy mereka sendiri untuk notifikasi push. Tetapkan server default di bawah untuk mengisi setelan pengguna secara otomatis.', - 'admin.notifications.testNtfy': 'Kirim uji Ntfy', - 'admin.notifications.testNtfySuccess': 'Uji Ntfy berhasil dikirim', - 'admin.notifications.testNtfyFailed': 'Uji Ntfy gagal', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Topik Ntfy ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari topik per pengguna dan selalu berjalan jika dikonfigurasi.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL Server Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Juga digunakan sebagai server default untuk notifikasi ntfy pengguna. Kosongkan untuk menggunakan ntfy.sh. Pengguna dapat menggantinya di pengaturan mereka sendiri.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Topik Admin', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token Akses (opsional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Token akses admin dihapus', - 'admin.notifications.adminNtfyPanel.saved': 'Pengaturan Ntfy admin tersimpan', - 'admin.notifications.adminNtfyPanel.test': 'Kirim uji Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Uji Ntfy berhasil dikirim', - 'admin.notifications.adminNtfyPanel.testFailed': 'Uji Ntfy gagal', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy selalu berjalan jika topik dikonfigurasi', - 'admin.notifications.adminNotificationsHint': 'Atur saluran mana yang mengirimkan notifikasi khusus admin (mis. peringatan versi).', - 'admin.notifications.tripReminders.title': 'Pengingat Perjalanan', - 'admin.notifications.tripReminders.hint': 'Mengirim notifikasi pengingat sebelum perjalanan dimulai (memerlukan hari pengingat yang diatur pada perjalanan).', - 'admin.notifications.tripReminders.enabled': 'Pengingat perjalanan diaktifkan', - 'admin.notifications.tripReminders.disabled': 'Pengingat perjalanan dinonaktifkan', - 'admin.smtp.title': 'Email & Notifikasi', - 'admin.smtp.hint': 'Konfigurasi SMTP untuk pengiriman notifikasi email.', - 'admin.smtp.testButton': 'Kirim email uji', - 'admin.webhook.hint': 'Izinkan pengguna mengatur URL webhook sendiri untuk notifikasi (Discord, Slack, dll.).', - 'admin.smtp.testSuccess': 'Email uji berhasil dikirim', - 'admin.smtp.testFailed': 'Email uji gagal', - 'settings.notificationsDisabled': 'Notifikasi belum dikonfigurasi. Minta admin untuk mengaktifkan notifikasi email atau webhook.', - 'settings.notificationsActive': 'Saluran aktif', - 'settings.notificationsManagedByAdmin': 'Acara notifikasi dikonfigurasi oleh administrator.', - 'dayplan.icsTooltip': 'Ekspor kalender (ICS)', - 'share.linkTitle': 'Tautan Publik', - 'share.linkHint': 'Buat tautan yang bisa digunakan siapa saja untuk melihat perjalanan ini tanpa masuk. Hanya baca — tidak bisa diedit.', - 'share.createLink': 'Buat tautan', - 'share.deleteLink': 'Hapus tautan', - 'share.createError': 'Gagal membuat tautan', - 'common.copy': 'Salin', - 'common.copied': 'Disalin', - 'share.permMap': 'Peta & Rencana', - 'share.permBookings': 'Pemesanan', - 'share.permPacking': 'Bawaan', - 'shared.expired': 'Tautan kedaluwarsa atau tidak valid', - 'shared.expiredHint': 'Tautan perjalanan bersama ini tidak lagi aktif.', - 'shared.readOnly': 'Tampilan bersama — hanya baca', - 'shared.tabPlan': 'Rencana', - 'shared.tabBookings': 'Pemesanan', - 'shared.tabPacking': 'Bawaan', - 'shared.tabBudget': 'Anggaran', - 'shared.tabChat': 'Chat', - 'shared.days': 'hari', - 'shared.places': 'tempat', - 'shared.other': 'Lainnya', - 'shared.totalBudget': 'Total Anggaran', - 'shared.messages': 'pesan', - 'shared.sharedVia': 'Dibagikan via', - 'shared.confirmed': 'Dikonfirmasi', - 'shared.pending': 'Menunggu', - 'share.permBudget': 'Anggaran', - 'share.permCollab': 'Chat', - 'settings.on': 'Aktif', - 'settings.off': 'Nonaktif', - 'settings.mcp.title': 'Konfigurasi MCP', - 'settings.mcp.endpoint': 'MCP Endpoint', - 'settings.mcp.clientConfig': 'Konfigurasi Client', - 'settings.mcp.clientConfigHint': 'Ganti dengan API token dari daftar di bawah. Path ke npx mungkin perlu disesuaikan untuk sistemmu (mis. C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Ganti dan dengan kredensial dari klien OAuth 2.1 yang kamu buat di atas. mcp-remote akan membuka browser untuk menyelesaikan otorisasi pertama kali kamu terhubung. Path ke npx mungkin perlu disesuaikan untuk sistemmu (misalnya C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).', - 'settings.mcp.copy': 'Salin', - 'settings.mcp.copied': 'Disalin!', - 'settings.mcp.apiTokens': 'API Tokens', - 'settings.mcp.createToken': 'Buat Token Baru', - 'settings.mcp.noTokens': 'Belum ada token. Buat satu untuk menghubungkan MCP client.', - 'settings.mcp.tokenCreatedAt': 'Dibuat', - 'settings.mcp.tokenUsedAt': 'Digunakan', - 'settings.mcp.deleteTokenTitle': 'Hapus Token', - 'settings.mcp.deleteTokenMessage': 'Token ini akan langsung berhenti bekerja. MCP client yang menggunakannya akan kehilangan akses.', - 'settings.mcp.modal.createTitle': 'Buat API Token', - 'settings.mcp.modal.tokenName': 'Nama Token', - 'settings.mcp.modal.tokenNamePlaceholder': 'mis. Claude Desktop, Laptop kerja', - 'settings.mcp.modal.creating': 'Membuat…', - 'settings.mcp.modal.create': 'Buat Token', - 'settings.mcp.modal.createdTitle': 'Token Dibuat', - 'settings.mcp.modal.createdWarning': 'Token ini hanya ditampilkan sekali. Salin dan simpan sekarang — tidak bisa dipulihkan.', - 'settings.mcp.modal.done': 'Selesai', - 'settings.mcp.toast.created': 'Token dibuat', - 'settings.mcp.toast.createError': 'Gagal membuat token', - 'settings.mcp.toast.deleted': 'Token dihapus', - 'settings.mcp.toast.deleteError': 'Gagal menghapus token', - 'settings.mcp.apiTokensDeprecated': 'API Token sudah tidak digunakan dan akan dihapus di rilis mendatang. Gunakan OAuth 2.1 Client sebagai gantinya.', - 'settings.oauth.clients': 'Klien OAuth 2.1', - 'settings.oauth.clientsHint': 'Daftarkan klien OAuth 2.1 agar aplikasi MCP pihak ketiga (Claude Web, Cursor, dll.) dapat terhubung tanpa token statis.', - 'settings.oauth.createClient': 'Klien Baru', - 'settings.oauth.noClients': 'Belum ada klien OAuth yang terdaftar.', - 'settings.oauth.clientId': 'ID Klien', - 'settings.oauth.clientSecret': 'Rahasia Klien', - 'settings.oauth.deleteClient': 'Hapus Klien', - 'settings.oauth.deleteClientMessage': 'Klien ini dan semua sesi aktif akan dihapus permanen. Aplikasi yang menggunakannya akan langsung kehilangan akses.', - 'settings.oauth.rotateSecret': 'Putar Ulang Secret', - 'settings.oauth.rotateSecretMessage': 'Secret klien baru akan dibuat dan semua sesi yang ada langsung dibatalkan. Perbarui aplikasimu sebelum menutup dialog ini.', - 'settings.oauth.rotateSecretConfirm': 'Putar Ulang', - 'settings.oauth.rotateSecretConfirming': 'Memutar ulang…', - 'settings.oauth.rotateSecretDoneTitle': 'Secret Baru Dibuat', - 'settings.oauth.rotateSecretDoneWarning': 'Secret ini hanya ditampilkan sekali. Salin sekarang dan perbarui aplikasimu — semua sesi sebelumnya telah dibatalkan.', - 'settings.oauth.activeSessions': 'Sesi OAuth Aktif', - 'settings.oauth.sessionScopes': 'Cakupan', - 'settings.oauth.sessionExpires': 'Kedaluwarsa', - 'settings.oauth.revoke': 'Cabut', - 'settings.oauth.revokeSession': 'Cabut Sesi', - 'settings.oauth.revokeSessionMessage': 'Ini akan segera mencabut akses untuk sesi OAuth ini.', - 'settings.oauth.modal.createTitle': 'Daftarkan OAuth Client', - 'settings.oauth.modal.presets': 'Preset cepat', - 'settings.oauth.modal.clientName': 'Nama Aplikasi', - 'settings.oauth.modal.clientNamePlaceholder': 'mis. Claude Web, Aplikasi MCP Saya', - 'settings.oauth.modal.redirectUris': 'Redirect URI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://aplikasiku.com/callback\nhttps://aplikasiku.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Satu URI per baris. HTTPS wajib (localhost dikecualikan). Kecocokan tepat diberlakukan.', - 'settings.oauth.modal.scopes': 'Cakupan yang Diizinkan', - 'settings.oauth.modal.scopesHint': 'list_trips dan get_trip_summary selalu tersedia — tidak perlu cakupan. Keduanya memungkinkan AI menemukan ID perjalanan yang diperlukan untuk menggunakan alat lainnya.', - 'settings.oauth.modal.selectAll': 'Pilih semua', - 'settings.oauth.modal.deselectAll': 'Batalkan semua', - 'settings.oauth.modal.creating': 'Mendaftarkan…', - 'settings.oauth.modal.create': 'Daftarkan Client', - 'settings.oauth.modal.createdTitle': 'Client Terdaftar', - 'settings.oauth.modal.createdWarning': 'Client secret hanya ditampilkan sekali. Salin sekarang — tidak dapat dipulihkan.', - 'settings.oauth.toast.createError': 'Gagal mendaftarkan klien OAuth', - 'settings.oauth.toast.deleted': 'Klien OAuth dihapus', - 'settings.oauth.toast.deleteError': 'Gagal menghapus klien OAuth', - 'settings.oauth.toast.revoked': 'Sesi dicabut', - 'settings.oauth.toast.revokeError': 'Gagal mencabut sesi', - 'settings.oauth.toast.rotateError': 'Gagal memutar ulang client secret', - 'settings.oauth.modal.machineClient': 'Klien mesin (tanpa login browser)', - 'settings.oauth.modal.machineClientHint': 'Menggunakan grant client_credentials — tidak perlu URI pengalihan. Token diterbitkan langsung melalui client_id + client_secret dan bertindak sebagai Anda dalam cakupan yang dipilih.', - 'settings.oauth.modal.machineClientUsage': 'Dapatkan token: POST /oauth/token dengan grant_type=client_credentials, client_id, dan client_secret. Tanpa browser, tanpa refresh token.', - 'settings.oauth.badge.machine': 'mesin', - 'settings.account': 'Akun', - 'settings.about': 'Tentang', - 'settings.about.reportBug': 'Laporkan Bug', - 'settings.about.reportBugHint': 'Menemukan masalah? Beri tahu kami', - 'settings.about.featureRequest': 'Permintaan Fitur', - 'settings.about.featureRequestHint': 'Sarankan fitur baru', - 'settings.about.wikiHint': 'Dokumentasi & panduan', - 'settings.about.supporters.badge': 'Pendukung Bulanan', - 'settings.about.supporters.title': 'Rekan perjalanan untuk TREK', - 'settings.about.supporters.subtitle': 'Saat kamu merencanakan rute berikutnya, orang-orang ini ikut merencanakan masa depan TREK. Kontribusi bulanan mereka langsung masuk ke pengembangan dan jam kerja nyata — supaya TREK tetap Open Source.', - 'settings.about.supporters.since': 'pendukung sejak {date}', - 'settings.about.supporters.tierEmpty': 'Jadilah yang pertama', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK adalah perencana perjalanan self-hosted yang membantu kamu mengatur perjalanan dari ide pertama hingga kenangan terakhir. Perencanaan harian, anggaran, daftar bawaan, foto dan masih banyak lagi — semua di satu tempat, di servermu sendiri.', - 'settings.about.madeWith': 'Dibuat dengan', - 'settings.about.madeBy': 'oleh Maurice dan komunitas open-source yang terus berkembang.', - 'settings.username': 'Nama pengguna', - 'settings.email': 'Email', - 'settings.role': 'Peran', - 'settings.roleAdmin': 'Administrator', - 'settings.oidcLinked': 'Terhubung dengan', - 'settings.changePassword': 'Ganti Kata Sandi', - 'settings.currentPassword': 'Kata sandi saat ini', - 'settings.currentPasswordRequired': 'Kata sandi saat ini wajib diisi', - 'settings.newPassword': 'Kata sandi baru', - 'settings.confirmPassword': 'Konfirmasi kata sandi baru', - 'settings.updatePassword': 'Perbarui kata sandi', - 'settings.passwordRequired': 'Masukkan kata sandi saat ini dan kata sandi baru', - 'settings.passwordTooShort': 'Kata sandi minimal 8 karakter', - 'settings.passwordMismatch': 'Kata sandi tidak cocok', - 'settings.passwordWeak': 'Kata sandi harus mengandung huruf besar, huruf kecil, angka, dan karakter khusus', - 'settings.passwordChanged': 'Kata sandi berhasil diubah', - 'settings.mustChangePassword': 'Kamu harus mengubah kata sandi sebelum melanjutkan. Atur kata sandi baru di bawah ini.', - 'settings.deleteAccount': 'Hapus akun', - 'settings.deleteAccountTitle': 'Hapus akunmu?', - 'settings.deleteAccountWarning': 'Akunmu beserta semua perjalanan, tempat, dan file akan dihapus permanen. Tindakan ini tidak bisa dibatalkan.', - 'settings.deleteAccountConfirm': 'Hapus permanen', - 'settings.deleteBlockedTitle': 'Penghapusan tidak memungkinkan', - 'settings.deleteBlockedMessage': 'Kamu satu-satunya administrator. Promosikan pengguna lain menjadi admin sebelum menghapus akunmu.', - 'settings.roleUser': 'Pengguna', - 'settings.saveProfile': 'Simpan Profil', - 'settings.toast.mapSaved': 'Pengaturan peta tersimpan', - 'settings.toast.keysSaved': 'API keys tersimpan', - 'settings.toast.displaySaved': 'Pengaturan tampilan tersimpan', - 'settings.toast.profileSaved': 'Profil tersimpan', - 'settings.uploadAvatar': 'Unggah Foto Profil', - 'settings.removeAvatar': 'Hapus Foto Profil', - 'settings.avatarUploaded': 'Foto profil diperbarui', - 'settings.avatarRemoved': 'Foto profil dihapus', - 'settings.avatarError': 'Gagal mengunggah', - 'settings.mfa.title': 'Autentikasi dua faktor (2FA)', - 'settings.mfa.description': 'Menambahkan langkah kedua saat masuk dengan email dan kata sandi. Gunakan aplikasi autentikator (Google Authenticator, Authy, dll.).', - 'settings.mfa.requiredByPolicy': 'Administrator mengharuskan autentikasi dua faktor. Atur aplikasi autentikator di bawah ini sebelum melanjutkan.', - 'settings.mfa.backupTitle': 'Kode cadangan', - 'settings.mfa.backupDescription': 'Gunakan kode cadangan sekali pakai ini jika kamu kehilangan akses ke aplikasi autentikator.', - 'settings.mfa.backupWarning': 'Simpan kode ini sekarang. Setiap kode hanya bisa digunakan sekali.', - 'settings.mfa.backupCopy': 'Salin kode', - 'settings.mfa.backupDownload': 'Unduh TXT', - 'settings.mfa.backupPrint': 'Cetak / PDF', - 'settings.mfa.backupCopied': 'Kode cadangan disalin', - 'settings.mfa.enabled': '2FA aktif di akunmu.', - 'settings.mfa.disabled': '2FA belum diaktifkan.', - 'settings.mfa.setup': 'Atur autentikator', - 'settings.mfa.scanQr': 'Pindai kode QR ini dengan aplikasimu, atau masukkan secret secara manual.', - 'settings.mfa.secretLabel': 'Kunci secret (entri manual)', - 'settings.mfa.codePlaceholder': 'Kode 6 digit', - 'settings.mfa.enable': 'Aktifkan 2FA', - 'settings.mfa.cancelSetup': 'Batal', - 'settings.mfa.disableTitle': 'Nonaktifkan 2FA', - 'settings.mfa.disableHint': 'Masukkan kata sandi akun dan kode terkini dari aplikasi autentikatormu.', - 'settings.mfa.disable': 'Nonaktifkan 2FA', - 'settings.mfa.toastEnabled': 'Autentikasi dua faktor diaktifkan', - 'settings.mfa.toastDisabled': 'Autentikasi dua faktor dinonaktifkan', - 'settings.mfa.demoBlocked': 'Tidak tersedia dalam mode demo', - - // Login - 'login.error': 'Login gagal. Periksa kembali kredensial kamu.', - 'login.tagline': 'Perjalananmu.\nRencanamu.', - 'login.description': 'Rencanakan perjalanan bersama dengan peta interaktif, anggaran, dan sinkronisasi real-time.', - 'login.features.maps': 'Peta Interaktif', - 'login.features.mapsDesc': 'Google Places, rute & pengelompokan', - 'login.features.realtime': 'Sinkronisasi Real-Time', - 'login.features.realtimeDesc': 'Rencanakan bersama via WebSocket', - 'login.features.budget': 'Pelacak Anggaran', - 'login.features.budgetDesc': 'Kategori, grafik & biaya per orang', - 'login.features.collab': 'Kolaborasi', - 'login.features.collabDesc': 'Multi-pengguna dengan perjalanan bersama', - 'login.features.packing': 'Daftar Packing', - 'login.features.packingDesc': 'Kategori, progres & saran', - 'login.features.bookings': 'Reservasi', - 'login.features.bookingsDesc': 'Penerbangan, hotel, restoran & lainnya', - 'login.features.files': 'Dokumen', - 'login.features.filesDesc': 'Unggah & kelola dokumen', - 'login.features.routes': 'Rute Cerdas', - 'login.features.routesDesc': 'Optimasi otomatis & ekspor ke Google Maps', - 'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Datamu tetap milikmu', - 'login.title': 'Masuk', - 'login.subtitle': 'Selamat datang kembali', - 'login.signingIn': 'Sedang masuk…', - 'login.signIn': 'Masuk', - 'login.createAdmin': 'Buat Akun Admin', - 'login.createAdminHint': 'Siapkan akun admin pertama untuk TREK.', - 'login.setNewPassword': 'Atur Kata Sandi Baru', - 'login.setNewPasswordHint': 'Kamu harus mengganti kata sandi sebelum melanjutkan.', - 'login.createAccount': 'Buat Akun', - 'login.createAccountHint': 'Daftarkan akun baru.', - 'login.creating': 'Membuat…', - 'login.noAccount': 'Belum punya akun?', - 'login.hasAccount': 'Sudah punya akun?', - 'login.register': 'Daftar', - 'login.emailPlaceholder': 'kamu@email.com', - 'login.username': 'Nama pengguna', - 'login.oidc.registrationDisabled': 'Pendaftaran dinonaktifkan. Hubungi administrator kamu.', - 'login.oidc.noEmail': 'Tidak ada email yang diterima dari penyedia.', - 'login.oidc.tokenFailed': 'Autentikasi gagal.', - 'login.oidc.invalidState': 'Sesi tidak valid. Coba lagi.', - 'login.demoFailed': 'Login demo gagal', - 'login.oidcSignIn': 'Masuk dengan {name}', - 'login.oidcOnly': 'Autentikasi kata sandi dinonaktifkan. Masuk menggunakan penyedia SSO kamu.', - 'login.oidcLoggedOut': 'Kamu telah keluar. Masuk kembali menggunakan penyedia SSO kamu.', - 'login.demoHint': 'Coba demo — tidak perlu registrasi', - 'login.mfaTitle': 'Autentikasi dua faktor', - 'login.mfaSubtitle': 'Masukkan kode 6 digit dari aplikasi autentikator kamu.', - 'login.mfaCodeLabel': 'Kode verifikasi', - 'login.mfaCodeRequired': 'Masukkan kode dari aplikasi autentikator kamu.', - 'login.mfaHint': 'Buka Google Authenticator, Authy, atau aplikasi TOTP lainnya.', - 'login.mfaBack': '← Kembali ke halaman masuk', - 'login.mfaVerify': 'Verifikasi', - 'login.invalidInviteLink': 'Tautan undangan tidak valid atau sudah kedaluwarsa', - 'login.oidcFailed': 'Login OIDC gagal', - 'login.usernameRequired': 'Nama pengguna wajib diisi', - 'login.passwordMinLength': 'Kata sandi minimal 8 karakter', - 'login.forgotPassword': 'Lupa kata sandi?', - 'login.forgotPasswordTitle': 'Setel ulang kata sandi', - 'login.forgotPasswordBody': 'Masukkan alamat email akunmu. Jika akun ada, kami akan mengirim tautan reset.', - 'login.forgotPasswordSubmit': 'Kirim tautan', - 'login.forgotPasswordSentTitle': 'Periksa email kamu', - 'login.forgotPasswordSentBody': 'Jika ada akun dengan email tersebut, tautannya sedang dikirim. Berlaku 60 menit.', - 'login.forgotPasswordSmtpHintOff': 'Catatan: administrator belum mengonfigurasi SMTP, jadi tautan reset akan ditulis ke konsol server alih-alih dikirim lewat email.', - 'login.backToLogin': 'Kembali ke login', - 'login.newPassword': 'Kata sandi baru', - 'login.confirmPassword': 'Konfirmasi kata sandi baru', - 'login.passwordsDontMatch': 'Kata sandi tidak cocok', - 'login.mfaCode': 'Kode 2FA', - 'login.resetPasswordTitle': 'Tetapkan kata sandi baru', - 'login.resetPasswordBody': 'Pilih kata sandi kuat yang belum pernah kamu pakai di sini. Minimal 8 karakter.', - 'login.resetPasswordMfaBody': 'Masukkan kode 2FA atau kode cadangan untuk menyelesaikan reset.', - 'login.resetPasswordSubmit': 'Setel ulang kata sandi', - 'login.resetPasswordVerify': 'Verifikasi & setel ulang', - 'login.resetPasswordSuccessTitle': 'Kata sandi diperbarui', - 'login.resetPasswordSuccessBody': 'Sekarang kamu bisa login dengan kata sandi baru.', - 'login.resetPasswordInvalidLink': 'Tautan tidak valid', - 'login.resetPasswordInvalidLinkBody': 'Tautan hilang atau rusak. Minta tautan baru untuk melanjutkan.', - 'login.resetPasswordFailed': 'Reset gagal. Tautan mungkin sudah kedaluwarsa.', - - // Register - 'register.passwordMismatch': 'Kata sandi tidak cocok', - 'register.passwordTooShort': 'Kata sandi minimal 8 karakter', - 'register.failed': 'Pendaftaran gagal', - 'register.getStarted': 'Mulai Sekarang', - 'register.subtitle': 'Buat akun dan mulai rencanakan perjalanan impianmu.', - 'register.feature1': 'Rencana perjalanan tak terbatas', - 'register.feature2': 'Tampilan peta interaktif', - 'register.feature3': 'Kelola tempat dan kategori', - 'register.feature4': 'Lacak reservasi', - 'register.feature5': 'Buat daftar packing', - 'register.feature6': 'Simpan foto dan file', - 'register.createAccount': 'Buat Akun', - 'register.startPlanning': 'Mulai rencanakan perjalananmu', - 'register.minChars': 'Min. 6 karakter', - 'register.confirmPassword': 'Konfirmasi Kata Sandi', - 'register.repeatPassword': 'Ulangi kata sandi', - 'register.registering': 'Mendaftar...', - 'register.register': 'Daftar', - 'register.hasAccount': 'Sudah punya akun?', - 'register.signIn': 'Masuk', - - // Admin - 'admin.title': 'Administrasi', - 'admin.subtitle': 'Manajemen pengguna dan pengaturan sistem', - 'admin.tabs.users': 'Pengguna', - 'admin.tabs.categories': 'Kategori', - 'admin.tabs.backup': 'Backup', - 'admin.tabs.notifications': 'Notifikasi', - 'admin.tabs.audit': 'Audit', - 'admin.stats.users': 'Pengguna', - 'admin.stats.trips': 'Perjalanan', - 'admin.stats.places': 'Tempat', - 'admin.stats.photos': 'Foto', - 'admin.stats.files': 'File', - 'admin.table.user': 'Pengguna', - 'admin.table.email': 'Email', - 'admin.table.role': 'Peran', - 'admin.table.created': 'Dibuat', - 'admin.table.lastLogin': 'Login Terakhir', - 'admin.table.actions': 'Tindakan', - 'admin.you': '(Kamu)', - 'admin.editUser': 'Edit Pengguna', - 'admin.newPassword': 'Kata Sandi Baru', - 'admin.newPasswordHint': 'Kosongkan untuk mempertahankan kata sandi saat ini', - 'admin.deleteUser': 'Hapus pengguna "{name}"? Semua perjalanan akan dihapus secara permanen.', - 'admin.deleteUserTitle': 'Hapus pengguna', - 'admin.newPasswordPlaceholder': 'Masukkan kata sandi baru…', - 'admin.toast.loadError': 'Gagal memuat data admin', - 'admin.toast.userUpdated': 'Pengguna diperbarui', - 'admin.toast.updateError': 'Gagal memperbarui', - 'admin.toast.userDeleted': 'Pengguna dihapus', - 'admin.toast.deleteError': 'Gagal menghapus', - 'admin.toast.cannotDeleteSelf': 'Tidak bisa menghapus akun sendiri', - 'admin.toast.userCreated': 'Pengguna dibuat', - 'admin.toast.createError': 'Gagal membuat pengguna', - 'admin.toast.fieldsRequired': 'Nama pengguna, email, dan kata sandi wajib diisi', - 'admin.createUser': 'Buat Pengguna', - 'admin.invite.title': 'Tautan Undangan', - 'admin.invite.subtitle': 'Buat tautan pendaftaran sekali pakai', - 'admin.invite.create': 'Buat Tautan', - 'admin.invite.createAndCopy': 'Buat & Salin', - 'admin.invite.empty': 'Belum ada tautan undangan yang dibuat', - 'admin.invite.maxUses': 'Maks. Penggunaan', - 'admin.invite.expiry': 'Kedaluwarsa setelah', - 'admin.invite.uses': 'digunakan', - 'admin.invite.expiresAt': 'kedaluwarsa', - 'admin.invite.createdBy': 'oleh', - 'admin.invite.active': 'Aktif', - 'admin.invite.expired': 'Kedaluwarsa', - 'admin.invite.usedUp': 'Habis dipakai', - 'admin.invite.copied': 'Tautan undangan disalin ke clipboard', - 'admin.invite.copyLink': 'Salin tautan', - 'admin.invite.deleted': 'Tautan undangan dihapus', - 'admin.invite.createError': 'Gagal membuat tautan undangan', - 'admin.invite.deleteError': 'Gagal menghapus tautan undangan', - 'admin.tabs.settings': 'Pengaturan', - 'admin.allowRegistration': 'Izinkan Pendaftaran', - 'admin.allowRegistrationHint': 'Pengguna baru dapat mendaftar sendiri', - 'admin.authMethods': 'Metode Autentikasi', - 'admin.passwordLogin': 'Login dengan Kata Sandi', - 'admin.passwordLoginHint': 'Izinkan pengguna masuk dengan email dan kata sandi', - 'admin.passwordRegistration': 'Pendaftaran dengan Kata Sandi', - 'admin.passwordRegistrationHint': 'Izinkan pengguna baru mendaftar dengan email dan kata sandi', - 'admin.oidcLogin': 'Login SSO', - 'admin.oidcLoginHint': 'Izinkan pengguna masuk dengan SSO', - 'admin.oidcRegistration': 'Penyediaan Otomatis SSO', - 'admin.oidcRegistrationHint': 'Buat akun otomatis untuk pengguna SSO baru', - 'admin.envOverrideHint': 'Pengaturan login kata sandi dikendalikan oleh variabel lingkungan OIDC_ONLY dan tidak dapat diubah di sini.', - 'admin.lockoutWarning': 'Minimal satu metode login harus tetap aktif', - 'admin.requireMfa': 'Wajibkan autentikasi dua faktor (2FA)', - 'admin.requireMfaHint': 'Pengguna tanpa 2FA harus menyelesaikan pengaturan di Pengaturan sebelum menggunakan aplikasi.', - 'admin.apiKeys': 'Kunci API', - 'admin.apiKeysHint': 'Opsional. Mengaktifkan data tempat yang lebih lengkap seperti foto dan cuaca.', - 'admin.mapsKey': 'Kunci API Google Maps', - 'admin.mapsKeyHint': 'Diperlukan untuk pencarian tempat. Dapatkan di console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Tanpa kunci API, OpenStreetMap digunakan untuk pencarian tempat. Dengan kunci API Google, foto, rating, dan jam buka juga bisa dimuat. Dapatkan di console.cloud.google.com.', - 'admin.recommended': 'Direkomendasikan', - 'admin.weatherKey': 'Kunci API OpenWeatherMap', - 'admin.weatherKeyHint': 'Untuk data cuaca. Gratis di openweathermap.org', - 'admin.validateKey': 'Uji', - 'admin.keyValid': 'Terhubung', - 'admin.keyInvalid': 'Tidak valid', - 'admin.keySaved': 'Kunci API disimpan', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Izinkan login melalui penyedia eksternal seperti Google, Apple, Authentik, atau Keycloak.', - 'admin.oidcDisplayName': 'Nama Tampilan', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'Issuer URL OpenID Connect dari penyedia. Contoh: https://accounts.google.com', - 'admin.oidcSaved': 'Konfigurasi OIDC disimpan', - 'admin.oidcOnlyMode': 'Nonaktifkan autentikasi kata sandi', - 'admin.oidcOnlyModeHint': 'Jika diaktifkan, hanya login SSO yang diizinkan. Login dan pendaftaran berbasis kata sandi diblokir.', - - // File Types - 'admin.fileTypes': 'Jenis File yang Diizinkan', - 'admin.fileTypesHint': 'Atur jenis file apa saja yang boleh diunggah pengguna.', - 'admin.fileTypesFormat': 'Ekstensi dipisahkan koma (contoh: jpg,png,pdf,doc). Gunakan * untuk mengizinkan semua jenis.', - 'admin.fileTypesSaved': 'Pengaturan jenis file disimpan', - - // Packing Templates & Bag Tracking - 'admin.placesPhotos.title': 'Foto Tempat', - 'admin.placesPhotos.subtitle': 'Mengambil foto dari Google Places API. Nonaktifkan untuk menghemat kuota API. Foto Wikimedia tidak terpengaruh.', - 'admin.placesAutocomplete.title': 'Pelengkapan Otomatis Tempat', - 'admin.placesAutocomplete.subtitle': 'Menggunakan Google Places API untuk saran pencarian. Nonaktifkan untuk menghemat kuota API.', - 'admin.placesDetails.title': 'Detail Tempat', - 'admin.placesDetails.subtitle': 'Mengambil informasi detail tempat (jam, penilaian, situs web) dari Google Places API. Nonaktifkan untuk menghemat kuota API.', - 'admin.bagTracking.title': 'Pelacak Tas', - 'admin.bagTracking.subtitle': 'Aktifkan berat dan penugasan tas untuk item packing', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Pesan real-time untuk kolaborasi', - 'admin.collab.notes.title': 'Catatan', - 'admin.collab.notes.subtitle': 'Catatan dan dokumen bersama', - 'admin.collab.polls.title': 'Jajak Pendapat', - 'admin.collab.polls.subtitle': 'Jajak pendapat dan voting grup', - 'admin.collab.whatsnext.title': 'Selanjutnya', - 'admin.collab.whatsnext.subtitle': 'Saran aktivitas dan langkah selanjutnya', - 'admin.tabs.config': 'Personalisasi', - 'admin.tabs.defaults': 'Pengaturan Default Pengguna', - 'admin.defaultSettings.title': 'Pengaturan Default Pengguna', - 'admin.defaultSettings.description': 'Tetapkan nilai default untuk seluruh instance. Pengguna yang belum mengubah pengaturan akan melihat nilai-nilai ini. Perubahan mereka sendiri selalu diprioritaskan.', - 'admin.defaultSettings.saved': 'Default disimpan', - 'admin.defaultSettings.reset': 'Atur ulang ke default bawaan', - 'admin.defaultSettings.resetToBuiltIn': 'atur ulang', - 'admin.tabs.templates': 'Template Packing', - 'admin.packingTemplates.title': 'Template Packing', - 'admin.packingTemplates.subtitle': 'Buat daftar packing yang bisa digunakan ulang untuk perjalananmu', - 'admin.packingTemplates.create': 'Template Baru', - 'admin.packingTemplates.namePlaceholder': 'Nama template (contoh: Liburan Pantai)', - 'admin.packingTemplates.empty': 'Belum ada template yang dibuat', - 'admin.packingTemplates.items': 'item', - 'admin.packingTemplates.categories': 'kategori', - 'admin.packingTemplates.itemName': 'Nama item', - 'admin.packingTemplates.itemCategory': 'Kategori', - 'admin.packingTemplates.categoryName': 'Nama kategori (contoh: Pakaian)', - 'admin.packingTemplates.addCategory': 'Tambah kategori', - 'admin.packingTemplates.created': 'Template dibuat', - 'admin.packingTemplates.deleted': 'Template dihapus', - 'admin.packingTemplates.loadError': 'Gagal memuat template', - 'admin.packingTemplates.createError': 'Gagal membuat template', - 'admin.packingTemplates.deleteError': 'Gagal menghapus template', - 'admin.packingTemplates.saveError': 'Gagal menyimpan', - - // Addons - 'admin.tabs.addons': 'Addon', - 'admin.addons.title': 'Addon', - 'admin.addons.subtitle': 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan pengalaman TREK kamu.', - 'admin.addons.catalog.packing.name': 'Daftar', - 'admin.addons.catalog.packing.description': 'Daftar packing dan tugas to-do untuk perjalananmu', - 'admin.addons.catalog.budget.name': 'Anggaran', - 'admin.addons.catalog.budget.description': 'Lacak pengeluaran dan rencanakan anggaran perjalananmu', - 'admin.addons.catalog.documents.name': 'Dokumen', - 'admin.addons.catalog.documents.description': 'Simpan dan kelola dokumen perjalanan', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Perencana liburan pribadi dengan tampilan kalender', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Peta dunia dengan negara yang pernah dikunjungi dan statistik perjalanan', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Catatan real-time, polling, dan chat untuk perencanaan perjalanan', - 'admin.addons.catalog.memories.name': 'Foto (Immich)', - 'admin.addons.catalog.memories.description': 'Bagikan foto perjalanan melalui instans Immich kamu', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol untuk integrasi asisten AI', - 'admin.addons.subtitleBefore': 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan ', - 'admin.addons.subtitleAfter': ' kamu.', - 'admin.addons.enabled': 'Aktif', - 'admin.addons.disabled': 'Nonaktif', - 'admin.addons.type.trip': 'Perjalanan', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integrasi', - 'admin.addons.tripHint': 'Tersedia sebagai tab di setiap perjalanan', - 'admin.addons.globalHint': 'Tersedia sebagai bagian mandiri di navigasi utama', - 'admin.addons.integrationHint': 'Layanan backend dan integrasi API tanpa halaman tersendiri', - 'admin.addons.toast.updated': 'Addon diperbarui', - 'admin.addons.toast.error': 'Gagal memperbarui addon', - 'admin.addons.noAddons': 'Tidak ada addon yang tersedia', - // Weather info - 'admin.weather.title': 'Data Cuaca', - 'admin.weather.badge': 'Sejak 24 Maret 2026', - 'admin.weather.description': 'TREK menggunakan Open-Meteo sebagai sumber data cuaca. Open-Meteo adalah layanan cuaca gratis dan open-source — tidak perlu kunci API.', - 'admin.weather.forecast': 'Prakiraan 16 hari', - 'admin.weather.forecastDesc': 'Sebelumnya 5 hari (OpenWeatherMap)', - 'admin.weather.climate': 'Data iklim historis', - 'admin.weather.climateDesc': 'Rata-rata dari 85 tahun terakhir untuk hari di luar prakiraan 16 hari', - 'admin.weather.requests': '10.000 permintaan / hari', - 'admin.weather.requestsDesc': 'Gratis, tidak perlu kunci API', - 'admin.weather.locationHint': 'Cuaca didasarkan pada tempat pertama dengan koordinat di setiap hari. Jika tidak ada tempat yang ditetapkan untuk suatu hari, tempat mana pun dari daftar tempat digunakan sebagai referensi.', - - // GitHub - 'admin.tabs.mcpTokens': 'Akses MCP', - 'admin.mcpTokens.title': 'Akses MCP', - 'admin.mcpTokens.subtitle': 'Kelola sesi OAuth dan token API di semua pengguna', - 'admin.mcpTokens.sectionTitle': 'Token API', - 'admin.mcpTokens.owner': 'Pemilik', - 'admin.mcpTokens.tokenName': 'Nama Token', - 'admin.mcpTokens.created': 'Dibuat', - 'admin.mcpTokens.lastUsed': 'Terakhir Digunakan', - 'admin.mcpTokens.never': 'Tidak pernah', - 'admin.mcpTokens.empty': 'Belum ada token MCP yang dibuat', - 'admin.mcpTokens.deleteTitle': 'Hapus Token', - 'admin.mcpTokens.deleteMessage': 'Ini akan mencabut token segera. Pengguna akan kehilangan akses MCP melalui token ini.', - 'admin.mcpTokens.deleteSuccess': 'Token dihapus', - 'admin.mcpTokens.deleteError': 'Gagal menghapus token', - 'admin.mcpTokens.loadError': 'Gagal memuat token', - 'admin.oauthSessions.sectionTitle': 'Sesi OAuth', - 'admin.oauthSessions.clientName': 'Klien', - 'admin.oauthSessions.owner': 'Pemilik', - 'admin.oauthSessions.scopes': 'Cakupan', - 'admin.oauthSessions.created': 'Dibuat', - 'admin.oauthSessions.empty': 'Tidak ada sesi OAuth aktif', - 'admin.oauthSessions.revokeTitle': 'Cabut Sesi', - 'admin.oauthSessions.revokeMessage': 'Ini akan segera mencabut sesi OAuth. Client akan kehilangan akses MCP.', - 'admin.oauthSessions.revokeSuccess': 'Sesi dicabut', - 'admin.oauthSessions.revokeError': 'Gagal mencabut sesi', - 'admin.oauthSessions.loadError': 'Gagal memuat sesi OAuth', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Peristiwa sensitif keamanan dan administrasi (backup, pengguna, MFA, pengaturan).', - 'admin.audit.empty': 'Belum ada entri audit.', - 'admin.audit.refresh': 'Segarkan', - 'admin.audit.loadMore': 'Muat lebih banyak', - 'admin.audit.showing': '{count} dimuat · {total} total', - 'admin.audit.col.time': 'Waktu', - 'admin.audit.col.user': 'Pengguna', - 'admin.audit.col.action': 'Tindakan', - 'admin.audit.col.resource': 'Sumber Daya', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Detail', - 'admin.github.title': 'Riwayat Rilis', - 'admin.github.subtitle': 'Pembaruan terbaru dari {repo}', - 'admin.github.latest': 'Terbaru', - 'admin.github.prerelease': 'Pra-rilis', - 'admin.github.showDetails': 'Tampilkan detail', - 'admin.github.hideDetails': 'Sembunyikan detail', - 'admin.github.loadMore': 'Muat lebih banyak', - 'admin.github.loading': 'Memuat...', - 'admin.github.error': 'Gagal memuat rilis', - 'admin.github.by': 'oleh', - 'admin.github.support': 'Bantu saya terus mengembangkan TREK', - - 'admin.update.available': 'Pembaruan tersedia', - 'admin.update.text': 'TREK {version} tersedia. Kamu menggunakan {current}.', - 'admin.update.button': 'Lihat di GitHub', - 'admin.update.install': 'Pasang Pembaruan', - 'admin.update.confirmTitle': 'Pasang Pembaruan?', - 'admin.update.confirmText': 'TREK akan diperbarui dari {current} ke {version}. Server akan restart otomatis setelahnya.', - 'admin.update.dataInfo': 'Semua datamu (perjalanan, pengguna, kunci API, unggahan, Vacay, Atlas, anggaran) akan dipertahankan.', - 'admin.update.warning': 'Aplikasi akan tidak tersedia sebentar selama restart.', - 'admin.update.confirm': 'Perbarui Sekarang', - 'admin.update.installing': 'Memperbarui…', - 'admin.update.success': 'Pembaruan terpasang! Server sedang restart…', - 'admin.update.failed': 'Pembaruan gagal', - 'admin.update.backupHint': 'Kami merekomendasikan membuat backup sebelum memperbarui.', - 'admin.update.backupLink': 'Pergi ke Backup', - 'admin.update.howTo': 'Cara Memperbarui', - 'admin.update.dockerText': 'Instans TREK kamu berjalan di Docker. Untuk memperbarui ke {version}, jalankan perintah berikut di servermu:', - 'admin.update.reloadHint': 'Muat ulang halaman dalam beberapa detik.', - - // Vacay addon - 'vacay.subtitle': 'Rencanakan dan kelola hari cuti', - 'vacay.settings': 'Pengaturan', - 'vacay.year': 'Tahun', - 'vacay.addYear': 'Tambah tahun berikutnya', - 'vacay.addPrevYear': 'Tambah tahun sebelumnya', - 'vacay.removeYear': 'Hapus tahun', - 'vacay.removeYearConfirm': 'Hapus {year}?', - 'vacay.removeYearHint': 'Semua entri cuti dan hari libur perusahaan untuk tahun ini akan dihapus secara permanen.', - 'vacay.remove': 'Hapus', - 'vacay.persons': 'Orang', - 'vacay.noPersons': 'Belum ada orang ditambahkan', - 'vacay.addPerson': 'Tambah Orang', - 'vacay.editPerson': 'Edit Orang', - 'vacay.removePerson': 'Hapus Orang', - 'vacay.removePersonConfirm': 'Hapus {name}?', - 'vacay.removePersonHint': 'Semua entri cuti untuk orang ini akan dihapus secara permanen.', - 'vacay.personName': 'Nama', - 'vacay.personNamePlaceholder': 'Masukkan nama', - 'vacay.color': 'Warna', - 'vacay.add': 'Tambah', - 'vacay.legend': 'Keterangan', - 'vacay.publicHoliday': 'Hari Libur Nasional', - 'vacay.companyHoliday': 'Hari Libur Perusahaan', - 'vacay.weekend': 'Akhir Pekan', - 'vacay.modeVacation': 'Cuti', - 'vacay.modeCompany': 'Hari Libur Perusahaan', - 'vacay.entitlement': 'Jatah Cuti', - 'vacay.entitlementDays': 'Hari', - 'vacay.used': 'Terpakai', - 'vacay.remaining': 'Sisa', - 'vacay.carriedOver': 'dari {year}', - 'vacay.blockWeekends': 'Blokir Akhir Pekan', - 'vacay.blockWeekendsHint': 'Cegah entri cuti pada hari akhir pekan', - 'vacay.weekendDays': 'Hari akhir pekan', - 'vacay.mon': 'Sen', - 'vacay.tue': 'Sel', - 'vacay.wed': 'Rab', - 'vacay.thu': 'Kam', - 'vacay.fri': 'Jum', - 'vacay.sat': 'Sab', - 'vacay.sun': 'Min', - 'vacay.publicHolidays': 'Hari Libur Nasional', - 'vacay.publicHolidaysHint': 'Tandai hari libur nasional di kalender', - 'vacay.selectCountry': 'Pilih negara', - 'vacay.selectRegion': 'Pilih wilayah (opsional)', - 'vacay.addCalendar': 'Tambah kalender', - 'vacay.calendarLabel': 'Label (opsional)', - 'vacay.calendarColor': 'Warna', - 'vacay.noCalendars': 'Belum ada kalender hari libur ditambahkan', - 'vacay.companyHolidays': 'Hari Libur Perusahaan', - 'vacay.companyHolidaysHint': 'Izinkan penandaan hari libur seluruh perusahaan', - 'vacay.companyHolidaysNoDeduct': 'Hari libur perusahaan tidak dipotong dari jatah cuti.', - 'vacay.weekStart': 'Awal minggu', - 'vacay.weekStartHint': 'Pilih apakah minggu kalender dimulai pada hari Senin atau Minggu', - 'vacay.carryOver': 'Carry Over Cuti', - 'vacay.carryOverHint': 'Otomatis pindahkan sisa hari cuti ke tahun berikutnya', - 'vacay.sharing': 'Berbagi', - 'vacay.sharingHint': 'Bagikan rencana cuti kamu dengan pengguna TREK lainnya', - 'vacay.owner': 'Pemilik', - 'vacay.shareEmailPlaceholder': 'Email pengguna TREK', - 'vacay.shareSuccess': 'Rencana berhasil dibagikan', - 'vacay.shareError': 'Gagal membagikan rencana', - 'vacay.dissolve': 'Pisahkan Gabungan', - 'vacay.dissolveHint': 'Pisahkan kalender kembali. Entri kamu akan tetap disimpan.', - 'vacay.dissolveAction': 'Pisahkan', - 'vacay.dissolved': 'Kalender dipisahkan', - 'vacay.fusedWith': 'Digabung dengan', - 'vacay.you': 'kamu', - 'vacay.noData': 'Tidak ada data', - 'vacay.changeColor': 'Ganti warna', - 'vacay.inviteUser': 'Undang Pengguna', - 'vacay.inviteHint': 'Undang pengguna TREK lain untuk berbagi kalender cuti bersama.', - 'vacay.selectUser': 'Pilih pengguna', - 'vacay.sendInvite': 'Kirim Undangan', - 'vacay.inviteSent': 'Undangan terkirim', - 'vacay.inviteError': 'Gagal mengirim undangan', - 'vacay.pending': 'menunggu', - 'vacay.noUsersAvailable': 'Tidak ada pengguna tersedia', - 'vacay.accept': 'Terima', - 'vacay.decline': 'Tolak', - 'vacay.acceptFusion': 'Terima & Gabung', - 'vacay.inviteTitle': 'Permintaan Penggabungan', - 'vacay.inviteWantsToFuse': 'ingin berbagi kalender cuti bersamamu.', - 'vacay.fuseInfo1': 'Kalian berdua akan melihat semua entri cuti dalam satu kalender bersama.', - 'vacay.fuseInfo2': 'Kedua pihak dapat membuat dan mengedit entri satu sama lain.', - 'vacay.fuseInfo3': 'Kedua pihak dapat menghapus entri dan mengubah jatah cuti.', - 'vacay.fuseInfo4': 'Pengaturan seperti hari libur nasional dan hari libur perusahaan dibagikan bersama.', - 'vacay.fuseInfo5': 'Penggabungan dapat dipisahkan kapan saja oleh salah satu pihak. Entri kamu akan tetap disimpan.', - 'nav.myTrips': 'Perjalananku', - - // Atlas addon - 'atlas.subtitle': 'Jejak perjalananmu di seluruh dunia', - 'atlas.countries': 'Negara', - 'atlas.trips': 'Perjalanan', - 'atlas.places': 'Tempat', - 'atlas.unmark': 'Hapus', - 'atlas.confirmMark': 'Tandai negara ini sebagai sudah dikunjungi?', - 'atlas.confirmUnmark': 'Hapus negara ini dari daftar kunjunganmu?', - 'atlas.confirmUnmarkRegion': 'Hapus wilayah ini dari daftar kunjunganmu?', - 'atlas.markVisited': 'Tandai sudah dikunjungi', - 'atlas.markVisitedHint': 'Tambahkan negara ini ke daftar kunjunganmu', - 'atlas.markRegionVisitedHint': 'Tambahkan wilayah ini ke daftar kunjunganmu', - 'atlas.addToBucket': 'Tambah ke bucket list', - 'atlas.addPoi': 'Tambah tempat', - 'atlas.searchCountry': 'Cari negara...', - 'atlas.bucketNamePlaceholder': 'Nama (negara, kota, tempat...)', - 'atlas.month': 'Bulan', - 'atlas.year': 'Tahun', - 'atlas.addToBucketHint': 'Simpan sebagai tempat yang ingin dikunjungi', - 'atlas.bucketWhen': 'Kapan kamu berencana mengunjunginya?', - 'atlas.statsTab': 'Statistik', - 'atlas.bucketTab': 'Daftar Impian', - 'atlas.addBucket': 'Tambah ke daftar impian', - 'atlas.bucketNotesPlaceholder': 'Catatan (opsional)', - 'atlas.bucketEmpty': 'Daftar impianmu masih kosong', - 'atlas.bucketEmptyHint': 'Tambahkan tempat-tempat yang ingin kamu kunjungi', - 'atlas.days': 'Hari', - 'atlas.visitedCountries': 'Negara yang Dikunjungi', - 'atlas.cities': 'Kota', - 'atlas.noData': 'Belum ada data perjalanan', - 'atlas.noDataHint': 'Buat perjalanan dan tambahkan tempat untuk melihat peta duniamu', - 'atlas.lastTrip': 'Perjalanan terakhir', - 'atlas.nextTrip': 'Perjalanan berikutnya', - 'atlas.daysLeft': 'hari lagi', - 'atlas.streak': 'Rentetan', - 'atlas.years': 'tahun', - 'atlas.yearInRow': 'tahun berturut-turut', - 'atlas.yearsInRow': 'tahun berturut-turut', - 'atlas.tripIn': 'perjalanan ke', - 'atlas.tripsIn': 'perjalanan ke', - 'atlas.since': 'sejak', - 'atlas.europe': 'Eropa', - 'atlas.asia': 'Asia', - 'atlas.northAmerica': 'Amerika Utara', - 'atlas.southAmerica': 'Amerika Selatan', - 'atlas.africa': 'Afrika', - 'atlas.oceania': 'Oseania', - 'atlas.other': 'Lainnya', - 'atlas.firstVisit': 'Perjalanan pertama', - 'atlas.lastVisitLabel': 'Perjalanan terakhir', - 'atlas.tripSingular': 'Perjalanan', - 'atlas.tripPlural': 'Perjalanan', - 'atlas.placeVisited': 'Tempat dikunjungi', - 'atlas.placesVisited': 'Tempat dikunjungi', - - // Trip Planner - 'trip.tabs.plan': 'Rencana', - 'trip.tabs.transports': 'Transportasi', - 'trip.tabs.reservations': 'Pemesanan', - 'trip.tabs.reservationsShort': 'Pesan', - 'trip.tabs.packing': 'Daftar Perlengkapan', - 'trip.tabs.packingShort': 'Perlengkapan', - 'trip.tabs.lists': 'Daftar', - 'trip.tabs.listsShort': 'Daftar', - 'trip.tabs.budget': 'Anggaran', - 'trip.tabs.files': 'File', - 'trip.loading': 'Memuat perjalanan...', - 'trip.loadingPhotos': 'Memuat foto tempat...', - 'trip.mobilePlan': 'Rencana', - 'trip.mobilePlaces': 'Tempat', - 'trip.toast.placeUpdated': 'Tempat diperbarui', - 'trip.toast.placeAdded': 'Tempat ditambahkan', - 'trip.toast.placeDeleted': 'Tempat dihapus', - 'trip.toast.selectDay': 'Pilih hari terlebih dahulu', - 'trip.toast.assignedToDay': 'Tempat ditambahkan ke hari', - 'trip.toast.reorderError': 'Gagal mengurutkan ulang', - 'trip.toast.reservationUpdated': 'Reservasi diperbarui', - 'trip.toast.reservationAdded': 'Reservasi ditambahkan', - 'trip.toast.deleted': 'Dihapus', - 'trip.confirm.deletePlace': 'Apakah kamu yakin ingin menghapus tempat ini?', - 'trip.confirm.deletePlaces': 'Hapus {count} tempat?', - 'trip.toast.placesDeleted': '{count} tempat dihapus', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Belum ada tempat yang direncanakan untuk hari ini', - 'dayplan.cannotReorderTransport': 'Pemesanan dengan waktu tetap tidak bisa diurutkan ulang', - 'dayplan.confirmRemoveTimeTitle': 'Hapus waktu?', - 'dayplan.confirmRemoveTimeBody': 'Tempat ini memiliki waktu tetap ({time}). Memindahkannya akan menghapus waktu dan mengizinkan pengurutan bebas.', - 'dayplan.confirmRemoveTimeAction': 'Hapus waktu & pindahkan', - 'dayplan.cannotDropOnTimed': 'Item tidak dapat ditempatkan di antara entri yang terikat waktu', - 'dayplan.cannotBreakChronology': 'Ini akan merusak urutan kronologis item bertanggal dan pemesanan', - 'dayplan.addNote': 'Tambah Catatan', - 'dayplan.editNote': 'Edit Catatan', - 'dayplan.noteAdd': 'Tambah Catatan', - 'dayplan.noteEdit': 'Edit Catatan', - 'dayplan.noteTitle': 'Catatan', - 'dayplan.noteSubtitle': 'Catatan Harian', - 'dayplan.totalCost': 'Total Biaya', - 'dayplan.days': 'Hari', - 'dayplan.dayN': 'Hari {n}', - 'dayplan.calculating': 'Menghitung...', - 'dayplan.route': 'Rute', - 'dayplan.optimize': 'Optimalkan', - 'dayplan.optimized': 'Rute dioptimalkan', - 'dayplan.routeError': 'Gagal menghitung rute', - 'dayplan.toast.needTwoPlaces': 'Diperlukan minimal dua tempat untuk optimasi rute', - 'dayplan.toast.routeOptimized': 'Rute dioptimalkan', - 'dayplan.toast.noGeoPlaces': 'Tidak ditemukan tempat dengan koordinat untuk kalkulasi rute', - 'dayplan.confirmed': 'Dikonfirmasi', - 'dayplan.pendingRes': 'Menunggu', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Ekspor rencana hari sebagai PDF', - 'dayplan.pdfError': 'Gagal mengekspor PDF', - - // Places Sidebar - 'places.addPlace': 'Tambah Tempat/Aktivitas', - 'places.importFile': 'Impor file', - 'places.sidebarDrop': 'Lepas untuk mengimpor', - 'places.importFileHint': 'Impor file .gpx, .kml, atau .kmz dari Google My Maps, Google Earth, atau pelacak GPS.', - 'places.importFileDropHere': 'Klik untuk memilih file atau seret dan lepas di sini', - 'places.importFileDropActive': 'Lepas file untuk memilih', - 'places.importFileUnsupported': 'Jenis file tidak didukung. Gunakan .gpx, .kml, atau .kmz.', - 'places.importFileTooLarge': 'File terlalu besar. Ukuran unggah maksimum adalah {maxMb} MB.', - 'places.importFileError': 'Impor gagal', - 'places.importAllSkipped': 'Semua tempat sudah ada di perjalanan.', - 'places.gpxImported': '{count} tempat diimpor dari GPX', - 'places.gpxImportTypes': 'Apa yang ingin diimpor?', - 'places.gpxImportWaypoints': 'Titik jalan', - 'places.gpxImportRoutes': 'Rute', - 'places.gpxImportTracks': 'Trek (dengan geometri jalur)', - 'places.gpxImportNoneSelected': 'Pilih setidaknya satu jenis untuk diimpor.', - 'places.kmlImportTypes': 'Apa yang ingin diimpor?', - 'places.kmlImportPoints': 'Titik (Placemarks)', - 'places.kmlImportPaths': 'Jalur (LineStrings)', - 'places.kmlImportNoneSelected': 'Pilih setidaknya satu jenis.', - 'places.selectionCount': '{count} dipilih', - 'places.deleteSelected': 'Hapus yang dipilih', - 'places.kmlKmzImported': '{count} tempat diimpor dari KMZ/KML', - 'places.urlResolved': 'Tempat diimpor dari URL', - 'places.importList': 'Impor Daftar', - 'places.kmlKmzSummaryValues': 'Placemark: {total} • Diimpor: {created} • Dilewati: {skipped}', - 'places.importGoogleList': 'Daftar Google', - 'places.importNaverList': 'Daftar Naver', - 'places.googleListHint': 'Tempel tautan daftar Google Maps yang dibagikan untuk mengimpor semua tempat.', - 'places.googleListImported': '{count} tempat diimpor dari "{list}"', - 'places.googleListError': 'Gagal mengimpor daftar Google Maps', - 'places.naverListHint': 'Tempel tautan daftar Naver Maps yang dibagikan untuk mengimpor semua tempat.', - 'places.naverListImported': '{count} tempat diimpor dari "{list}"', - 'places.naverListError': 'Gagal mengimpor daftar Naver Maps', - 'places.viewDetails': 'Lihat Detail', - 'places.assignToDay': 'Tambah ke hari mana?', - 'places.all': 'Semua', - 'places.unplanned': 'Belum direncanakan', - 'places.filterTracks': 'Trek', - 'places.search': 'Cari tempat...', - 'places.allCategories': 'Semua Kategori', - 'places.categoriesSelected': 'kategori', - 'places.clearFilter': 'Hapus filter', - 'places.count': '{count} tempat', - 'places.countSingular': '1 tempat', - 'places.allPlanned': 'Semua tempat sudah direncanakan', - 'places.noneFound': 'Tidak ada tempat ditemukan', - 'places.editPlace': 'Edit Tempat', - 'places.formName': 'Nama', - 'places.formNamePlaceholder': 'mis. Menara Eiffel', - 'places.formDescription': 'Deskripsi', - 'places.formDescriptionPlaceholder': 'Deskripsi singkat...', - 'places.formAddress': 'Alamat', - 'places.formAddressPlaceholder': 'Jalan, Kota, Negara', - 'places.formLat': 'Lintang (mis. 48.8566)', - 'places.formLng': 'Bujur (mis. 2.3522)', - 'places.formCategory': 'Kategori', - 'places.noCategory': 'Tanpa Kategori', - 'places.categoryNamePlaceholder': 'Nama kategori', - 'places.formTime': 'Waktu', - 'places.startTime': 'Mulai', - 'places.endTime': 'Selesai', - 'places.endTimeBeforeStart': 'Waktu selesai lebih awal dari waktu mulai', - 'places.timeCollision': 'Waktu tumpang tindih dengan:', - 'places.formWebsite': 'Situs web', - 'places.formNotes': 'Catatan', - 'places.formNotesPlaceholder': 'Catatan pribadi...', - 'places.formReservation': 'Reservasi', - 'places.reservationNotesPlaceholder': 'Catatan reservasi, nomor konfirmasi...', - 'places.mapsSearchPlaceholder': 'Cari tempat...', - 'places.mapsSearchError': 'Pencarian tempat gagal.', - 'places.loadingDetails': 'Memuat detail tempat…', - 'places.osmHint': 'Menggunakan pencarian OpenStreetMap (tanpa foto, jam buka, atau penilaian). Tambahkan Google API key di pengaturan untuk detail lengkap.', - 'places.osmActive': 'Pencarian via OpenStreetMap (tanpa foto, penilaian, atau jam buka). Tambahkan Google API key di Pengaturan untuk data yang lebih lengkap.', - 'places.categoryCreateError': 'Gagal membuat kategori', - 'places.nameRequired': 'Harap masukkan nama', - 'places.saveError': 'Gagal menyimpan', - // Place Inspector - 'inspector.opened': 'Buka', - 'inspector.closed': 'Tutup', - 'inspector.openingHours': 'Jam Buka', - 'inspector.showHours': 'Tampilkan jam buka', - 'inspector.files': 'File', - 'inspector.filesCount': '{count} file', - 'inspector.remove': 'Hapus', - 'inspector.removeFromDay': 'Hapus dari Hari', - 'inspector.addToDay': 'Tambah ke Hari', - 'inspector.confirmedRes': 'Reservasi Dikonfirmasi', - 'inspector.pendingRes': 'Reservasi Menunggu', - 'inspector.google': 'Buka di Google Maps', - 'inspector.website': 'Buka Situs Web', - 'inspector.addRes': 'Reservasi', - 'inspector.editRes': 'Edit Reservasi', - 'inspector.participants': 'Peserta', - 'inspector.trackStats': 'Statistik Jalur', - - // Reservations - 'reservations.title': 'Pemesanan', - 'reservations.empty': 'Belum ada reservasi', - 'reservations.emptyHint': 'Tambahkan reservasi untuk penerbangan, hotel, dan lainnya', - 'reservations.add': 'Tambah Reservasi', - 'reservations.addManual': 'Pemesanan Manual', - 'reservations.placeHint': 'Tips: Reservasi paling baik dibuat langsung dari sebuah tempat agar terhubung dengan rencana harianmu.', - 'reservations.confirmed': 'Dikonfirmasi', - 'reservations.pending': 'Tertunda', - 'reservations.summary': '{confirmed} dikonfirmasi, {pending} tertunda', - 'reservations.fromPlan': 'Dari Rencana', - 'reservations.showFiles': 'Tampilkan File', - 'reservations.editTitle': 'Edit Reservasi', - 'reservations.status': 'Status', - 'reservations.datetime': 'Tanggal & Waktu', - 'reservations.startTime': 'Waktu mulai', - 'reservations.endTime': 'Waktu selesai', - 'reservations.date': 'Tanggal', - 'reservations.time': 'Waktu', - 'reservations.timeAlt': 'Waktu (alternatif, mis. 19:30)', - 'reservations.notes': 'Catatan', - 'reservations.notesPlaceholder': 'Catatan tambahan...', - 'reservations.meta.airline': 'Maskapai', - 'reservations.meta.flightNumber': 'No. Penerbangan', - 'reservations.meta.from': 'Dari', - 'reservations.meta.to': 'Ke', - 'reservations.needsReview': 'Tinjau', - 'reservations.needsReviewHint': 'Bandara tidak dapat dicocokkan otomatis — konfirmasi lokasi.', - 'reservations.searchLocation': 'Cari stasiun, pelabuhan, alamat...', - 'airport.searchPlaceholder': 'Kode bandara atau kota (mis. FRA)', - 'map.connections': 'Koneksi', - 'map.showConnections': 'Tampilkan rute pemesanan', - 'map.hideConnections': 'Sembunyikan rute pemesanan', - 'settings.bookingLabels': 'Label rute pemesanan', - 'settings.bookingLabelsHint': 'Menampilkan nama stasiun / bandara di peta. Jika mati, hanya ikon ditampilkan.', - 'reservations.meta.trainNumber': 'No. Kereta', - 'reservations.meta.platform': 'Peron', - 'reservations.meta.seat': 'Kursi', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in sampai', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Akomodasi', - 'reservations.meta.pickAccommodation': 'Hubungkan ke akomodasi', - 'reservations.meta.noAccommodation': 'Tidak ada', - 'reservations.meta.hotelPlace': 'Akomodasi', - 'reservations.meta.pickHotel': 'Pilih akomodasi', - 'reservations.meta.fromDay': 'Dari', - 'reservations.meta.toDay': 'Sampai', - 'reservations.meta.selectDay': 'Pilih hari', - 'reservations.type.flight': 'Penerbangan', - 'reservations.type.hotel': 'Akomodasi', - 'reservations.type.restaurant': 'Restoran', - 'reservations.type.train': 'Kereta', - 'reservations.type.car': 'Mobil', - 'reservations.type.cruise': 'Kapal Pesiar', - 'reservations.type.event': 'Acara', - 'reservations.type.tour': 'Tur', - 'reservations.type.other': 'Lainnya', - 'reservations.confirm.delete': 'Yakin ingin menghapus reservasi "{name}"?', - 'reservations.confirm.deleteTitle': 'Hapus pemesanan?', - 'reservations.confirm.deleteBody': '"{name}" akan dihapus permanen.', - 'reservations.toast.updated': 'Reservasi diperbarui', - 'reservations.toast.removed': 'Reservasi dihapus', - 'reservations.toast.fileUploaded': 'File diunggah', - 'reservations.toast.uploadError': 'Gagal mengunggah', - 'reservations.newTitle': 'Reservasi Baru', - 'reservations.bookingType': 'Jenis Pemesanan', - 'reservations.titleLabel': 'Judul', - 'reservations.titlePlaceholder': 'mis. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Lokasi / Alamat', - 'reservations.locationPlaceholder': 'Alamat, Bandara, Hotel...', - 'reservations.confirmationCode': 'Kode Pemesanan', - 'reservations.confirmationPlaceholder': 'mis. ABC12345', - 'reservations.day': 'Hari', - 'reservations.noDay': 'Tanpa Hari', - 'reservations.place': 'Tempat', - 'reservations.noPlace': 'Tanpa Tempat', - 'reservations.pendingSave': 'akan disimpan…', - 'reservations.uploading': 'Mengunggah...', - 'reservations.attachFile': 'Lampirkan file', - 'reservations.linkExisting': 'Hubungkan file yang ada', - 'reservations.toast.saveError': 'Gagal menyimpan', - 'reservations.toast.updateError': 'Gagal memperbarui', - 'reservations.toast.deleteError': 'Gagal menghapus', - 'reservations.confirm.remove': 'Hapus reservasi untuk "{name}"?', - 'reservations.linkAssignment': 'Hubungkan ke jadwal harian', - 'reservations.pickAssignment': 'Pilih jadwal dari rencanamu...', - 'reservations.noAssignment': 'Tanpa tautan (mandiri)', - 'reservations.price': 'Harga', - 'reservations.budgetCategory': 'Kategori anggaran', - 'reservations.budgetCategoryPlaceholder': 'mis. Transportasi, Akomodasi', - 'reservations.budgetCategoryAuto': 'Otomatis (dari jenis pemesanan)', - 'reservations.budgetHint': 'Entri anggaran akan dibuat otomatis saat menyimpan.', - 'reservations.departureDate': 'Keberangkatan', - 'reservations.arrivalDate': 'Kedatangan', - 'reservations.departureTime': 'Waktu berangkat', - 'reservations.arrivalTime': 'Waktu tiba', - 'reservations.pickupDate': 'Penjemputan', - 'reservations.returnDate': 'Pengembalian', - 'reservations.pickupTime': 'Waktu jemput', - 'reservations.returnTime': 'Waktu kembali', - 'reservations.endDate': 'Tanggal selesai', - 'reservations.meta.departureTimezone': 'TZ Berangkat', - 'reservations.meta.arrivalTimezone': 'TZ Tiba', - 'reservations.span.departure': 'Keberangkatan', - 'reservations.span.arrival': 'Kedatangan', - 'reservations.span.inTransit': 'Dalam perjalanan', - 'reservations.span.pickup': 'Penjemputan', - 'reservations.span.return': 'Pengembalian', - 'reservations.span.active': 'Aktif', - 'reservations.span.start': 'Mulai', - 'reservations.span.end': 'Selesai', - 'reservations.span.ongoing': 'Berlangsung', - 'reservations.validation.endBeforeStart': 'Tanggal/waktu selesai harus setelah tanggal/waktu mulai', - 'reservations.addBooking': 'Tambah pemesanan', - - // Budget - 'budget.title': 'Anggaran', - 'budget.exportCsv': 'Ekspor CSV', - 'budget.emptyTitle': 'Belum ada anggaran', - 'budget.emptyText': 'Buat kategori dan entri untuk merencanakan anggaran perjalananmu', - 'budget.emptyPlaceholder': 'Masukkan nama kategori...', - 'budget.createCategory': 'Buat Kategori', - 'budget.category': 'Kategori', - 'budget.categoryName': 'Nama Kategori', - 'budget.table.name': 'Nama', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Orang', - 'budget.table.days': 'Hari', - 'budget.table.perPerson': 'Per Orang', - 'budget.table.perDay': 'Per Hari', - 'budget.table.perPersonDay': 'P. o / Hari', - 'budget.table.note': 'Catatan', - 'budget.table.date': 'Tanggal', - 'budget.newEntry': 'Entri Baru', - 'budget.defaultEntry': 'Entri Baru', - 'budget.defaultCategory': 'Kategori Baru', - 'budget.total': 'Total', - 'budget.totalBudget': 'Total Anggaran', - 'budget.byCategory': 'Per Kategori', - 'budget.editTooltip': 'Klik untuk edit', - 'budget.linkedToReservation': 'Terhubung ke reservasi — edit nama di sana', - 'budget.confirm.deleteCategory': 'Yakin ingin menghapus kategori "{name}" dengan {count} entri?', - 'budget.deleteCategory': 'Hapus Kategori', - 'budget.perPerson': 'Per Orang', - 'budget.paid': 'Sudah dibayar', - 'budget.open': 'Belum dibayar', - 'budget.noMembers': 'Tidak ada anggota yang ditugaskan', - 'budget.settlement': 'Penyelesaian', - 'budget.settlementInfo': 'Klik foto anggota di item anggaran untuk menandainya hijau — artinya mereka sudah bayar. Penyelesaian lalu menunjukkan siapa berhutang ke siapa dan berapa.', - 'budget.netBalances': 'Saldo Bersih', - - // Files - 'files.title': 'File', - 'files.pageTitle': 'File & Dokumen', - 'files.subtitle': '{count} file untuk {trip}', - 'files.download': 'Unduh', - 'files.openError': 'Tidak dapat membuka file', - 'files.downloadPdf': 'Unduh PDF', - 'files.count': '{count} file', - 'files.countSingular': '1 berkas', - 'files.uploaded': '{count} diunggah', - 'files.uploadError': 'Gagal mengunggah', - 'files.dropzone': 'Jatuhkan file di sini', - 'files.dropzoneHint': 'atau klik untuk memilih', - 'files.allowedTypes': 'Gambar, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB', - 'files.uploading': 'Mengunggah...', - 'files.filterAll': 'Semua', - 'files.filterPdf': 'PDFs', - 'files.filterImages': 'Gambar', - 'files.filterDocs': 'Dokumen', - 'files.filterCollab': 'Catatan Collab', - 'files.sourceCollab': 'Dari Catatan Collab', - 'files.empty': 'Belum ada file', - 'files.emptyHint': 'Unggah file untuk melampirkannya ke perjalananmu', - 'files.openTab': 'Buka di tab baru', - 'files.confirm.delete': 'Yakin ingin menghapus file ini?', - 'files.toast.deleted': 'File dihapus', - 'files.toast.deleteError': 'Gagal menghapus file', - 'files.sourcePlan': 'Rencana Harian', - 'files.sourceBooking': 'Pemesanan', - 'files.sourceTransport': 'Transportasi', - 'files.attach': 'Lampirkan', - 'files.pasteHint': 'Kamu juga bisa menempel gambar dari clipboard (Ctrl+V)', - 'files.trash': 'Sampah', - 'files.trashEmpty': 'Sampah kosong', - 'files.emptyTrash': 'Kosongkan Sampah', - 'files.restore': 'Pulihkan', - 'files.star': 'Bintang', - 'files.unstar': 'Hapus bintang', - 'files.assign': 'Tugaskan', - 'files.assignTitle': 'Tugaskan File', - 'files.assignPlace': 'Tempat', - 'files.assignBooking': 'Pemesanan', - 'files.assignTransport': 'Transportasi', - 'files.unassigned': 'Tidak ditugaskan', - 'files.unlink': 'Hapus tautan', - 'files.toast.trashed': 'Dipindahkan ke sampah', - 'files.toast.restored': 'File dipulihkan', - 'files.toast.trashEmptied': 'Sampah dikosongkan', - 'files.toast.assigned': 'File ditugaskan', - 'files.toast.assignError': 'Penugasan gagal', - 'files.toast.restoreError': 'Pemulihan gagal', - 'files.confirm.permanentDelete': 'Hapus file ini secara permanen? Tindakan ini tidak bisa dibatalkan.', - 'files.confirm.emptyTrash': 'Hapus semua file di sampah secara permanen? Tindakan ini tidak bisa dibatalkan.', - 'files.noteLabel': 'Catatan', - 'files.notePlaceholder': 'Tambahkan catatan...', - - // Packing - 'packing.title': 'Daftar Bawaan', - 'packing.empty': 'Daftar bawaan kosong', - 'packing.import': 'Impor', - 'packing.importTitle': 'Impor Daftar Bawaan', - 'packing.importHint': 'Satu item per baris. Format: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional)', - 'packing.importPlaceholder': 'Kebersihan, Sikat Gigi\nPakaian, Kaos, 200\nDokumen, Paspor, , Kabin\nElektronik, Charger, 50, Koper, checked', - 'packing.importCsv': 'Muat CSV/TXT', - 'packing.importAction': 'Impor {count}', - 'packing.importSuccess': '{count} item berhasil diimpor', - 'packing.importError': 'Impor gagal', - 'packing.importEmpty': 'Tidak ada item untuk diimpor', - 'packing.progress': '{packed} dari {total} sudah dikemas ({percent}%)', - 'packing.clearChecked': 'Hapus {count} yang dicentang', - 'packing.clearCheckedShort': 'Hapus {count}', - 'packing.suggestions': 'Saran', - 'packing.suggestionsTitle': 'Tambah Saran', - 'packing.allSuggested': 'Semua saran sudah ditambahkan', - 'packing.allPacked': 'Semua sudah dikemas!', - 'packing.addPlaceholder': 'Tambah item baru...', - 'packing.categoryPlaceholder': 'Kategori...', - 'packing.filterAll': 'Semua', - 'packing.filterOpen': 'Belum', - 'packing.filterDone': 'Selesai', - 'packing.emptyTitle': 'Daftar bawaan kosong', - 'packing.emptyHint': 'Tambah item atau gunakan saran', - 'packing.emptyFiltered': 'Tidak ada item yang cocok dengan filter ini', - 'packing.menuRename': 'Ganti Nama', - 'packing.menuCheckAll': 'Centang Semua', - 'packing.menuUncheckAll': 'Hapus Centang Semua', - 'packing.menuDeleteCat': 'Hapus Kategori', - 'packing.noMembers': 'Tidak ada anggota perjalanan', - 'packing.addItem': 'Tambah item', - 'packing.addItemPlaceholder': 'Nama item...', - 'packing.addCategory': 'Tambah kategori', - 'packing.newCategoryPlaceholder': 'Nama kategori (contoh: Pakaian)', - 'packing.applyTemplate': 'Terapkan template', - 'packing.template': 'Template', - 'packing.templateApplied': '{count} item ditambahkan dari template', - 'packing.templateError': 'Gagal menerapkan template', - 'packing.saveAsTemplate': 'Simpan sebagai template', - 'packing.templateName': 'Nama template', - 'packing.templateSaved': 'Daftar bawaan disimpan sebagai template', - 'packing.bags': 'Tas', - 'packing.noBag': 'Belum ditugaskan', - 'packing.totalWeight': 'Total berat', - 'packing.bagName': 'Nama tas...', - 'packing.addBag': 'Tambah tas', - 'packing.changeCategory': 'Ganti Kategori', - 'packing.confirm.clearChecked': 'Yakin ingin menghapus {count} item yang dicentang?', - 'packing.confirm.deleteCat': 'Yakin ingin menghapus kategori "{name}" beserta {count} item di dalamnya?', - 'packing.defaultCategory': 'Lainnya', - 'packing.toast.saveError': 'Gagal menyimpan', - 'packing.toast.deleteError': 'Gagal menghapus', - 'packing.toast.renameError': 'Gagal mengganti nama', - 'packing.toast.addError': 'Gagal menambahkan', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Paspor', category: 'Dokumen' }, - { name: 'KTP', category: 'Dokumen' }, - { name: 'Asuransi Perjalanan', category: 'Dokumen' }, - { name: 'Tiket Penerbangan', category: 'Dokumen' }, - { name: 'Kartu Kredit', category: 'Keuangan' }, - { name: 'Uang Tunai', category: 'Keuangan' }, - { name: 'Visa', category: 'Dokumen' }, - { name: 'Kaos', category: 'Pakaian' }, - { name: 'Celana', category: 'Pakaian' }, - { name: 'Pakaian Dalam', category: 'Pakaian' }, - { name: 'Kaos Kaki', category: 'Pakaian' }, - { name: 'Jaket', category: 'Pakaian' }, - { name: 'Pakaian Tidur', category: 'Pakaian' }, - { name: 'Pakaian Renang', category: 'Pakaian' }, - { name: 'Jas Hujan', category: 'Pakaian' }, - { name: 'Sepatu Nyaman', category: 'Pakaian' }, - { name: 'Sikat Gigi', category: 'Perlengkapan Mandi' }, - { name: 'Pasta Gigi', category: 'Perlengkapan Mandi' }, - { name: 'Sampo', category: 'Perlengkapan Mandi' }, - { name: 'Deodoran', category: 'Perlengkapan Mandi' }, - { name: 'Tabir Surya', category: 'Perlengkapan Mandi' }, - { name: 'Pisau Cukur', category: 'Perlengkapan Mandi' }, - { name: 'Charger', category: 'Elektronik' }, - { name: 'Power Bank', category: 'Elektronik' }, - { name: 'Headphone', category: 'Elektronik' }, - { name: 'Adaptor Perjalanan', category: 'Elektronik' }, - { name: 'Kamera', category: 'Elektronik' }, - { name: 'Obat Pereda Nyeri', category: 'Kesehatan' }, - { name: 'Plester', category: 'Kesehatan' }, - { name: 'Disinfektan', category: 'Kesehatan' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Bagikan Perjalanan', - 'members.inviteUser': 'Undang Pengguna', - 'members.selectUser': 'Pilih pengguna…', - 'members.invite': 'Undang', - 'members.allHaveAccess': 'Semua pengguna sudah punya akses.', - 'members.access': 'Akses', - 'members.person': 'orang', - 'members.persons': 'orang', - 'members.you': 'kamu', - 'members.owner': 'Pemilik', - 'members.leaveTrip': 'Keluar dari perjalanan', - 'members.removeAccess': 'Hapus akses', - 'members.confirmLeave': 'Keluar dari perjalanan? Kamu akan kehilangan akses.', - 'members.confirmRemove': 'Hapus akses untuk pengguna ini?', - 'members.loadError': 'Gagal memuat anggota', - 'members.added': 'ditambahkan', - 'members.addError': 'Gagal menambahkan', - 'members.removed': 'Anggota dihapus', - 'members.removeError': 'Gagal menghapus', - - // Categories (Admin) - 'categories.title': 'Kategori', - 'categories.subtitle': 'Kelola kategori untuk tempat', - 'categories.new': 'Kategori Baru', - 'categories.empty': 'Belum ada kategori', - 'categories.namePlaceholder': 'Nama kategori', - 'categories.icon': 'Ikon', - 'categories.color': 'Warna', - 'categories.customColor': 'Pilih warna kustom', - 'categories.preview': 'Pratinjau', - 'categories.defaultName': 'Kategori', - 'categories.update': 'Perbarui', - 'categories.create': 'Buat', - 'categories.confirm.delete': 'Hapus kategori? Tempat dalam kategori ini tidak akan dihapus.', - 'categories.toast.loadError': 'Gagal memuat kategori', - 'categories.toast.nameRequired': 'Harap masukkan nama', - 'categories.toast.updated': 'Kategori diperbarui', - 'categories.toast.created': 'Kategori dibuat', - 'categories.toast.saveError': 'Gagal menyimpan', - 'categories.toast.deleted': 'Kategori dihapus', - 'categories.toast.deleteError': 'Gagal menghapus', - - // Backup (Admin) - 'backup.title': 'Pencadangan Data', - 'backup.subtitle': 'Database dan semua file yang diunggah', - 'backup.refresh': 'Segarkan', - 'backup.upload': 'Unggah Cadangan', - 'backup.uploading': 'Mengunggah…', - 'backup.create': 'Buat Cadangan', - 'backup.creating': 'Membuat…', - 'backup.empty': 'Belum ada cadangan', - 'backup.createFirst': 'Buat cadangan pertama', - 'backup.download': 'Unduh', - 'backup.restore': 'Pulihkan', - 'backup.confirm.restore': 'Pulihkan cadangan "{name}"?\n\nSemua data saat ini akan digantikan dengan cadangan.', - 'backup.confirm.uploadRestore': 'Unggah dan pulihkan file cadangan "{name}"?\n\nSemua data saat ini akan ditimpa.', - 'backup.confirm.delete': 'Hapus cadangan "{name}"?', - 'backup.toast.loadError': 'Gagal memuat cadangan', - 'backup.toast.created': 'Cadangan berhasil dibuat', - 'backup.toast.createError': 'Gagal membuat cadangan', - 'backup.toast.restored': 'Cadangan dipulihkan. Halaman akan dimuat ulang…', - 'backup.toast.restoreError': 'Gagal memulihkan', - 'backup.toast.uploadError': 'Gagal mengunggah', - 'backup.toast.deleted': 'Cadangan dihapus', - 'backup.toast.deleteError': 'Gagal menghapus', - 'backup.toast.downloadError': 'Unduhan gagal', - 'backup.toast.settingsSaved': 'Pengaturan pencadangan otomatis disimpan', - 'backup.toast.settingsError': 'Gagal menyimpan pengaturan', - 'backup.auto.title': 'Cadangan Otomatis', - 'backup.auto.subtitle': 'Pencadangan otomatis sesuai jadwal', - 'backup.auto.enable': 'Aktifkan cadangan otomatis', - 'backup.auto.enableHint': 'Cadangan akan dibuat secara otomatis sesuai jadwal yang dipilih', - 'backup.auto.interval': 'Interval', - 'backup.auto.hour': 'Jalankan pada jam', - 'backup.auto.hourHint': 'Waktu lokal server (format {format})', - 'backup.auto.dayOfWeek': 'Hari dalam seminggu', - 'backup.auto.dayOfMonth': 'Tanggal dalam sebulan', - 'backup.auto.dayOfMonthHint': 'Dibatasi 1–28 agar kompatibel dengan semua bulan', - 'backup.auto.scheduleSummary': 'Jadwal', - 'backup.auto.summaryDaily': 'Setiap hari pukul {hour}:00', - 'backup.auto.summaryWeekly': 'Setiap {day} pukul {hour}:00', - 'backup.auto.summaryMonthly': 'Tanggal {day} setiap bulan pukul {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Cadangan otomatis dikonfigurasi melalui variabel lingkungan Docker. Untuk mengubah pengaturan ini, perbarui docker-compose.yml kamu dan restart container.', - 'backup.auto.copyEnv': 'Salin variabel env Docker', - 'backup.auto.envCopied': 'Variabel env Docker disalin ke clipboard', - 'backup.auto.keepLabel': 'Hapus cadangan lama setelah', - 'backup.dow.sunday': 'Min', - 'backup.dow.monday': 'Sen', - 'backup.dow.tuesday': 'Sel', - 'backup.dow.wednesday': 'Rab', - 'backup.dow.thursday': 'Kam', - 'backup.dow.friday': 'Jum', - 'backup.dow.saturday': 'Sab', - 'backup.interval.hourly': 'Per jam', - 'backup.interval.daily': 'Harian', - 'backup.interval.weekly': 'Mingguan', - 'backup.interval.monthly': 'Bulanan', - 'backup.keep.1day': '1 hari', - 'backup.keep.3days': '3 hari', - 'backup.keep.7days': '7 hari', - 'backup.keep.14days': '14 hari', - 'backup.keep.30days': '30 hari', - 'backup.keep.forever': 'Simpan selamanya', - - // Photos - 'photos.title': 'Foto', - 'photos.subtitle': '{count} foto untuk {trip}', - 'photos.dropHere': 'Lepas foto di sini...', - 'photos.dropHereActive': 'Lepas foto di sini', - 'photos.captionForAll': 'Keterangan (untuk semua)', - 'photos.captionPlaceholder': 'Keterangan opsional...', - 'photos.addCaption': 'Tambah keterangan...', - 'photos.allDays': 'Semua Hari', - 'photos.noPhotos': 'Belum ada foto', - 'photos.uploadHint': 'Unggah foto perjalananmu', - 'photos.clickToSelect': 'atau klik untuk memilih', - 'photos.linkPlace': 'Tautkan Tempat', - 'photos.noPlace': 'Tanpa Tempat', - 'photos.uploadN': 'Unggah {n} foto', - 'photos.linkDay': 'Tautkan Hari', - 'photos.noDay': 'Tanpa Hari', - 'photos.dayLabel': 'Hari {number}', - 'photos.photoSelected': 'Foto dipilih', - 'photos.photosSelected': 'Foto dipilih', - 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · hingga 30 foto', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Pulihkan Cadangan?', - 'backup.restoreWarning': 'Semua data saat ini (perjalanan, tempat, pengguna, unggahan) akan digantikan secara permanen oleh cadangan ini. Tindakan ini tidak dapat dibatalkan.', - 'backup.restoreTip': 'Tips: Buat cadangan kondisi saat ini sebelum memulihkan.', - 'backup.restoreConfirm': 'Ya, pulihkan', - - // PDF - 'pdf.travelPlan': 'Rencana Perjalanan', - 'pdf.planned': 'Direncanakan', - 'pdf.costLabel': 'Biaya EUR', - 'pdf.preview': 'Pratinjau PDF', - 'pdf.saveAsPdf': 'Simpan sebagai PDF', - - // Planner - 'planner.places': 'Tempat', - 'planner.bookings': 'Pemesanan', - 'planner.packingList': 'Daftar Bawaan', - 'planner.documents': 'Dokumen', - 'planner.dayPlan': 'Rencana Hari', - 'planner.reservations': 'Reservasi', - 'planner.minTwoPlaces': 'Dibutuhkan minimal 2 tempat dengan koordinat', - 'planner.noGeoPlaces': 'Tidak ada tempat dengan koordinat yang tersedia', - 'planner.routeCalculated': 'Rute berhasil dihitung', - 'planner.routeCalcFailed': 'Rute tidak dapat dihitung', - 'planner.routeError': 'Terjadi kesalahan saat menghitung rute', - 'planner.icsExportFailed': 'Ekspor ICS gagal', - 'planner.routeOptimized': 'Rute dioptimalkan', - 'planner.reservationUpdated': 'Reservasi diperbarui', - 'planner.reservationAdded': 'Reservasi ditambahkan', - 'planner.confirmDeleteReservation': 'Hapus reservasi?', - 'planner.reservationDeleted': 'Reservasi dihapus', - 'planner.days': 'Hari', - 'planner.allPlaces': 'Semua Tempat', - 'planner.totalPlaces': '{n} tempat total', - 'planner.noDaysPlanned': 'Belum ada hari yang direncanakan', - 'planner.editTrip': 'Edit perjalanan \u2192', - 'planner.placeOne': '1 tempat', - 'planner.placeN': '{n} tempat', - 'planner.addNote': 'Tambah catatan', - 'planner.noEntries': 'Tidak ada entri untuk hari ini', - 'planner.addPlace': 'Tambah tempat/aktivitas', - 'planner.addPlaceShort': '+ Tambah tempat/aktivitas', - 'planner.resPending': 'Reservasi tertunda · ', - 'planner.resConfirmed': 'Reservasi dikonfirmasi · ', - 'planner.notePlaceholder': 'Catatan\u2026', - 'planner.noteTimePlaceholder': 'Waktu (opsional)', - 'planner.noteExamplePlaceholder': 'mis. S3 pukul 14:30 dari stasiun pusat, feri dari dermaga 7, istirahat makan siang\u2026', - 'planner.totalCost': 'Total biaya', - 'planner.searchPlaces': 'Cari tempat\u2026', - 'planner.allCategories': 'Semua Kategori', - 'planner.noPlacesFound': 'Tempat tidak ditemukan', - 'planner.addFirstPlace': 'Tambah tempat pertama', - 'planner.noReservations': 'Tidak ada reservasi', - 'planner.addFirstReservation': 'Tambah reservasi pertama', - 'planner.new': 'Baru', - 'planner.addToDay': '+ Hari', - 'planner.calculating': 'Menghitung\u2026', - 'planner.route': 'Rute', - 'planner.optimize': 'Optimalkan', - 'planner.openGoogleMaps': 'Buka di Google Maps', - 'planner.selectDayHint': 'Pilih hari dari daftar kiri untuk melihat rencana hari', - 'planner.noPlacesForDay': 'Belum ada tempat untuk hari ini', - 'planner.addPlacesLink': 'Tambah tempat \u2192', - 'planner.minTotal': 'total min.', - 'planner.noReservation': 'Tidak ada reservasi', - 'planner.removeFromDay': 'Hapus dari hari', - 'planner.addToThisDay': 'Tambah ke hari ini', - 'planner.overview': 'Ikhtisar', - 'planner.noDays': 'Belum ada hari', - 'planner.editTripToAddDays': 'Edit perjalanan untuk menambah hari', - 'planner.dayCount': '{n} Hari', - 'planner.clickToUnlock': 'Klik untuk membuka kunci', - 'planner.keepPosition': 'Pertahankan posisi saat mengoptimalkan rute', - 'planner.dayDetails': 'Detail hari', - 'planner.dayN': 'Hari {n}', - - // Dashboard Stats - 'stats.countries': 'Negara', - 'stats.cities': 'Kota', - 'stats.trips': 'Perjalanan', - 'stats.places': 'Tempat', - 'stats.worldProgress': 'Progres Dunia', - 'stats.visited': 'dikunjungi', - 'stats.remaining': 'tersisa', - 'stats.visitedCountries': 'Negara yang Dikunjungi', - - // Day Detail Panel - 'day.precipProb': 'Peluang hujan', - 'day.precipitation': 'Curah hujan', - 'day.wind': 'Angin', - 'day.sunrise': 'Matahari terbit', - 'day.sunset': 'Matahari terbenam', - 'day.hourlyForecast': 'Prakiraan Per Jam', - 'day.climateHint': 'Rata-rata historis — prakiraan nyata tersedia dalam 16 hari dari tanggal ini.', - 'day.noWeather': 'Data cuaca tidak tersedia. Tambahkan tempat dengan koordinat.', - 'day.overview': 'Ikhtisar Harian', - 'day.accommodation': 'Akomodasi', - 'day.addAccommodation': 'Tambah akomodasi', - 'day.hotelDayRange': 'Terapkan ke hari', - 'day.noPlacesForHotel': 'Tambahkan tempat ke perjalananmu terlebih dahulu', - 'day.allDays': 'Semua', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Sampai', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Konfirmasi', - 'day.editAccommodation': 'Edit akomodasi', - 'day.reservations': 'Reservasi', - - // Photos / Immich - 'memories.title': 'Foto', - 'memories.notConnected': '{provider_name} belum terhubung', - 'memories.notConnectedHint': 'Hubungkan instans {provider_name} kamu di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.', - 'memories.notConnectedMultipleHint': 'Hubungkan salah satu penyedia foto berikut: {provider_names} di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.', - 'memories.noDates': 'Tambahkan tanggal ke perjalananmu untuk memuat foto.', - 'memories.noPhotos': 'Foto tidak ditemukan', - 'memories.noPhotosHint': 'Tidak ada foto ditemukan di {provider_name} untuk rentang tanggal perjalanan ini.', - 'memories.photosFound': 'foto', - 'memories.fromOthers': 'dari orang lain', - 'memories.sharePhotos': 'Bagikan foto', - 'memories.sharing': 'Berbagi', - 'memories.reviewTitle': 'Tinjau foto kamu', - 'memories.reviewHint': 'Klik foto untuk mengecualikannya dari berbagi.', - 'memories.shareCount': 'Bagikan {count} foto', - //------------------------- - //todo section - 'memories.providerUrl': 'URL Server', - 'memories.providerApiKey': 'API Key', - 'memories.providerUsername': 'Nama pengguna', - 'memories.providerPassword': 'Kata sandi', - 'memories.providerOTP': 'Kode MFA (jika diaktifkan)', - 'memories.skipSSLVerification': 'Lewati verifikasi sertifikat SSL', - 'memories.immichAutoUpload': 'Salin foto journey ke Immich saat diunggah', - 'memories.providerUrlHintSynology': 'Sertakan path aplikasi Photos di URL, mis. https://nas:5001/photo', - 'memories.testConnection': 'Uji koneksi', - 'memories.testShort': 'Uji', - 'memories.testFirst': 'Uji koneksi terlebih dahulu', - 'memories.connected': 'Terhubung', - 'memories.disconnected': 'Tidak terhubung', - 'memories.connectionSuccess': 'Terhubung ke {provider_name}', - 'memories.connectionError': 'Tidak dapat terhubung ke {provider_name}', - 'memories.saved': 'Pengaturan {provider_name} disimpan', - 'memories.providerDisconnectedBanner': 'Koneksi {provider_name} kamu terputus. Hubungkan ulang di Pengaturan untuk melihat foto.', - 'memories.saveError': 'Tidak dapat menyimpan pengaturan {provider_name}', - //------------------------ - 'memories.addPhotos': 'Tambah foto', - 'memories.linkAlbum': 'Tautkan Album', - 'memories.selectAlbum': 'Pilih Album {provider_name}', - 'memories.selectAlbumMultiple': 'Pilih Album', - 'memories.noAlbums': 'Tidak ada album ditemukan', - 'memories.syncAlbum': 'Sinkronkan album', - 'memories.unlinkAlbum': 'Putuskan tautan album', - 'memories.photos': 'foto', - 'memories.selectPhotos': 'Pilih foto dari {provider_name}', - 'memories.selectPhotosMultiple': 'Pilih Foto', - 'memories.selectHint': 'Ketuk foto untuk memilihnya.', - 'memories.selected': 'dipilih', - 'memories.addSelected': 'Tambah {count} foto', - 'memories.alreadyAdded': 'Sudah ditambahkan', - 'memories.private': 'Privat', - 'memories.stopSharing': 'Berhenti berbagi', - 'memories.oldest': 'Terlama dulu', - 'memories.newest': 'Terbaru dulu', - 'memories.allLocations': 'Semua lokasi', - 'memories.tripDates': 'Tanggal perjalanan', - 'memories.allPhotos': 'Semua foto', - 'memories.confirmShareTitle': 'Bagikan ke anggota perjalanan?', - 'memories.confirmShareHint': '{count} foto akan terlihat oleh semua anggota perjalanan ini. Kamu bisa membuat foto tertentu menjadi privat nanti.', - 'memories.confirmShareButton': 'Bagikan foto', - 'memories.error.loadAlbums': 'Gagal memuat album', - 'memories.error.linkAlbum': 'Gagal menautkan album', - 'memories.error.unlinkAlbum': 'Gagal memutuskan tautan album', - 'memories.error.syncAlbum': 'Gagal menyinkronkan album', - 'memories.error.loadPhotos': 'Gagal memuat foto', - 'memories.error.addPhotos': 'Gagal menambahkan foto', - 'memories.error.removePhoto': 'Gagal menghapus foto', - 'memories.error.toggleSharing': 'Gagal memperbarui berbagi', - 'memories.saveRouteNotConfigured': 'Rute simpan tidak dikonfigurasi untuk penyedia ini', - 'memories.testRouteNotConfigured': 'Rute uji tidak dikonfigurasi untuk penyedia ini', - 'memories.fillRequiredFields': 'Harap isi semua kolom yang wajib diisi', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Catatan', - 'collab.tabs.polls': 'Polling', - 'collab.whatsNext.title': 'Berikutnya', - 'collab.whatsNext.today': 'Hari ini', - 'collab.whatsNext.tomorrow': 'Besok', - 'collab.whatsNext.empty': 'Tidak ada aktivitas mendatang', - 'collab.whatsNext.until': 'sampai', - 'collab.whatsNext.emptyHint': 'Aktivitas dengan waktu akan muncul di sini', - 'collab.chat.send': 'Kirim', - 'collab.chat.placeholder': 'Ketik pesan...', - 'collab.chat.empty': 'Mulai percakapan', - 'collab.chat.emptyHint': 'Pesan dibagikan kepada semua anggota perjalanan', - 'collab.chat.emptyDesc': 'Bagikan ide, rencana, dan update dengan grup perjalananmu', - 'collab.chat.today': 'Hari ini', - 'collab.chat.yesterday': 'Kemarin', - 'collab.chat.deletedMessage': 'menghapus pesan', - 'collab.chat.reply': 'Balas', - 'collab.chat.loadMore': 'Muat pesan lebih lama', - 'collab.chat.justNow': 'baru saja', - 'collab.chat.minutesAgo': '{n}m lalu', - 'collab.chat.hoursAgo': '{n}j lalu', - 'collab.notes.title': 'Catatan', - 'collab.notes.new': 'Catatan Baru', - 'collab.notes.empty': 'Belum ada catatan', - 'collab.notes.emptyHint': 'Mulai catat ide dan rencana', - 'collab.notes.all': 'Semua', - 'collab.notes.titlePlaceholder': 'Judul catatan', - 'collab.notes.contentPlaceholder': 'Tulis sesuatu...', - 'collab.notes.categoryPlaceholder': 'Kategori', - 'collab.notes.newCategory': 'Kategori baru...', - 'collab.notes.category': 'Kategori', - 'collab.notes.noCategory': 'Tanpa kategori', - 'collab.notes.color': 'Warna', - 'collab.notes.save': 'Simpan', - 'collab.notes.cancel': 'Batal', - 'collab.notes.edit': 'Edit', - 'collab.notes.delete': 'Hapus', - 'collab.notes.pin': 'Sematkan', - 'collab.notes.unpin': 'Lepas sematan', - 'collab.notes.daysAgo': '{n}h lalu', - 'collab.notes.categorySettings': 'Kelola Kategori', - 'collab.notes.create': 'Buat', - 'collab.notes.website': 'Website', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Lampirkan file', - 'collab.notes.noCategoriesYet': 'Belum ada kategori', - 'collab.notes.emptyDesc': 'Buat catatan untuk memulai', - 'collab.polls.title': 'Polling', - 'collab.polls.new': 'Polling Baru', - 'collab.polls.empty': 'Belum ada polling', - 'collab.polls.emptyHint': 'Tanyakan ke grup dan voting bersama', - 'collab.polls.question': 'Pertanyaan', - 'collab.polls.questionPlaceholder': 'Apa yang harus kita lakukan?', - 'collab.polls.addOption': '+ Tambah pilihan', - 'collab.polls.optionPlaceholder': 'Pilihan {n}', - 'collab.polls.create': 'Buat Polling', - 'collab.polls.close': 'Tutup', - 'collab.polls.closed': 'Ditutup', - 'collab.polls.votes': '{n} suara', - 'collab.polls.vote': '{n} suara', - 'collab.polls.multipleChoice': 'Pilihan ganda', - 'collab.polls.multiChoice': 'Pilihan ganda', - 'collab.polls.deadline': 'Tenggat waktu', - 'collab.polls.option': 'Pilihan', - 'collab.polls.options': 'Pilihan', - 'collab.polls.delete': 'Hapus', - 'collab.polls.closedSection': 'Ditutup', - - // Permissions - 'admin.tabs.permissions': 'Izin', - 'perm.title': 'Pengaturan Izin', - 'perm.subtitle': 'Kendalikan siapa yang bisa melakukan tindakan di aplikasi', - 'perm.saved': 'Pengaturan izin disimpan', - 'perm.resetDefaults': 'Kembalikan ke default', - 'perm.customized': 'dikustomisasi', - 'perm.level.admin': 'Hanya Admin', - 'perm.level.tripOwner': 'Pemilik perjalanan', - 'perm.level.tripMember': 'Anggota perjalanan', - 'perm.level.everybody': 'Semua orang', - 'perm.cat.trip': 'Manajemen Perjalanan', - 'perm.cat.members': 'Manajemen Anggota', - 'perm.cat.files': 'File', - 'perm.cat.content': 'Konten & Jadwal', - 'perm.cat.extras': 'Anggaran, Perlengkapan & Kolaborasi', - 'perm.action.trip_create': 'Buat perjalanan', - 'perm.action.trip_edit': 'Edit detail perjalanan', - 'perm.action.trip_delete': 'Hapus perjalanan', - 'perm.action.trip_archive': 'Arsipkan / batalkan arsip perjalanan', - 'perm.action.trip_cover_upload': 'Unggah gambar sampul', - 'perm.action.member_manage': 'Tambah / hapus anggota', - 'perm.action.file_upload': 'Unggah file', - 'perm.action.file_edit': 'Edit metadata file', - 'perm.action.file_delete': 'Hapus file', - 'perm.action.place_edit': 'Tambah / edit / hapus tempat', - 'perm.action.day_edit': 'Edit hari, catatan & penugasan', - 'perm.action.reservation_edit': 'Kelola reservasi', - 'perm.action.budget_edit': 'Kelola anggaran', - 'perm.action.packing_edit': 'Kelola daftar perlengkapan', - 'perm.action.collab_edit': 'Kolaborasi (catatan, polling, chat)', - 'perm.action.share_manage': 'Kelola tautan berbagi', - 'perm.actionHint.trip_create': 'Siapa yang bisa membuat perjalanan baru', - 'perm.actionHint.trip_edit': 'Siapa yang bisa mengubah nama, tanggal, deskripsi, dan mata uang perjalanan', - 'perm.actionHint.trip_delete': 'Siapa yang bisa menghapus perjalanan secara permanen', - 'perm.actionHint.trip_archive': 'Siapa yang bisa mengarsipkan atau membatalkan arsip perjalanan', - 'perm.actionHint.trip_cover_upload': 'Siapa yang bisa mengunggah atau mengganti gambar sampul', - 'perm.actionHint.member_manage': 'Siapa yang bisa mengundang atau menghapus anggota perjalanan', - 'perm.actionHint.file_upload': 'Siapa yang bisa mengunggah file ke perjalanan', - 'perm.actionHint.file_edit': 'Siapa yang bisa mengedit deskripsi dan tautan file', - 'perm.actionHint.file_delete': 'Siapa yang bisa memindahkan file ke sampah atau menghapusnya secara permanen', - 'perm.actionHint.place_edit': 'Siapa yang bisa menambah, mengedit, atau menghapus tempat', - 'perm.actionHint.day_edit': 'Siapa yang bisa mengedit hari, catatan hari, dan penugasan tempat', - 'perm.actionHint.reservation_edit': 'Siapa yang bisa membuat, mengedit, atau menghapus reservasi', - 'perm.actionHint.budget_edit': 'Siapa yang bisa membuat, mengedit, atau menghapus item anggaran', - 'perm.actionHint.packing_edit': 'Siapa yang bisa mengelola item perlengkapan dan tas', - 'perm.actionHint.collab_edit': 'Siapa yang bisa membuat catatan, polling, dan mengirim pesan', - 'perm.actionHint.share_manage': 'Siapa yang bisa membuat atau menghapus tautan berbagi publik', - - // Undo - 'undo.button': 'Batalkan', - 'undo.tooltip': 'Batalkan: {action}', - 'undo.assignPlace': 'Tempat ditambahkan ke hari', - 'undo.removeAssignment': 'Tempat dihapus dari hari', - 'undo.reorder': 'Tempat diurutkan ulang', - 'undo.optimize': 'Rute dioptimalkan', - 'undo.deletePlace': 'Tempat dihapus', - 'undo.deletePlaces': 'Tempat dihapus', - 'undo.moveDay': 'Tempat dipindah ke hari lain', - 'undo.lock': 'Kunci tempat diubah', - 'undo.importGpx': 'Impor GPX', - 'undo.importKeyholeMarkup': 'Impor KMZ/KML', - 'undo.importGoogleList': 'Impor Google Maps', - 'undo.importNaverList': 'Impor Naver Maps', - 'undo.addPlace': 'Tempat ditambahkan', - 'undo.done': 'Dibatalkan: {action}', - - // Notifications - 'notifications.title': 'Notifikasi', - 'notifications.markAllRead': 'Tandai semua dibaca', - 'notifications.deleteAll': 'Hapus semua', - 'notifications.showAll': 'Tampilkan semua notifikasi', - 'notifications.empty': 'Tidak ada notifikasi', - 'notifications.emptyDescription': 'Semuanya sudah terbaca!', - 'notifications.all': 'Semua', - 'notifications.unreadOnly': 'Belum dibaca', - 'notifications.markRead': 'Tandai sudah dibaca', - 'notifications.markUnread': 'Tandai belum dibaca', - 'notifications.delete': 'Hapus', - 'notifications.system': 'Sistem', - 'notifications.synologySessionCleared.title': 'Synology Photos terputus', - 'notifications.synologySessionCleared.text': 'Server atau akun kamu berubah — buka Pengaturan untuk menguji koneksi lagi.', - - // Notification test keys (dev only) - 'notifications.versionAvailable.title': 'Pembaruan Tersedia', - 'notifications.versionAvailable.text': 'TREK {version} kini tersedia.', - 'notifications.versionAvailable.button': 'Lihat Detail', - 'notifications.test.title': 'Notifikasi uji dari {actor}', - 'notifications.test.text': 'Ini adalah notifikasi uji sederhana.', - 'notifications.test.booleanTitle': '{actor} meminta persetujuanmu', - 'notifications.test.booleanText': 'Ini adalah notifikasi uji boolean. Pilih tindakan di bawah.', - 'notifications.test.accept': 'Setujui', - 'notifications.test.decline': 'Tolak', - 'notifications.test.navigateTitle': 'Cek sesuatu', - 'notifications.test.navigateText': 'Ini adalah notifikasi uji navigasi.', - 'notifications.test.goThere': 'Ke sana', - 'notifications.test.adminTitle': 'Siaran Admin', - 'notifications.test.adminText': '{actor} mengirim notifikasi uji ke semua admin.', - 'notifications.test.tripTitle': '{actor} memposting di perjalananmu', - 'notifications.test.tripText': 'Notifikasi uji untuk perjalanan "{trip}".', - - // Todo - 'todo.subtab.packing': 'Daftar Perlengkapan', - 'todo.subtab.todo': 'Tugas', - 'todo.completed': 'selesai', - 'todo.filter.all': 'Semua', - 'todo.filter.open': 'Terbuka', - 'todo.filter.done': 'Selesai', - 'todo.uncategorized': 'Tanpa kategori', - 'todo.namePlaceholder': 'Nama tugas', - 'todo.descriptionPlaceholder': 'Deskripsi (opsional)', - 'todo.unassigned': 'Belum ditugaskan', - 'todo.noCategory': 'Tanpa kategori', - 'todo.hasDescription': 'Ada deskripsi', - 'todo.addItem': 'Tugas baru', - 'todo.sidebar.sortBy': 'Urutkan', - 'todo.priority': 'Prioritas', - 'todo.newCategoryLabel': 'baru', - 'budget.categoriesLabel': 'kategori', - 'todo.newCategory': 'Nama kategori', - 'todo.addCategory': 'Tambah kategori', - 'todo.newItem': 'Tugas baru', - 'todo.empty': 'Belum ada tugas. Tambah tugas untuk memulai!', - 'todo.filter.my': 'Tugasku', - 'todo.filter.overdue': 'Terlambat', - 'todo.sidebar.tasks': 'Tugas', - 'todo.sidebar.categories': 'Kategori', - 'todo.detail.title': 'Tugas', - 'todo.detail.description': 'Deskripsi', - 'todo.detail.category': 'Kategori', - 'todo.detail.dueDate': 'Tenggat waktu', - 'todo.detail.assignedTo': 'Ditugaskan ke', - 'todo.detail.delete': 'Hapus', - 'todo.detail.save': 'Simpan perubahan', - 'todo.sortByPrio': 'Prioritas', - 'todo.detail.priority': 'Prioritas', - 'todo.detail.noPriority': 'Tidak ada', - 'todo.detail.create': 'Buat tugas', - - // Notifications — dev test events - 'notif.test.title': '[Test] Notifikasi', - 'notif.test.simple.text': 'Ini adalah notifikasi uji sederhana.', - 'notif.test.boolean.text': 'Apakah kamu menerima notifikasi uji ini?', - 'notif.test.navigate.text': 'Klik di bawah untuk pergi ke dasbor.', - - // Notifications - 'notif.trip_invite.title': 'Undangan Perjalanan', - 'notif.trip_invite.text': '{actor} mengundangmu ke {trip}', - 'notif.booking_change.title': 'Pemesanan Diperbarui', - 'notif.booking_change.text': '{actor} memperbarui pemesanan di {trip}', - 'notif.trip_reminder.title': 'Pengingat Perjalanan', - 'notif.trip_reminder.text': 'Perjalananmu {trip} akan segera dimulai!', - 'notif.todo_due.title': 'Tugas jatuh tempo', - 'notif.todo_due.text': '{todo} di {trip} jatuh tempo pada {due}', - 'notif.vacay_invite.title': 'Undangan Vacay Fusion', - 'notif.vacay_invite.text': '{actor} mengundangmu untuk menggabungkan rencana liburan', - 'notif.photos_shared.title': 'Foto Dibagikan', - 'notif.photos_shared.text': '{actor} membagikan {count} foto di {trip}', - 'notif.collab_message.title': 'Pesan Baru', - 'notif.collab_message.text': '{actor} mengirim pesan di {trip}', - 'notif.packing_tagged.title': 'Penugasan Perlengkapan', - 'notif.packing_tagged.text': '{actor} menugaskanmu ke {category} di {trip}', - 'notif.version_available.title': 'Versi Baru Tersedia', - 'notif.version_available.text': 'TREK {version} kini tersedia', - 'notif.action.view_trip': 'Lihat Perjalanan', - 'notif.action.view_collab': 'Lihat Pesan', - 'notif.action.view_packing': 'Lihat Perlengkapan', - 'notif.action.view_photos': 'Lihat Foto', - 'notif.action.view_vacay': 'Lihat Vacay', - 'notif.action.view_admin': 'Ke Admin', - 'notif.action.view': 'Lihat', - 'notif.action.accept': 'Terima', - 'notif.action.decline': 'Tolak', - 'notif.generic.title': 'Notifikasi', - 'notif.generic.text': 'Kamu punya notifikasi baru', - 'notif.dev.unknown_event.title': '[DEV] Event Tidak Dikenal', - 'notif.dev.unknown_event.text': 'Tipe event "{event}" tidak terdaftar di EVENT_NOTIFICATION_CONFIG', - - // Journey addon - 'journey.search.placeholder': 'Cari perjalanan…', - 'journey.search.noResults': 'Tidak ada perjalanan yang cocok dengan "{query}"', - 'journey.title': 'Journey', - 'journey.subtitle': 'Lacak perjalananmu saat terjadi', - 'journey.new': 'Journey Baru', - 'journey.create': 'Buat', - 'journey.titlePlaceholder': 'Ke mana kamu pergi?', - 'journey.empty': 'Belum ada journey', - 'journey.emptyHint': 'Mulai mendokumentasikan perjalananmu berikutnya', - 'journey.deleted': 'Journey dihapus', - 'journey.createError': 'Tidak dapat membuat journey', - 'journey.deleteError': 'Tidak dapat menghapus journey', - 'journey.deleteConfirmTitle': 'Hapus', - 'journey.deleteConfirmMessage': 'Hapus "{title}"? Tindakan ini tidak dapat dibatalkan.', - 'journey.deleteConfirmGeneric': 'Apakah kamu yakin ingin menghapus ini?', - 'journey.notFound': 'Journey tidak ditemukan', - 'journey.photos': 'Foto', - 'journey.timelineEmpty': 'Belum ada persinggahan', - 'journey.timelineEmptyHint': 'Tambahkan check-in atau tulis entri jurnal untuk memulai', - 'journey.status.draft': 'Draf', - 'journey.status.active': 'Aktif', - 'journey.status.completed': 'Selesai', - 'journey.status.upcoming': 'Mendatang', - 'journey.status.archived': 'Diarsipkan', - 'journey.checkin.add': 'Check in', - 'journey.checkin.namePlaceholder': 'Nama lokasi', - 'journey.checkin.notesPlaceholder': 'Catatan (opsional)', - 'journey.checkin.save': 'Simpan', - 'journey.checkin.error': 'Tidak dapat menyimpan check-in', - 'journey.entry.add': 'Jurnal', - 'journey.entry.edit': 'Edit entri', - 'journey.entry.titlePlaceholder': 'Judul (opsional)', - 'journey.entry.bodyPlaceholder': 'Apa yang terjadi hari ini?', - 'journey.entry.save': 'Simpan', - 'journey.entry.error': 'Tidak dapat menyimpan entri', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Unggah gagal', - 'journey.share.share': 'Bagikan', - 'journey.share.public': 'Publik', - 'journey.share.linkCopied': 'Tautan publik disalin', - 'journey.share.disabled': 'Berbagi publik dinonaktifkan', - 'journey.editor.titlePlaceholder': 'Beri nama momen ini...', - 'journey.editor.bodyPlaceholder': 'Ceritakan kisah hari ini...', - 'journey.editor.placePlaceholder': 'Lokasi (opsional)', - 'journey.editor.tagsPlaceholder': 'Tag: permata tersembunyi, makan terbaik, wajib dikunjungi lagi...', - 'journey.visibility.private': 'Pribadi', - 'journey.visibility.shared': 'Dibagikan', - 'journey.visibility.public': 'Publik', - 'journey.emptyState.title': 'Kisahmu dimulai di sini', - 'journey.emptyState.subtitle': 'Check in di suatu tempat atau tulis entri jurnal pertamamu', - - // Journey Frontpage - 'journey.frontpage.subtitle': 'Ubah perjalananmu menjadi kisah yang tak terlupakan', - 'journey.frontpage.createJourney': 'Buat Journey', - 'journey.frontpage.activeJourney': 'Journey Aktif', - 'journey.frontpage.allJourneys': 'Semua Journey', - 'journey.frontpage.journeys': 'journey', - 'journey.frontpage.createNew': 'Buat Journey baru', - 'journey.frontpage.createNewSub': 'Pilih perjalanan, tulis cerita, bagikan petualanganmu', - 'journey.frontpage.live': 'Langsung', - 'journey.frontpage.synced': 'Tersinkron', - 'journey.frontpage.continueWriting': 'Lanjutkan menulis', - 'journey.frontpage.updated': 'Diperbarui {time}', - 'journey.frontpage.suggestionLabel': 'Perjalanan baru saja selesai', - 'journey.frontpage.suggestionText': 'Ubah {title} menjadi Journey', - 'journey.frontpage.dismiss': 'Tutup', - 'journey.frontpage.journeyName': 'Nama Journey', - 'journey.frontpage.namePlaceholder': 'mis. Asia Tenggara 2026', - 'journey.frontpage.selectTrips': 'Pilih Perjalanan', - 'journey.frontpage.tripsSelected': 'perjalanan dipilih', - 'journey.frontpage.trips': 'perjalanan', - 'journey.frontpage.placesImported': 'tempat akan diimpor', - 'journey.frontpage.places': 'tempat', - - // Journey Detail - 'journey.detail.backToJourney': 'Kembali ke Journey', - 'journey.detail.syncedWithTrips': 'Tersinkron dengan Perjalanan', - 'journey.detail.addEntry': 'Tambah Entri', - 'journey.detail.newEntry': 'Entri Baru', - 'journey.detail.editEntry': 'Edit Entri', - 'journey.detail.noEntries': 'Belum ada entri', - 'journey.detail.noEntriesHint': 'Tambahkan perjalanan untuk mulai dengan entri kerangka', - 'journey.detail.noPhotos': 'Belum ada foto', - 'journey.detail.noPhotosHint': 'Unggah foto ke entri atau jelajahi galeri Immich/Synology-mu', - 'journey.detail.journeyStats': 'Statistik Journey', - 'journey.detail.syncedTrips': 'Perjalanan Tersinkron', - 'journey.detail.noTripsLinked': 'Belum ada perjalanan yang ditautkan', - 'journey.detail.contributors': 'Kontributor', - 'journey.detail.readMore': 'Baca selengkapnya', - 'journey.detail.prosCons': 'Pro & Kontra', - 'journey.detail.photos': 'foto', - 'journey.detail.day': 'Hari {number}', - 'journey.detail.places': 'tempat', - - // Journey Detail — Stats - 'journey.stats.days': 'Hari', - 'journey.stats.cities': 'Kota', - 'journey.stats.entries': 'Entri', - 'journey.stats.photos': 'Foto', - 'journey.stats.places': 'Tempat', - 'journey.skeletons.show': 'Tampilkan saran', - 'journey.skeletons.hide': 'Sembunyikan saran', - - // Journey Detail — Verdict - 'journey.verdict.lovedIt': 'Sangat suka', - 'journey.verdict.couldBeBetter': 'Bisa lebih baik', - - // Journey Detail — Synced badge - 'journey.synced.places': 'tempat', - 'journey.synced.synced': 'tersinkron', - - // Journey Entry Editor - 'journey.editor.discardChangesConfirm': 'Anda memiliki perubahan yang belum disimpan. Buang?', - 'journey.editor.uploadFailed': 'Gagal mengunggah foto', - 'journey.editor.uploadPhotos': 'Unggah foto', - 'journey.editor.uploading': 'Mengunggah...', - 'journey.editor.uploadingProgress': 'Mengunggah {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} dari {total} foto gagal — simpan lagi untuk mencoba ulang', - 'journey.editor.fromGallery': 'Dari Galeri', - 'journey.editor.allPhotosAdded': 'Semua foto sudah ditambahkan', - 'journey.editor.writeStory': 'Tulis kisahmu...', - 'journey.editor.prosCons': 'Pro & Kontra', - 'journey.editor.pros': 'Pro', - 'journey.editor.cons': 'Kontra', - 'journey.editor.proPlaceholder': 'Sesuatu yang bagus...', - 'journey.editor.conPlaceholder': 'Tidak begitu bagus...', - 'journey.editor.addAnother': 'Tambah lagi', - 'journey.editor.date': 'Tanggal', - 'journey.editor.location': 'Lokasi', - 'journey.editor.searchLocation': 'Cari lokasi...', - 'journey.editor.mood': 'Suasana Hati', - 'journey.editor.weather': 'Cuaca', - 'journey.editor.photoFirst': '1.', - 'journey.editor.makeFirst': 'Jadikan ke-1', - 'journey.editor.searching': 'Mencari...', - - // Journey Entry — Moods - 'journey.mood.amazing': 'Luar biasa', - 'journey.mood.good': 'Baik', - 'journey.mood.neutral': 'Biasa', - 'journey.mood.rough': 'Berat', - - // Journey Entry — Weather - 'journey.weather.sunny': 'Cerah', - 'journey.weather.partly': 'Berawan sebagian', - 'journey.weather.cloudy': 'Mendung', - 'journey.weather.rainy': 'Hujan', - 'journey.weather.stormy': 'Badai', - 'journey.weather.cold': 'Bersalju', - - // Journey — Trip Linking - 'journey.trips.linkTrip': 'Tautkan Perjalanan', - 'journey.trips.searchTrip': 'Cari Perjalanan', - 'journey.trips.searchPlaceholder': 'Nama perjalanan atau tujuan...', - 'journey.trips.noTripsAvailable': 'Tidak ada perjalanan tersedia', - 'journey.trips.link': 'Tautkan', - 'journey.trips.tripLinked': 'Perjalanan ditautkan', - 'journey.trips.linkFailed': 'Gagal menautkan perjalanan', - 'journey.trips.addTrip': 'Tambah Perjalanan', - 'journey.trips.unlinkTrip': 'Lepas Tautan Perjalanan', - 'journey.trips.unlinkMessage': 'Lepas tautan "{title}"? Semua entri dan foto yang tersinkron dari perjalanan ini akan dihapus permanen. Tindakan ini tidak dapat dibatalkan.', - 'journey.trips.unlink': 'Lepas Tautan', - 'journey.trips.tripUnlinked': 'Tautan perjalanan dilepas', - 'journey.trips.unlinkFailed': 'Gagal melepas tautan perjalanan', - 'journey.trips.noTripsLinkedSettings': 'Tidak ada perjalanan yang ditautkan', - - // Journey — Contributors - 'journey.contributors.invite': 'Undang Kontributor', - 'journey.contributors.searchUser': 'Cari Pengguna', - 'journey.contributors.searchPlaceholder': 'Nama pengguna atau email...', - 'journey.contributors.noUsers': 'Tidak ada pengguna ditemukan', - 'journey.contributors.role': 'Peran', - 'journey.contributors.added': 'Kontributor ditambahkan', - 'journey.contributors.addFailed': 'Gagal menambahkan kontributor', - - // Journey — Share - 'journey.share.publicShare': 'Berbagi Publik', - 'journey.share.createLink': 'Buat tautan berbagi', - 'journey.share.linkCreated': 'Tautan berbagi dibuat', - 'journey.share.createFailed': 'Gagal membuat tautan', - 'journey.share.copy': 'Salin', - 'journey.share.copied': 'Disalin!', - 'journey.share.timeline': 'Linimasa', - 'journey.share.gallery': 'Galeri', - 'journey.share.map': 'Peta', - 'journey.share.removeLink': 'Hapus tautan berbagi', - 'journey.share.linkDeleted': 'Tautan berbagi dihapus', - 'journey.share.deleteFailed': 'Gagal menghapus', - 'journey.share.updateFailed': 'Gagal memperbarui', - - // Journey — Invite - 'journey.invite.role': 'Peran', - 'journey.invite.viewer': 'Penonton', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Undang', - 'journey.invite.inviting': 'Mengundang...', - - // Journey — Settings Dialog - 'journey.settings.title': 'Pengaturan Journey', - 'journey.settings.coverImage': 'Gambar Sampul', - 'journey.settings.changeCover': 'Ubah sampul', - 'journey.settings.addCover': 'Tambah gambar sampul', - 'journey.settings.name': 'Nama', - 'journey.settings.subtitle': 'Subjudul', - 'journey.settings.subtitlePlaceholder': 'mis. Thailand, Vietnam & Kamboja', - 'journey.settings.endJourney': 'Arsipkan Perjalanan', - 'journey.settings.reopenJourney': 'Pulihkan Perjalanan', - 'journey.settings.archived': 'Perjalanan diarsipkan', - 'journey.settings.reopened': 'Perjalanan dibuka kembali', - 'journey.settings.endDescription': 'Menyembunyikan lencana Langsung. Anda dapat membuka kembali kapan saja.', - 'journey.settings.delete': 'Hapus', - 'journey.settings.deleteJourney': 'Hapus Journey', - 'journey.settings.deleteMessage': 'Hapus "{title}"? Semua entri dan foto akan hilang.', - 'journey.settings.saved': 'Pengaturan disimpan', - 'journey.settings.saveFailed': 'Gagal menyimpan', - 'journey.settings.coverUpdated': 'Sampul diperbarui', - 'journey.settings.coverFailed': 'Unggah gagal', - 'journey.settings.failedToDelete': 'Gagal menghapus', - 'journey.entries.deleteTitle': 'Hapus Entri', - 'journey.photosUploaded': '{count} foto diunggah', - 'journey.photosUploadFailed': 'Beberapa foto gagal diunggah', - 'journey.photosAdded': '{count} foto ditambahkan', - - // Journey — Public Page - 'journey.public.notFound': 'Tidak Ditemukan', - 'journey.public.notFoundMessage': 'Journey ini tidak ada atau tautan telah kedaluwarsa.', - 'journey.public.readOnly': 'Hanya baca · Journey Publik', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Dibagikan melalui', - 'journey.public.madeWith': 'Dibuat dengan', - - // Journey — PDF Export - 'journey.pdf.journeyBook': 'Buku Journey', - 'journey.pdf.madeWith': 'Dibuat dengan TREK', - 'journey.pdf.day': 'Hari', - 'journey.pdf.theEnd': 'Tamat', - 'journey.pdf.saveAsPdf': 'Simpan sebagai PDF', - 'journey.pdf.pages': 'halaman', - 'journey.picker.tripPeriod': 'Periode Perjalanan', - 'journey.picker.dateRange': 'Rentang Tanggal', - 'journey.picker.allPhotos': 'Semua Foto', - 'journey.picker.albums': 'Album', - 'journey.picker.selected': 'dipilih', - 'journey.picker.addTo': 'Tambahkan ke', - 'journey.picker.newGallery': 'Galeri Baru', - 'journey.picker.selectAll': 'Pilih semua', - 'journey.picker.deselectAll': 'Batalkan semua', - 'journey.picker.noAlbums': 'Tidak ada album ditemukan', - 'journey.picker.selectDate': 'Pilih tanggal', - 'journey.picker.search': 'Cari', - - // Dashboard Mobile - 'dashboard.greeting.morning': 'Selamat pagi,', - 'dashboard.greeting.afternoon': 'Selamat siang,', - 'dashboard.greeting.evening': 'Selamat malam,', - 'dashboard.mobile.liveNow': 'Sedang Berlangsung', - 'dashboard.mobile.tripProgress': 'Progres perjalanan', - 'dashboard.mobile.daysLeft': '{count} hari lagi', - 'dashboard.mobile.places': 'Tempat', - 'dashboard.mobile.buddies': 'Teman', - 'dashboard.mobile.newTrip': 'Perjalanan Baru', - 'dashboard.mobile.currency': 'Mata Uang', - 'dashboard.mobile.timezone': 'Zona Waktu', - 'dashboard.mobile.upcomingTrips': 'Perjalanan Mendatang', - 'dashboard.mobile.yourTrips': 'Perjalananmu', - 'dashboard.mobile.trips': 'perjalanan', - 'dashboard.mobile.starts': 'Mulai', - 'dashboard.mobile.duration': 'Durasi', - 'dashboard.mobile.day': 'hari', - 'dashboard.mobile.days': 'hari', - 'dashboard.mobile.ongoing': 'Sedang berlangsung', - 'dashboard.mobile.startsToday': 'Mulai hari ini', - 'dashboard.mobile.tomorrow': 'Besok', - 'dashboard.mobile.inDays': 'Dalam {count} hari', - 'dashboard.mobile.inMonths': 'Dalam {count} bulan', - 'dashboard.mobile.completed': 'Selesai', - 'dashboard.mobile.currencyConverter': 'Konverter Mata Uang', - - // BottomNav & Profile - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Pengaturan', - 'nav.bottomAdmin': 'Pengaturan Admin', - 'nav.bottomLogout': 'Keluar', - 'nav.bottomAdminBadge': 'Admin', - - // DayPlan Mobile - 'dayplan.mobile.addPlace': 'Tambah Tempat', - 'dayplan.mobile.searchPlaces': 'Cari tempat...', - 'dayplan.mobile.allAssigned': 'Semua tempat sudah ditugaskan', - 'dayplan.mobile.noMatch': 'Tidak ditemukan', - 'dayplan.mobile.createNew': 'Buat tempat baru', - - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': 'Pelacakan perjalanan & jurnal dengan check-in, foto, dan cerita harian', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Perjalanan', - 'oauth.scope.group.places': 'Tempat', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Perlengkapan', - 'oauth.scope.group.todos': 'To-do', - 'oauth.scope.group.budget': 'Anggaran', - 'oauth.scope.group.reservations': 'Reservasi', - 'oauth.scope.group.collab': 'Kolaborasi', - 'oauth.scope.group.notifications': 'Notifikasi', - 'oauth.scope.group.vacay': 'Liburan', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Cuaca', - 'oauth.scope.group.journey': 'Journey', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Lihat perjalanan & itinerari', - 'oauth.scope.trips:read.description': 'Baca perjalanan, hari, catatan harian, dan anggota', - 'oauth.scope.trips:write.label': 'Edit perjalanan & itinerari', - 'oauth.scope.trips:write.description': 'Buat dan perbarui perjalanan, hari, catatan, dan kelola anggota', - 'oauth.scope.trips:delete.label': 'Hapus perjalanan', - 'oauth.scope.trips:delete.description': 'Hapus permanen seluruh perjalanan — tindakan ini tidak dapat dibatalkan', - 'oauth.scope.trips:share.label': 'Kelola tautan berbagi', - 'oauth.scope.trips:share.description': 'Buat, perbarui, dan cabut tautan berbagi publik untuk perjalanan', - 'oauth.scope.places:read.label': 'Lihat tempat & data peta', - 'oauth.scope.places:read.description': 'Baca tempat, penugasan hari, tag, dan kategori', - 'oauth.scope.places:write.label': 'Kelola tempat', - 'oauth.scope.places:write.description': 'Buat, perbarui, dan hapus tempat, penugasan, dan tag', - 'oauth.scope.atlas:read.label': 'Lihat Atlas', - 'oauth.scope.atlas:read.description': 'Baca negara yang dikunjungi, wilayah, dan daftar impian', - 'oauth.scope.atlas:write.label': 'Kelola Atlas', - 'oauth.scope.atlas:write.description': 'Tandai negara dan wilayah yang dikunjungi, kelola daftar impian', - 'oauth.scope.packing:read.label': 'Lihat daftar perlengkapan', - 'oauth.scope.packing:read.description': 'Baca barang perlengkapan, tas, dan penugasan kategori', - 'oauth.scope.packing:write.label': 'Kelola daftar perlengkapan', - 'oauth.scope.packing:write.description': 'Tambah, perbarui, hapus, centang, dan urutkan barang dan tas', - 'oauth.scope.todos:read.label': 'Lihat daftar to-do', - 'oauth.scope.todos:read.description': 'Baca item to-do perjalanan dan penugasan kategori', - 'oauth.scope.todos:write.label': 'Kelola daftar to-do', - 'oauth.scope.todos:write.description': 'Buat, perbarui, centang, hapus, dan urutkan item to-do', - 'oauth.scope.budget:read.label': 'Lihat anggaran', - 'oauth.scope.budget:read.description': 'Baca item anggaran dan rincian pengeluaran', - 'oauth.scope.budget:write.label': 'Kelola anggaran', - 'oauth.scope.budget:write.description': 'Buat, perbarui, dan hapus item anggaran', - 'oauth.scope.reservations:read.label': 'Lihat reservasi', - 'oauth.scope.reservations:read.description': 'Baca reservasi dan detail akomodasi', - 'oauth.scope.reservations:write.label': 'Kelola reservasi', - 'oauth.scope.reservations:write.description': 'Buat, perbarui, hapus, dan urutkan reservasi', - 'oauth.scope.collab:read.label': 'Lihat kolaborasi', - 'oauth.scope.collab:read.description': 'Baca catatan, polling, dan pesan kolaborasi', - 'oauth.scope.collab:write.label': 'Kelola kolaborasi', - 'oauth.scope.collab:write.description': 'Buat, perbarui, dan hapus catatan, polling, dan pesan kolaborasi', - 'oauth.scope.notifications:read.label': 'Lihat notifikasi', - 'oauth.scope.notifications:read.description': 'Baca notifikasi dalam aplikasi dan jumlah yang belum dibaca', - 'oauth.scope.notifications:write.label': 'Kelola notifikasi', - 'oauth.scope.notifications:write.description': 'Tandai notifikasi sebagai telah dibaca dan tanggapi', - 'oauth.scope.vacay:read.label': 'Lihat rencana liburan', - 'oauth.scope.vacay:read.description': 'Baca data perencanaan liburan, entri, dan statistik', - 'oauth.scope.vacay:write.label': 'Kelola rencana liburan', - 'oauth.scope.vacay:write.description': 'Buat dan kelola entri liburan, hari libur, dan rencana tim', - 'oauth.scope.geo:read.label': 'Peta & geokoding', - 'oauth.scope.geo:read.description': 'Cari lokasi, selesaikan URL peta, dan geokode terbalik koordinat', - 'oauth.scope.weather:read.label': 'Prakiraan cuaca', - 'oauth.scope.weather:read.description': 'Ambil prakiraan cuaca untuk lokasi dan tanggal perjalanan', - 'oauth.scope.journey:read.label': 'Lihat Journey', - 'oauth.scope.journey:read.description': 'Baca Journey, entri, dan daftar kontributor', - 'oauth.scope.journey:write.label': 'Kelola Journey', - 'oauth.scope.journey:write.description': 'Buat, perbarui, dan hapus Journey beserta entrinya', - 'oauth.scope.journey:share.label': 'Kelola tautan Journey', - 'oauth.scope.journey:share.description': 'Buat, perbarui, dan cabut tautan berbagi publik untuk Journey', - - - - // System notices - 'system_notice.welcome_v1.title': 'Selamat datang di TREK', - 'system_notice.welcome_v1.body': 'Perencana perjalanan lengkap Anda. Buat itinerari, bagikan perjalanan dengan teman, dan tetap terorganisir — online maupun offline.', - 'system_notice.welcome_v1.cta_label': 'Rencanakan perjalanan', - 'system_notice.welcome_v1.hero_alt': 'Destinasi wisata indah dengan antarmuka TREK', - 'system_notice.welcome_v1.highlight_plan': 'Itinerari harian untuk setiap perjalanan', - 'system_notice.welcome_v1.highlight_share': 'Berkolaborasi dengan teman perjalanan', - 'system_notice.welcome_v1.highlight_offline': 'Bekerja offline di ponsel', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Pemberitahuan sebelumnya', - 'system_notice.pager.next': 'Pemberitahuan berikutnya', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Pergi ke pemberitahuan {n}', - 'system_notice.pager.position': 'Pemberitahuan {current} dari {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Foto dipindahkan di 3.0', - 'system_notice.v3_photos.body': '**Foto** di Perencana Perjalanan telah dihapus. Foto Anda aman — TREK tidak pernah mengubah perpustakaan Immich atau Synology Anda.\n\nFoto kini ada di addon **Journey**. Journey bersifat opsional — jika belum tersedia, minta admin untuk mengaktifkannya di Admin → Addon.', - 'system_notice.v3_journey.title': 'Kenali Journey — jurnal perjalanan', - 'system_notice.v3_journey.body': 'Dokumentasikan perjalanan Anda sebagai cerita hidup dengan linimasa, galeri foto, dan peta interaktif.', - 'system_notice.v3_journey.cta_label': 'Buka Journey', - 'system_notice.v3_journey.highlight_timeline': 'Linimasa & galeri', - 'system_notice.v3_journey.highlight_photos': 'Impor dari Immich atau Synology', - 'system_notice.v3_journey.highlight_share': 'Bagikan secara publik — tanpa login', - 'system_notice.v3_journey.highlight_export': 'Ekspor sebagai buku foto PDF', - 'system_notice.v3_features.title': 'Sorotan lain di 3.0', - 'system_notice.v3_features.body': 'Beberapa pembaruan lain dalam rilis ini.', - 'system_notice.v3_features.highlight_dashboard': 'Desain ulang dashboard mobile-first', - 'system_notice.v3_features.highlight_offline': 'Mode offline penuh sebagai PWA', - 'system_notice.v3_features.highlight_search': 'Pelengkapan otomatis tempat secara real-time', - 'system_notice.v3_features.highlight_import': 'Impor tempat dari file KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: pembaruan OAuth 2.1', - 'system_notice.v3_mcp.body': 'Integrasi MCP telah sepenuhnya diperbarui. OAuth 2.1 kini menjadi metode autentikasi yang direkomendasikan. Token statis (trek_…) sudah usang dan akan dihapus pada versi mendatang.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 direkomendasikan (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 cakupan izin yang terperinci', - 'system_notice.v3_mcp.highlight_deprecated': 'Token statis trek_ sudah usang', - 'system_notice.v3_mcp.highlight_tools': 'Perangkat dan prompt yang diperluas', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Catatan pribadi dari saya', - 'system_notice.v3_thankyou.body': 'Sebelum kamu lanjut — saya ingin berhenti sejenak.\n\nTREK dimulai sebagai proyek sampingan yang saya buat untuk perjalanan saya sendiri. Saya tidak pernah membayangkan ia akan tumbuh menjadi sesuatu yang dipercaya oleh 4.000 dari kalian untuk merencanakan petualangan. Setiap bintang, setiap issue, setiap permintaan fitur — saya membaca semuanya, dan itulah yang membuat saya terus bertahan di malam-malam larut antara pekerjaan penuh waktu dan kuliah.\n\nSaya ingin kalian tahu: TREK akan selalu open source, selalu self-hosted, selalu milik kalian. Tanpa pelacakan, tanpa langganan, tanpa syarat tersembunyi. Hanya sebuah alat yang dibuat oleh seseorang yang mencintai traveling sama seperti kalian.\n\nTerima kasih khusus untuk [jubnl](https://github.com/jubnl) — kamu telah menjadi kolaborator yang luar biasa. Begitu banyak hal yang membuat versi 3.0 hebat memiliki jejakmu. Terima kasih telah percaya pada proyek ini ketika masih kasar.\n\nDan untuk setiap dari kalian yang melaporkan bug, menerjemahkan string, membagikan TREK kepada teman, atau sekadar menggunakannya untuk merencanakan perjalanan — **terima kasih**. Kalianlah alasan semua ini ada.\n\nUntuk lebih banyak petualangan bersama.\n\n— Maurice\n\n---\n\n[Bergabunglah dengan komunitas di Discord](https://discord.gg/7Q6M6jDwzf)\n\nJika TREK membuat perjalananmu lebih baik, [secangkir kopi kecil](https://ko-fi.com/mauriceboe) selalu membantu menjaga lampu tetap menyala.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Tindakan diperlukan: konflik akun pengguna', - 'system_notice.v3014_whitespace_collision.body': 'Pembaruan 3.0.14 mendeteksi satu atau lebih konflik nama pengguna atau email yang disebabkan oleh spasi di awal atau akhir nilai yang tersimpan. Akun yang terpengaruh telah diganti nama secara otomatis. Periksa log server untuk baris yang dimulai dengan **[migration] WHITESPACE COLLISION** guna mengidentifikasi akun mana yang perlu ditinjau.', - 'transport.addTransport': 'Tambah transportasi', - 'transport.modalTitle.create': 'Tambah transportasi', - 'transport.modalTitle.edit': 'Edit transportasi', - 'transport.title': 'Transportasi', - 'transport.addManual': 'Transportasi Manual', -}; - -export default id; diff --git a/client/src/i18n/translations/it.ts b/client/src/i18n/translations/it.ts deleted file mode 100644 index d4b0d59d..00000000 --- a/client/src/i18n/translations/it.ts +++ /dev/null @@ -1,2368 +0,0 @@ -const it: Record = { - // Common - 'common.save': 'Salva', - 'common.showMore': 'Mostra di più', - 'common.showLess': 'Mostra meno', - 'common.cancel': 'Annulla', - 'common.clear': 'Cancella', - 'common.delete': 'Elimina', - 'common.edit': 'Modifica', - 'common.add': 'Aggiungi', - 'common.loading': 'Caricamento...', - 'common.import': 'Importa', - 'common.select': 'Seleziona', - 'common.selectAll': 'Seleziona tutto', - 'common.deselectAll': 'Deseleziona tutto', - 'common.error': 'Errore', - 'common.unknownError': 'Errore sconosciuto', - 'common.tooManyAttempts': 'Troppi tentativi. Riprova più tardi.', - 'common.back': 'Indietro', - 'common.all': 'Tutti', - 'common.close': 'Chiudi', - 'common.open': 'Apri', - 'common.upload': 'Carica', - 'common.search': 'Cerca', - 'common.confirm': 'Conferma', - 'common.ok': 'OK', - 'common.yes': 'Sì', - 'common.no': 'No', - 'common.or': 'o', - 'common.none': 'Nessuno', - 'common.date': 'Data', - 'common.rename': 'Rinomina', - 'common.discardChanges': 'Scarta modifiche', - 'common.discard': 'Scarta', - 'common.name': 'Nome', - 'common.email': 'Email', - 'common.password': 'Password', - 'common.saving': 'Salvataggio...', - 'common.saved': 'Salvato', - 'trips.memberRemoved': '{username} rimosso', - 'trips.memberRemoveError': 'Rimozione non riuscita', - 'trips.memberAdded': '{username} aggiunto', - 'trips.memberAddError': 'Aggiunta non riuscita', - 'common.expand': 'Espandi', - 'common.collapse': 'Comprimi', - 'trips.reminder': 'Promemoria', - 'trips.reminderNone': 'Nessuno', - 'trips.reminderDay': 'giorno', - 'trips.reminderDays': 'giorni', - 'trips.reminderCustom': 'Personalizzato', - 'trips.reminderDaysBefore': 'giorni prima della partenza', - 'trips.reminderDisabledHint': 'I promemoria dei viaggi sono disabilitati. Abilitali in Admin > Impostazioni > Notifiche.', - 'common.update': 'Aggiorna', - 'common.change': 'Cambia', - 'common.uploading': 'Caricamento…', - 'common.backToPlanning': 'Torna al Programma', - 'common.reset': 'Reimposta', - - // Navbar - 'nav.trip': 'Viaggio', - 'nav.share': 'Condividi', - 'nav.settings': 'Impostazioni', - 'nav.admin': 'Amministrazione', - 'nav.logout': 'Esci', - 'nav.lightMode': 'Modalità chiara', - 'nav.darkMode': 'Modalità scura', - 'nav.autoMode': 'Modalità automatica', - 'nav.administrator': 'Amministratore', - - // Dashboard - 'dashboard.title': 'I miei Viaggi', - 'dashboard.subtitle.loading': 'Caricamento viaggi...', - 'dashboard.subtitle.trips': '{count} viaggi ({archived} archiviati)', - 'dashboard.subtitle.empty': 'Inizia il tuo primo viaggio', - 'dashboard.subtitle.activeOne': '{count} viaggio attivo', - 'dashboard.subtitle.activeMany': '{count} viaggi attivi', - 'dashboard.subtitle.archivedSuffix': ' · {count} archiviati', - 'dashboard.newTrip': 'Nuovo Viaggio', - 'dashboard.gridView': 'Vista a griglia', - 'dashboard.listView': 'Vista a lista', - 'dashboard.currency': 'Valuta', - 'dashboard.timezone': 'Fusi orari', - 'dashboard.localTime': 'Locale', - 'dashboard.timezoneCustomTitle': 'Fuso orario personalizzato', - 'dashboard.timezoneCustomLabelPlaceholder': 'Etichetta (opzionale)', - 'dashboard.timezoneCustomTzPlaceholder': 'es. Europe/Rome', - 'dashboard.timezoneCustomAdd': 'Aggiungi', - 'dashboard.timezoneCustomErrorEmpty': 'Inserisci un identificatore di fuso orario', - 'dashboard.timezoneCustomErrorInvalid': 'Fuso orario non valido. Usa formati come Europe/Rome', - 'dashboard.timezoneCustomErrorDuplicate': 'Già aggiunto', - 'dashboard.emptyTitle': 'Ancora nessun viaggio', - 'dashboard.emptyText': 'Crea il tuo primo viaggio e inizia a programmare!', - 'dashboard.emptyButton': 'Crea il primo viaggio', - 'dashboard.nextTrip': 'Prossimo Viaggio', - 'dashboard.shared': 'Condiviso', - 'dashboard.sharedBy': 'Condiviso da {name}', - 'dashboard.days': 'Giorni', - 'dashboard.places': 'Luoghi', - 'dashboard.members': 'Compagni di viaggio', - 'dashboard.archive': 'Archivia', - 'dashboard.copyTrip': 'Copia', - 'dashboard.copySuffix': 'copia', - 'dashboard.restore': 'Ripristina', - 'dashboard.archived': 'Archiviati', - 'dashboard.status.ongoing': 'In corso', - 'dashboard.status.today': 'Oggi', - 'dashboard.status.tomorrow': 'Domani', - 'dashboard.status.past': 'Passato', - 'dashboard.status.daysLeft': '-{count} giorni', - 'dashboard.toast.loadError': 'Impossibile caricare i viaggi', - 'dashboard.toast.created': 'Viaggio creato con successo!', - 'dashboard.toast.createError': 'Impossibile creare il viaggio', - 'dashboard.toast.updated': 'Viaggio aggiornato!', - 'dashboard.toast.updateError': 'Impossibile aggiornare il viaggio', - 'dashboard.toast.deleted': 'Viaggio eliminato', - 'dashboard.toast.deleteError': 'Impossibile eliminare il viaggio', - 'dashboard.toast.archived': 'Viaggio archiviato', - 'dashboard.toast.archiveError': 'Impossibile archiviare il viaggio', - 'dashboard.toast.restored': 'Viaggio ripristinato', - 'dashboard.toast.restoreError': 'Impossibile ripristinare il viaggio', - 'dashboard.toast.copied': 'Viaggio copiato!', - 'dashboard.toast.copyError': 'Impossibile copiare il viaggio', - 'dashboard.confirm.delete': 'Eliminare il viaggio "{title}"? Tutti i luoghi e i programmi verranno eliminati in modo permanente.', - 'dashboard.editTrip': 'Modifica Viaggio', - 'dashboard.createTrip': 'Crea Nuovo Viaggio', - 'dashboard.tripTitle': 'Titolo', - 'dashboard.tripTitlePlaceholder': 'es. Estate in Giappone', - 'dashboard.tripDescription': 'Descrizione', - 'dashboard.tripDescriptionPlaceholder': 'Di cosa tratta questo viaggio?', - 'dashboard.startDate': 'Data di inizio', - 'dashboard.endDate': 'Data di fine', - 'dashboard.dayCount': 'Numero di giorni', - 'dashboard.dayCountHint': 'Quanti giorni pianificare quando non sono impostate date di viaggio.', - 'dashboard.noDateHint': 'Nessuna data impostata — verranno creati 7 giorni predefiniti. Puoi cambiarlo in qualsiasi momento.', - 'dashboard.coverImage': 'Immagine di copertina', - 'dashboard.addCoverImage': 'Aggiungi immagine di copertina (o trascinala qui)', - 'dashboard.addMembers': 'Compagni di viaggio', - 'dashboard.addMember': 'Aggiungi membro', - 'dashboard.coverSaved': 'Immagine di copertina salvata', - 'dashboard.coverUploadError': 'Impossibile caricare', - 'dashboard.coverRemoveError': 'Impossibile rimuovere', - 'dashboard.titleRequired': 'Il titolo è obbligatorio', - 'dashboard.endDateError': 'La data di fine deve essere successiva alla data di inizio', - - // Settings - 'settings.title': 'Impostazioni', - 'settings.subtitle': 'Configura le tue impostazioni personali', - 'settings.tabs.display': 'Visualizzazione', - 'settings.tabs.map': 'Mappa', - 'settings.tabs.notifications': 'Notifiche', - 'settings.tabs.integrations': 'Integrazioni', - 'settings.tabs.account': 'Account', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Informazioni', - 'settings.map': 'Mappa', - 'settings.mapTemplate': 'Modello Mappa', - 'settings.mapTemplatePlaceholder.select': 'Seleziona modello...', - 'settings.mapDefaultHint': 'Lascia vuoto per OpenStreetMap (predefinito)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'Modello URL per i tile della mappa', - 'settings.mapProvider': 'Provider mappa', - 'settings.mapProviderHint': 'Influisce sulle mappe Trip Planner e Journey. Atlas usa sempre Leaflet.', - 'settings.mapLeafletSubtitle': 'Classica 2D, qualsiasi tile raster', - 'settings.mapMapboxSubtitle': 'Tile vettoriali, edifici 3D e terreno', - 'settings.mapExperimental': 'Sperimentale', - 'settings.mapMapboxToken': 'Token di accesso Mapbox', - 'settings.mapMapboxTokenHint': 'Token pubblico (pk.*) da', - 'settings.mapMapboxTokenLink': 'mapbox.com → Token di accesso', - 'settings.mapStyle': 'Stile mappa', - 'settings.mapStylePlaceholder': 'Seleziona uno stile Mapbox', - 'settings.mapStyleHint': 'Preset o il tuo URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': 'Edifici 3D e terreno', - 'settings.map3dHint': 'Inclinazione + estrusioni 3D reali degli edifici — funziona con ogni stile, incluso satellite.', - 'settings.mapHighQuality': 'Modalità alta qualità', - 'settings.mapHighQualityHint': 'Antialiasing + proiezione globo per bordi più nitidi e una vista realistica del mondo.', - 'settings.mapHighQualityWarning': 'Può influire sulle prestazioni su dispositivi meno potenti.', - 'settings.mapTipLabel': 'Suggerimento:', - 'settings.mapTip': 'Click destro e trascina per ruotare/inclinare la mappa. Click centrale per aggiungere un luogo (il click destro è riservato alla rotazione).', - 'settings.latitude': 'Latitudine', - 'settings.longitude': 'Longitudine', - 'settings.saveMap': 'Salva Mappa', - 'settings.apiKeys': 'Chiavi API', - 'settings.mapsKey': 'Chiave API Google Maps', - 'settings.mapsKeyHint': 'Per la ricerca dei luoghi. Richiede Places API (New). Ottienila su console.cloud.google.com', - 'settings.weatherKey': 'Chiave API OpenWeatherMap', - 'settings.weatherKeyHint': 'Per i dati meteo. Gratuita su openweathermap.org/api', - 'settings.keyPlaceholder': 'Inserisci la chiave...', - 'settings.configured': 'Configurata', - 'settings.saveKeys': 'Salva Chiavi', - 'settings.display': 'Visualizzazione', - 'settings.colorMode': 'Modalità Colore', - 'settings.light': 'Chiara', - 'settings.dark': 'Scura', - 'settings.auto': 'Automatica', - 'settings.language': 'Lingua', - 'settings.temperature': 'Unità di Temperatura', - 'settings.timeFormat': 'Formato Ora', - 'settings.blurBookingCodes': 'Nascondi codici di prenotazione', - 'settings.notifications': 'Notifiche', - 'settings.notifyTripInvite': 'Inviti di viaggio', - 'settings.notifyBookingChange': 'Modifiche alle prenotazioni', - 'settings.notifyTripReminder': 'Promemoria di viaggio', - 'settings.notifyTodoDue': 'Attività in scadenza', - 'settings.notifyVacayInvite': 'Inviti fusione Vacay', - 'settings.notifyPhotosShared': 'Foto condivise (Immich)', - 'settings.notifyCollabMessage': 'Messaggi chat (Collab)', - 'settings.notifyPackingTagged': 'Lista valigia: assegnazioni', - 'settings.notifyWebhook': 'Notifiche webhook', - 'settings.notificationsDisabled': 'Le notifiche non sono configurate. Chiedi a un amministratore di abilitare le notifiche e-mail o webhook.', - 'settings.notificationsActive': 'Canale attivo', - 'settings.notificationsManagedByAdmin': 'Gli eventi di notifica sono configurati dall\'amministratore.', - 'settings.on': 'On', - 'settings.off': 'Off', - 'settings.mcp.title': 'Configurazione MCP', - 'settings.mcp.endpoint': 'Endpoint MCP', - 'settings.mcp.clientConfig': 'Configurazione client', - 'settings.mcp.clientConfigHint': 'Sostituisci con un token API dalla lista sottostante. Il percorso di npx potrebbe dover essere adattato per il tuo sistema (es. C:\\PROGRA~1\\nodejs\\npx.cmd su Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).', - 'settings.mcp.copy': 'Copia', - 'settings.mcp.copied': 'Copiato!', - 'settings.mcp.apiTokens': 'Token API', - 'settings.mcp.createToken': 'Crea nuovo token', - 'settings.mcp.noTokens': 'Nessun token ancora. Creane uno per connettere i client MCP.', - 'settings.mcp.tokenCreatedAt': 'Creato', - 'settings.mcp.tokenUsedAt': 'Utilizzato', - 'settings.mcp.deleteTokenTitle': 'Elimina token', - 'settings.mcp.deleteTokenMessage': 'Questo token smetterà di funzionare immediatamente. Qualsiasi client MCP che lo utilizza perderà l\'accesso.', - 'settings.mcp.modal.createTitle': 'Crea token API', - 'settings.mcp.modal.tokenName': 'Nome del token', - 'settings.mcp.modal.tokenNamePlaceholder': 'es. Claude Desktop, Laptop di lavoro', - 'settings.mcp.modal.creating': 'Creazione…', - 'settings.mcp.modal.create': 'Crea token', - 'settings.mcp.modal.createdTitle': 'Token creato', - 'settings.mcp.modal.createdWarning': 'Questo token verrà mostrato solo una volta. Copialo e salvalo ora — non può essere recuperato.', - 'settings.mcp.modal.done': 'Fatto', - 'settings.mcp.toast.created': 'Token creato', - 'settings.mcp.toast.createError': 'Impossibile creare il token', - 'settings.mcp.toast.deleted': 'Token eliminato', - 'settings.mcp.toast.deleteError': 'Impossibile eliminare il token', - 'settings.mcp.apiTokensDeprecated': 'I token API sono deprecati e verranno rimossi in una versione futura. Utilizza invece i client OAuth 2.1.', - 'settings.oauth.clients': 'Client OAuth 2.1', - 'settings.oauth.clientsHint': 'Registra client OAuth 2.1 per consentire alle applicazioni MCP di terze parti (Claude Web, Cursor, ecc.) di connettersi senza token statici.', - 'settings.oauth.createClient': 'Nuovo client', - 'settings.oauth.noClients': 'Nessun client OAuth registrato.', - 'settings.oauth.clientId': 'ID client', - 'settings.oauth.clientSecret': 'Segreto client', - 'settings.oauth.deleteClient': 'Elimina client', - 'settings.oauth.deleteClientMessage': 'Questo client e tutte le sessioni attive verranno eliminati definitivamente. Qualsiasi applicazione che lo utilizza perderà immediatamente l\'accesso.', - 'settings.oauth.rotateSecret': 'Rinnova segreto', - 'settings.oauth.rotateSecretMessage': 'Verrà generato un nuovo segreto client e tutte le sessioni esistenti verranno invalidate immediatamente. Aggiorna la tua applicazione prima di chiudere questa finestra.', - 'settings.oauth.rotateSecretConfirm': 'Rinnova', - 'settings.oauth.rotateSecretConfirming': 'Rinnovo in corso…', - 'settings.oauth.rotateSecretDoneTitle': 'Nuovo segreto generato', - 'settings.oauth.rotateSecretDoneWarning': 'Questo segreto viene mostrato una sola volta. Copialo ora e aggiorna la tua applicazione — tutte le sessioni precedenti sono state invalidate.', - 'settings.oauth.activeSessions': 'Sessioni OAuth attive', - 'settings.oauth.sessionScopes': 'Ambiti', - 'settings.oauth.sessionExpires': 'Scade', - 'settings.oauth.revoke': 'Revoca', - 'settings.oauth.revokeSession': 'Revoca sessione', - 'settings.oauth.revokeSessionMessage': 'Questo revocherà immediatamente l\'accesso per questa sessione OAuth.', - 'settings.oauth.modal.createTitle': 'Registra client OAuth', - 'settings.oauth.modal.presets': 'Preimpostazioni rapide', - 'settings.oauth.modal.clientName': 'Nome applicazione', - 'settings.oauth.modal.clientNamePlaceholder': 'es. Claude Web, La mia app MCP', - 'settings.oauth.modal.redirectUris': 'URI di reindirizzamento', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Un URI per riga. HTTPS richiesto (localhost esente). Corrispondenza esatta richiesta.', - 'settings.oauth.modal.scopes': 'Ambiti consentiti', - 'settings.oauth.modal.scopesHint': 'list_trips e get_trip_summary sono sempre disponibili — nessun ambito richiesto. Permettono all\'IA di scoprire gli ID viaggio necessari.', - 'settings.oauth.modal.selectAll': 'Seleziona tutto', - 'settings.oauth.modal.deselectAll': 'Deseleziona tutto', - 'settings.oauth.modal.creating': 'Registrazione…', - 'settings.oauth.modal.create': 'Registra client', - 'settings.oauth.modal.createdTitle': 'Client registrato', - 'settings.oauth.modal.createdWarning': 'Il segreto client viene mostrato una sola volta. Copialo ora — non può essere recuperato.', - 'settings.oauth.toast.createError': 'Impossibile registrare il client OAuth', - 'settings.oauth.toast.deleted': 'Client OAuth eliminato', - 'settings.oauth.toast.deleteError': 'Impossibile eliminare il client OAuth', - 'settings.oauth.toast.revoked': 'Sessione revocata', - 'settings.oauth.toast.revokeError': 'Impossibile revocare la sessione', - 'settings.oauth.toast.rotateError': 'Impossibile rinnovare il segreto client', - 'settings.oauth.modal.machineClient': 'Client macchina (senza login nel browser)', - 'settings.oauth.modal.machineClientHint': 'Usa il grant client_credentials — nessun URI di reindirizzamento necessario. Il token viene emesso direttamente tramite client_id + client_secret e agisce come te negli ambiti selezionati.', - 'settings.oauth.modal.machineClientUsage': 'Ottieni token: POST /oauth/token con grant_type=client_credentials, client_id e client_secret. Senza browser, senza token di aggiornamento.', - 'settings.oauth.badge.machine': 'macchina', - 'settings.account': 'Account', - 'settings.about': 'Informazioni', - 'settings.about.reportBug': 'Segnala un bug', - 'settings.about.reportBugHint': 'Hai trovato un problema? Faccelo sapere', - 'settings.about.featureRequest': 'Richiedi funzionalità', - 'settings.about.featureRequestHint': 'Suggerisci una nuova funzionalità', - 'settings.about.wikiHint': 'Documentazione e guide', - 'settings.about.supporters.badge': 'Sostenitori Mensili', - 'settings.about.supporters.title': 'Compagni di viaggio per TREK', - 'settings.about.supporters.subtitle': 'Mentre pianifichi il tuo prossimo itinerario, queste persone aiutano a pianificare il futuro di TREK. Il loro contributo mensile va direttamente allo sviluppo e alle ore realmente investite — per mantenere TREK Open Source.', - 'settings.about.supporters.since': 'sostenitore da {date}', - 'settings.about.supporters.tierEmpty': 'Sii il primo', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK è un pianificatore di viaggi self-hosted che ti aiuta a organizzare i tuoi viaggi dalla prima idea all\'ultimo ricordo. Pianificazione giornaliera, budget, liste bagagli, foto e molto altro — tutto in un unico posto, sul tuo server.', - 'settings.about.madeWith': 'Fatto con', - 'settings.about.madeBy': 'da Maurice e una crescente comunità open-source.', - 'settings.username': 'Nome utente', - 'settings.email': 'Email', - 'settings.role': 'Ruolo', - 'settings.roleAdmin': 'Amministratore', - 'settings.oidcLinked': 'Collegato con', - 'settings.changePassword': 'Cambia Password', - 'settings.currentPassword': 'Password attuale', - 'settings.currentPasswordRequired': 'La password attuale è obbligatoria', - 'settings.newPassword': 'Nuova password', - 'settings.confirmPassword': 'Conferma nuova password', - 'settings.updatePassword': 'Aggiorna password', - 'settings.passwordRequired': 'Inserisci la password attuale e quella nuova', - 'settings.passwordTooShort': 'La password deve contenere almeno 8 caratteri', - 'settings.passwordMismatch': 'Le password non corrispondono', - 'settings.passwordWeak': 'La password deve contenere lettere maiuscole, minuscole, un numero e un carattere speciale', - 'settings.passwordChanged': 'Password cambiata con successo', - 'settings.deleteAccount': 'Elimina account', - 'settings.deleteAccountTitle': 'Eliminare il tuo account?', - 'settings.deleteAccountWarning': 'Il tuo account e tutti i tuoi viaggi, luoghi e file verranno eliminati in modo permanente. Questa azione non può essere annullata.', - 'settings.deleteAccountConfirm': 'Elimina in modo permanente', - 'settings.deleteBlockedTitle': 'Eliminazione impossibile', - 'settings.deleteBlockedMessage': 'Sei l\'unico amministratore. Promuovi un altro utente ad amministratore prima di eliminare il tuo account.', - 'settings.roleUser': 'Utente', - 'settings.saveProfile': 'Salva Profillo', - 'settings.toast.mapSaved': 'Impostazioni mappa salvate', - 'settings.toast.keysSaved': 'Chiavi API salvate', - 'settings.toast.displaySaved': 'Impostazioni di visualizzazione salvate', - 'settings.toast.profileSaved': 'Profilo salvato', - 'settings.uploadAvatar': 'Carica Immagine del Profilo', - 'settings.removeAvatar': 'Rimuovi Immagine del Profilo', - 'settings.avatarUploaded': 'Immagine del profilo aggiornata', - 'settings.avatarRemoved': 'Immagine del profilo rimossa', - 'settings.avatarError': 'Impossibile caricare', - 'settings.mfa.title': 'Autenticazione a due fattori (2FA)', - 'settings.mfa.description': 'Aggiunge un secondo passaggio quando accedi con email e password. Usa un\'app authenticator (Google Authenticator, Authy, ecc.).', - 'settings.mfa.requiredByPolicy': 'L\'amministratore richiede l\'autenticazione a due fattori. Configura un\'app authenticator qui sotto prima di continuare.', - 'settings.mfa.backupTitle': 'Codici di backup', - 'settings.mfa.backupDescription': 'Usa questi codici monouso se perdi l\'accesso alla tua app authenticator.', - 'settings.mfa.backupWarning': 'Salvali adesso. Ogni codice può essere usato una sola volta.', - 'settings.mfa.backupCopy': 'Copia codici', - 'settings.mfa.backupDownload': 'Scarica TXT', - 'settings.mfa.backupPrint': 'Stampa / PDF', - 'settings.mfa.backupCopied': 'Codici di backup copiati', - 'settings.mfa.enabled': 'La 2FA è abilitata sul tuo account.', - 'settings.mfa.disabled': 'La 2FA non è abilitata.', - 'settings.mfa.setup': 'Configura authenticator', - 'settings.mfa.scanQr': 'Scansiona questo codice QR con la tua app, o inserisci il segreto manualmente.', - 'settings.mfa.secretLabel': 'Chiave segreta (inserimento manuale)', - 'settings.mfa.codePlaceholder': 'Codice a 6 cifre', - 'settings.mfa.enable': 'Abilita 2FA', - 'settings.mfa.cancelSetup': 'Annulla', - 'settings.mfa.disableTitle': 'Disabilita 2FA', - 'settings.mfa.disableHint': 'Inserisci la password del tuo account e un codice attuale dal tuo authenticator.', - 'settings.mfa.disable': 'Disabilita 2FA', - 'settings.mfa.toastEnabled': 'Autenticazione a due fattori abilitata', - 'settings.mfa.toastDisabled': 'Autenticazione a due fattori disabilitata', - 'settings.mfa.demoBlocked': 'Non disponibile in modalità demo', - 'settings.mustChangePassword': 'Devi cambiare la password prima di continuare. Imposta una nuova password qui sotto.', - 'admin.notifications.title': 'Notifiche', - 'admin.notifications.hint': 'Scegli un canale di notifica. Solo uno può essere attivo alla volta.', - 'admin.notifications.none': 'Disattivato', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Salva impostazioni notifiche', - 'admin.notifications.saved': 'Impostazioni notifiche salvate', - 'admin.notifications.testWebhook': 'Invia webhook di test', - 'admin.notifications.testWebhookSuccess': 'Webhook di test inviato con successo', - 'admin.notifications.testWebhookFailed': 'Invio webhook di test fallito', - 'admin.smtp.title': 'Email e notifiche', - 'admin.smtp.hint': 'Configurazione SMTP per l\'invio delle notifiche via e-mail.', - 'admin.smtp.testButton': 'Invia email di prova', - 'admin.webhook.hint': 'Invia notifiche a un webhook esterno (Discord, Slack, ecc.).', - 'admin.smtp.testSuccess': 'Email di prova inviata con successo', - 'admin.smtp.testFailed': 'Invio email di prova fallito', - 'dayplan.icsTooltip': 'Esporta calendario (ICS)', - 'share.linkTitle': 'Link pubblico', - 'share.linkHint': 'Crea un link che chiunque può usare per visualizzare questo viaggio senza accedere. Solo lettura — nessuna modifica possibile.', - 'share.createLink': 'Crea link', - 'share.deleteLink': 'Elimina link', - 'share.createError': 'Impossibile creare il link', - 'common.copy': 'Copia', - 'common.copied': 'Copiato', - 'share.permMap': 'Mappa e programma', - 'share.permBookings': 'Prenotazioni', - 'share.permPacking': 'Valigia', - 'shared.expired': 'Link scaduto o non valido', - 'shared.expiredHint': 'Questo link di viaggio condiviso non è più attivo.', - 'shared.readOnly': 'Vista in sola lettura', - 'shared.tabPlan': 'Programma', - 'shared.tabBookings': 'Prenotazioni', - 'shared.tabPacking': 'Valigia', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'giorni', - 'shared.places': 'luoghi', - 'shared.other': 'Altro', - 'shared.totalBudget': 'Budget totale', - 'shared.messages': 'messaggi', - 'shared.sharedVia': 'Condiviso tramite', - 'shared.confirmed': 'Confermato', - 'shared.pending': 'In attesa', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - - // Login - 'login.error': 'Accesso fallito. Controlla le tue credenziali.', - 'login.tagline': 'I tuoi viaggi.\nIl tuo programma.', - 'login.description': 'Programma viaggi in collaborazione con mappe interattive, budget e sincronizzazione in tempo reale.', - 'login.features.maps': 'Mappe Interattive', - 'login.features.mapsDesc': 'Google Places, percorsi e clustering', - 'login.features.realtime': 'Sincronizzazione in tempo reale', - 'login.features.realtimeDesc': 'Programmate insieme tramite WebSocket', - 'login.features.budget': 'Tracciamento Budget', - 'login.features.budgetDesc': 'Categorie, grafici e costi per persona', - 'login.features.collab': 'Collaborazione', - 'login.features.collabDesc': 'Multi-utente con viaggi condivisi', - 'login.features.packing': 'Lista Valigia', - 'login.features.packingDesc': 'Categorie, progressi e suggerimenti', - 'login.features.bookings': 'Prenotazioni', - 'login.features.bookingsDesc': 'Voli, alloggi, ristoranti e altro', - 'login.features.files': 'Documenti', - 'login.features.filesDesc': 'Carica e gestisci i documenti', - 'login.features.routes': 'Percorsi Intelligenti', - 'login.features.routesDesc': 'Ottimizzazione automatica ed esportazione su Google Maps', - 'login.selfHosted': 'Self-hosted · Open Source · Your data stays yours', - 'login.title': 'Accedi', - 'login.subtitle': 'Bentornato', - 'login.signingIn': 'Accesso in corso…', - 'login.signIn': 'Accedi', - 'login.createAdmin': 'Crea Account Amministratore', - 'login.createAdminHint': 'Imposta il primo account amministratore per TREK.', - 'login.setNewPassword': 'Imposta nuova password', - 'login.setNewPasswordHint': 'Devi cambiare la password prima di continuare.', - 'login.createAccount': 'Crea Account', - 'login.createAccountHint': 'Registra un nuovo account.', - 'login.creating': 'Creazione in corso…', - 'login.noAccount': "Non hai un account?", - 'login.hasAccount': 'Hai già un account?', - 'login.register': 'Registrati', - 'login.emailPlaceholder': 'tua@email.com', - 'login.username': 'Nome utente', - 'login.oidc.registrationDisabled': 'La registrazione è disabilitata. Contatta il tuo amministratore.', - 'login.oidc.noEmail': 'Nessuna email ricevuta dal provider.', - 'login.oidc.tokenFailed': 'Autenticazione fallita.', - 'login.oidc.invalidState': 'Sessione non valida. Riprova.', - 'login.demoFailed': 'Accesso demo fallito', - 'login.oidcSignIn': 'Accedi con {name}', - 'login.oidcOnly': 'L\'autenticazione tramite password è disabilitata. Accedi utilizzando il tuo provider SSO.', - 'login.oidcLoggedOut': 'Sei stato disconnesso. Accedi nuovamente tramite il tuo provider SSO.', - 'login.demoHint': 'Prova la demo — nessuna registrazione necessaria', - 'login.mfaTitle': 'Autenticazione a due fattori', - 'login.mfaSubtitle': 'Inserisci il codice a 6 cifre dalla tua app authenticator.', - 'login.mfaCodeLabel': 'Codice di verifica', - 'login.mfaCodeRequired': 'Inserisci il codice dalla tua app authenticator.', - 'login.mfaHint': 'Apri Google Authenticator, Authy o un\'altra app TOTP.', - 'login.mfaBack': '← Torna all\'accesso', - 'login.mfaVerify': 'Verifica', - 'login.invalidInviteLink': 'Link di invito non valido o scaduto', - 'login.oidcFailed': 'Accesso OIDC non riuscito', - 'login.usernameRequired': 'Il nome utente è obbligatorio', - 'login.passwordMinLength': 'La password deve contenere almeno 8 caratteri', - 'login.forgotPassword': 'Password dimenticata?', - 'login.forgotPasswordTitle': 'Reimposta la password', - 'login.forgotPasswordBody': 'Inserisci l’indirizzo email del tuo account. Se esiste un account, invieremo un link per reimpostarla.', - 'login.forgotPasswordSubmit': 'Invia link', - 'login.forgotPasswordSentTitle': 'Controlla la tua email', - 'login.forgotPasswordSentBody': 'Se esiste un account con questa email, il link è in arrivo. Scade tra 60 minuti.', - 'login.forgotPasswordSmtpHintOff': 'Nota: il tuo amministratore non ha configurato SMTP, quindi il link di reset verrà scritto nella console del server invece di essere inviato via email.', - 'login.backToLogin': 'Torna all’accesso', - 'login.newPassword': 'Nuova password', - 'login.confirmPassword': 'Conferma nuova password', - 'login.passwordsDontMatch': 'Le password non corrispondono', - 'login.mfaCode': 'Codice 2FA', - 'login.resetPasswordTitle': 'Imposta una nuova password', - 'login.resetPasswordBody': 'Scegli una password robusta che non hai già usato qui. Minimo 8 caratteri.', - 'login.resetPasswordMfaBody': 'Inserisci il codice 2FA o un codice di backup per completare il reset.', - 'login.resetPasswordSubmit': 'Reimposta password', - 'login.resetPasswordVerify': 'Verifica e reimposta', - 'login.resetPasswordSuccessTitle': 'Password aggiornata', - 'login.resetPasswordSuccessBody': 'Ora puoi accedere con la nuova password.', - 'login.resetPasswordInvalidLink': 'Link di reset non valido', - 'login.resetPasswordInvalidLinkBody': 'Il link è mancante o danneggiato. Richiedine uno nuovo per continuare.', - 'login.resetPasswordFailed': 'Reset non riuscito. Il link potrebbe essere scaduto.', - - // Register - 'register.passwordMismatch': 'Le password non corrispondono', - 'register.passwordTooShort': 'La password deve contenere almeno 8 caratteri', - 'register.failed': 'Registrazione fallita', - 'register.getStarted': 'Inizia', - 'register.subtitle': 'Crea un account e inizia a programmare i viaggi dei tuoi sogni.', - 'register.feature1': 'Piani di viaggio illimitati', - 'register.feature2': 'Vista mappa interattiva', - 'register.feature3': 'Gestisci luoghi e categorie', - 'register.feature4': 'Traccia le prenotazioni', - 'register.feature5': 'Crea liste per la valigia', - 'register.feature6': 'Archivia foto e file', - 'register.createAccount': 'Crea Account', - 'register.startPlanning': 'Inizia a programmare il tuo viaggio', - 'register.minChars': 'Min. 6 caratteri', - 'register.confirmPassword': 'Conferma Password', - 'register.repeatPassword': 'Ripeti password', - 'register.registering': 'Registrazione in corso...', - 'register.register': 'Registrati', - 'register.hasAccount': 'Hai già un account?', - 'register.signIn': 'Accedi', - - // Admin - 'admin.title': 'Amministrazione', - 'admin.subtitle': 'Gestione utenti e impostazioni di sistema', - 'admin.tabs.users': 'Utenti', - 'admin.tabs.categories': 'Categorie', - 'admin.tabs.backup': 'Backup', - 'admin.stats.users': 'Utenti', - 'admin.stats.trips': 'Viaggi', - 'admin.stats.places': 'Luoghi', - 'admin.stats.photos': 'Foto', - 'admin.stats.files': 'File', - 'admin.table.user': 'Utente', - 'admin.table.email': 'Email', - 'admin.table.role': 'Ruolo', - 'admin.table.created': 'Creato', - 'admin.table.lastLogin': 'Ultimo Accesso', - 'admin.table.actions': 'Azioni', - 'admin.you': '(Tu)', - 'admin.editUser': 'Modifica Utente', - 'admin.newPassword': 'Nuova Password', - 'admin.newPasswordHint': 'Lascia vuoto per mantenere la password attuale', - 'admin.deleteUser': 'Eliminare l\'utente "{name}"? Tutti i viaggi verranno eliminati in modo permanente.', - 'admin.deleteUserTitle': 'Elimina utente', - 'admin.newPasswordPlaceholder': 'Inserisci nuova password…', - 'admin.toast.loadError': 'Impossibile caricare i dati di amministrazione', - 'admin.toast.userUpdated': 'Utente aggiornato', - 'admin.toast.updateError': 'Impossibile aggiornare', - 'admin.toast.userDeleted': 'Utente eliminato', - 'admin.toast.deleteError': 'Impossibile eliminare', - 'admin.toast.cannotDeleteSelf': 'Impossibile eliminare il proprio account', - 'admin.toast.userCreated': 'Utente creato', - 'admin.toast.createError': 'Impossibile creare l\'utente', - 'admin.toast.fieldsRequired': 'Username, email e password sono obbligatori', - 'admin.createUser': 'Crea Utente', - 'admin.invite.title': 'Link di Invito', - 'admin.invite.subtitle': 'Crea link di registrazione monouso', - 'admin.invite.create': 'Crea Link', - 'admin.invite.createAndCopy': 'Crea & Copia', - 'admin.invite.empty': 'Nessun link di invito ancora creato', - 'admin.invite.maxUses': 'Usi Max.', - 'admin.invite.expiry': 'Scade tra', - 'admin.invite.uses': 'usato', - 'admin.invite.expiresAt': 'scade', - 'admin.invite.createdBy': 'da', - 'admin.invite.active': 'Attivo', - 'admin.invite.expired': 'Scaduto', - 'admin.invite.usedUp': 'Esaurito', - 'admin.invite.copied': 'Link di invito copiato negli appunti', - 'admin.invite.copyLink': 'Copia link', - 'admin.invite.deleted': 'Link di invito eliminato', - 'admin.invite.createError': 'Impossibile creare il link di invito', - 'admin.invite.deleteError': 'Impossibile eliminare il link di invito', - 'admin.tabs.settings': 'Impostazioni', - 'admin.allowRegistration': 'Consenti Registrazione', - 'admin.allowRegistrationHint': 'I nuovi utenti possono registrarsi autonomamente', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Richiedi autenticazione a due fattori (2FA)', - 'admin.requireMfaHint': 'Gli utenti senza 2FA devono completare la configurazione in Impostazioni prima di usare l\'app.', - 'admin.apiKeys': 'Chiavi API', - 'admin.apiKeysHint': 'Opzionale. Abilita dati estesi per i luoghi come foto e meteo.', - 'admin.mapsKey': 'Chiave API Google Maps', - 'admin.mapsKeyHint': 'Richiesta per la ricerca dei luoghi. Ottienila su console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Senza una chiave API, OpenStreetMap viene utilizzato per la ricerca dei luoghi. Con una chiave API di Google, è possibile caricare anche foto, valutazioni e orari di apertura. Ottienine una su console.cloud.google.com.', - 'admin.recommended': 'Consigliato', - 'admin.weatherKey': 'Chiave API OpenWeatherMap', - 'admin.weatherKeyHint': 'Per i dati meteo. Gratuita su openweathermap.org', - 'admin.validateKey': 'Testa', - 'admin.keyValid': 'Connessa', - 'admin.keyInvalid': 'Non valida', - 'admin.keySaved': 'Chiavi API salvate', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Consenti l\'accesso tramite provider esterni come Google, Apple, Authentik o Keycloak.', - 'admin.oidcDisplayName': 'Nome Visualizzato', - 'admin.oidcIssuer': 'URL Emittente', - 'admin.oidcIssuerHint': 'L\'URL dell\'Emittente OpenID Connect del provider. es. https://accounts.google.com', - 'admin.oidcSaved': 'Configurazione OIDC salvata', - 'admin.oidcOnlyMode': 'Disabilita autenticazione con password', - 'admin.oidcOnlyModeHint': 'Se abilitato, è consentito solo l\'accesso SSO. L\'accesso basato su password e la registrazione sono bloccati.', - - // File Types - 'admin.fileTypes': 'Tipi di File Consentiti', - 'admin.fileTypesHint': 'Configura quali tipi di file gli utenti possono caricare.', - 'admin.fileTypesFormat': 'Estensioni separate da virgola (es. jpg,png,pdf,doc). Usa * per consentire tutti i tipi.', - 'admin.fileTypesSaved': 'Impostazioni dei tipi di file salvate', - // Packing Templates & Bag Tracking - 'admin.placesPhotos.title': 'Foto dei luoghi', - 'admin.placesPhotos.subtitle': "Recupera le foto dall'API Google Places. Disabilita per risparmiare la quota API. Le foto di Wikimedia non sono interessate.", - 'admin.placesAutocomplete.title': 'Completamento automatico dei luoghi', - 'admin.placesAutocomplete.subtitle': "Utilizza l'API Google Places per i suggerimenti di ricerca. Disabilita per risparmiare la quota API.", - 'admin.placesDetails.title': 'Dettagli del luogo', - 'admin.placesDetails.subtitle': "Recupera informazioni dettagliate sul luogo (orari, valutazione, sito web) dall'API Google Places. Disabilita per risparmiare la quota API.", - 'admin.bagTracking.title': 'Tracciamento valigia', - 'admin.bagTracking.subtitle': 'Abilita il peso e l\'assegnazione della valigia per gli elementi della lista valigia', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Messaggistica in tempo reale per la collaborazione', - 'admin.collab.notes.title': 'Note', - 'admin.collab.notes.subtitle': 'Note e documenti condivisi', - 'admin.collab.polls.title': 'Sondaggi', - 'admin.collab.polls.subtitle': 'Sondaggi e votazioni di gruppo', - 'admin.collab.whatsnext.title': 'Prossimi passi', - 'admin.collab.whatsnext.subtitle': 'Suggerimenti attività e prossimi passi', - 'admin.tabs.config': 'Personalizzazione', - 'admin.tabs.defaults': 'Impostazioni predefinite', - 'admin.defaultSettings.title': 'Impostazioni predefinite utente', - 'admin.defaultSettings.description': "Imposta i valori predefiniti per l'intera istanza. Gli utenti che non hanno modificato un'impostazione vedranno questi valori. Le loro modifiche hanno sempre la priorità.", - 'admin.defaultSettings.saved': 'Predefinito salvato', - 'admin.defaultSettings.reset': 'Ripristina il predefinito integrato', - 'admin.defaultSettings.resetToBuiltIn': 'ripristina', - 'admin.tabs.templates': 'Modelli lista valigia', - 'admin.packingTemplates.title': 'Modelli lista valigia', - 'admin.packingTemplates.subtitle': 'Crea liste valigia riutilizzabili per i tuoi viaggi', - 'admin.packingTemplates.create': 'Nuovo modello', - 'admin.packingTemplates.namePlaceholder': 'Nome modello (es. Vacanza al mare)', - 'admin.packingTemplates.empty': 'Ancora nessun modello creato', - 'admin.packingTemplates.items': 'elementi', - 'admin.packingTemplates.categories': 'categorie', - 'admin.packingTemplates.itemName': 'Nome elemento', - 'admin.packingTemplates.itemCategory': 'Categoria', - 'admin.packingTemplates.categoryName': 'Nome categoria (es. Abbigliamento)', - 'admin.packingTemplates.addCategory': 'Aggiungi categoria', - 'admin.packingTemplates.created': 'Modello creato', - 'admin.packingTemplates.deleted': 'Modello eliminato', - 'admin.packingTemplates.loadError': 'Impossibile caricare i modelli', - 'admin.packingTemplates.createError': 'Impossibile creare il modello', - 'admin.packingTemplates.deleteError': 'Impossibile eliminare il modello', - 'admin.packingTemplates.saveError': 'Impossibile salvare', - - // Addons - 'admin.tabs.addons': 'Moduli', - 'admin.addons.title': 'Moduli', - 'admin.addons.subtitle': 'Abilita o disabilita le funzionalità per personalizzare la tua esperienza TREK.', - 'admin.addons.catalog.packing.name': 'Liste', - 'admin.addons.catalog.packing.description': 'Liste di imballaggio e attività da svolgere per i tuoi viaggi', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Tieni traccia delle spese e pianifica il budget del tuo viaggio', - 'admin.addons.catalog.documents.name': 'Documenti', - 'admin.addons.catalog.documents.description': 'Archivia e gestisci i documenti di viaggio', - 'admin.addons.catalog.vacay.name': 'Ferie', - 'admin.addons.catalog.vacay.description': 'Pianificatore personale delle ferie con vista calendario', - 'admin.addons.catalog.atlas.name': 'Atlante', - 'admin.addons.catalog.atlas.description': 'Mappa del mondo con paesi visitati e statistiche di viaggio', - 'admin.addons.catalog.collab.name': 'Collaborazione', - 'admin.addons.catalog.collab.description': 'Note, sondaggi e chat in tempo reale per la pianificazione del viaggio', - 'admin.addons.catalog.memories.name': 'Foto (Immich)', - 'admin.addons.catalog.memories.description': 'Condividi le foto del viaggio tramite la tua istanza Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol per l\'integrazione di assistenti AI', - 'admin.addons.subtitleBefore': 'Abilita o disabilita le funzionalità per personalizzare la tua ', - 'admin.addons.subtitleAfter': ' esperienza.', - 'admin.addons.enabled': 'Abilitato', - 'admin.addons.disabled': 'Disabilitato', - 'admin.addons.type.trip': 'Viaggio', - 'admin.addons.type.global': 'Globale', - 'admin.addons.type.integration': 'Integrazione', - 'admin.addons.tripHint': 'Disponibile come scheda all\'interno di ciascun viaggio', - 'admin.addons.globalHint': 'Disponibile come sezione autonoma nella navigazione principale', - 'admin.addons.integrationHint': 'Servizi backend e integrazioni API senza pagina dedicata', - 'admin.addons.toast.updated': 'Modulo aggiornato', - 'admin.addons.toast.error': 'Impossibile aggiornare il modulo', - 'admin.addons.noAddons': 'Nessun modulo disponibile', - - // Weather info - 'admin.weather.title': 'Dati meteo', - 'admin.weather.badge': 'Dal 24 marzo 2026', - 'admin.weather.description': 'TREK utilizza Open-Meteo come fonte dei dati meteo. Open-Meteo è un servizio meteo gratuito e open-source — non è richiesta alcuna chiave API.', - 'admin.weather.forecast': 'Previsioni a 16 giorni', - 'admin.weather.forecastDesc': 'In precedenza 5 giorni (OpenWeatherMap)', - 'admin.weather.climate': 'Dati climatici storici', - 'admin.weather.climateDesc': 'Medie degli ultimi 85 anni per i giorni oltre le previsioni a 16 giorni', - 'admin.weather.requests': '10.000 richieste / giorno', - 'admin.weather.requestsDesc': 'Gratis, nessuna chiave API richiesta', - 'admin.weather.locationHint': 'Il meteo si basa sul primo luogo con coordinate di ogni giorno. Se a un giorno non è assegnato alcun luogo, viene utilizzato come riferimento un qualsiasi luogo dell\'elenco.', - - 'admin.tabs.audit': 'Audit', - - 'admin.audit.subtitle': 'Eventi sensibili di sicurezza e amministrazione (backup, utenti, 2FA, impostazioni).', - 'admin.audit.empty': 'Nessuna voce di audit.', - 'admin.audit.refresh': 'Aggiorna', - 'admin.audit.loadMore': 'Carica altro', - 'admin.audit.showing': '{count} caricati · {total} totali', - 'admin.audit.col.time': 'Ora', - 'admin.audit.col.user': 'Utente', - 'admin.audit.col.action': 'Azione', - 'admin.audit.col.resource': 'Risorsa', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Dettagli', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'Accesso MCP', - 'admin.mcpTokens.title': 'Accesso MCP', - 'admin.mcpTokens.subtitle': 'Gestisci le sessioni OAuth e i token API di tutti gli utenti', - 'admin.mcpTokens.sectionTitle': 'Token API', - 'admin.mcpTokens.owner': 'Proprietario', - 'admin.mcpTokens.tokenName': 'Nome token', - 'admin.mcpTokens.created': 'Creato', - 'admin.mcpTokens.lastUsed': 'Ultimo utilizzo', - 'admin.mcpTokens.never': 'Mai', - 'admin.mcpTokens.empty': 'Non sono ancora stati creati token MCP', - 'admin.mcpTokens.deleteTitle': 'Elimina token', - 'admin.mcpTokens.deleteMessage': 'Questo token verrà revocato immediatamente. L\'utente perderà l\'accesso MCP tramite questo token.', - 'admin.mcpTokens.deleteSuccess': 'Token eliminato', - 'admin.mcpTokens.deleteError': 'Impossibile eliminare il token', - 'admin.mcpTokens.loadError': 'Impossibile caricare i token', - 'admin.oauthSessions.sectionTitle': 'Sessioni OAuth', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Proprietario', - 'admin.oauthSessions.scopes': 'Ambiti', - 'admin.oauthSessions.created': 'Creato', - 'admin.oauthSessions.empty': 'Nessuna sessione OAuth attiva', - 'admin.oauthSessions.revokeTitle': 'Revoca sessione', - 'admin.oauthSessions.revokeMessage': 'Questa sessione OAuth verrà revocata immediatamente. Il client perderà l\'accesso MCP.', - 'admin.oauthSessions.revokeSuccess': 'Sessione revocata', - 'admin.oauthSessions.revokeError': 'Impossibile revocare la sessione', - 'admin.oauthSessions.loadError': 'Impossibile caricare le sessioni OAuth', - - // GitHub - 'admin.tabs.github': 'GitHub', - 'admin.github.title': 'Cronologia rilasci', - 'admin.github.subtitle': 'Ultimi aggiornamenti da {repo}', - 'admin.github.latest': 'Ultimo', - 'admin.github.prerelease': 'Pre-release', - 'admin.github.showDetails': 'Mostra dettagli', - 'admin.github.hideDetails': 'Nascondi dettagli', - 'admin.github.loadMore': 'Carica altro', - 'admin.github.loading': 'Caricamento...', - 'admin.github.error': 'Impossibile caricare i rilasci', - 'admin.github.by': 'da', - 'admin.github.support': 'Mi aiuta a continuare a sviluppare TREK', - - 'admin.update.available': 'Aggiornamento disponibile', - 'admin.update.text': 'TREK {version} è disponibile. Stai eseguendo {current}.', - 'admin.update.button': 'Vedi su GitHub', - 'admin.update.install': 'Installa aggiornamento', - 'admin.update.confirmTitle': 'Installare l\'aggiornamento?', - 'admin.update.confirmText': 'TREK verrà aggiornato da {current} a {version}. Il server si riavvierà automaticamente in seguito.', - 'admin.update.dataInfo': 'Tutti i tuoi dati (viaggi, utenti, chiavi API, caricamenti, Ferie, Atlante, budget) saranno preservati.', - 'admin.update.warning': 'L\'app sarà temporaneamente non disponibile durante il riavvio.', - 'admin.update.confirm': 'Aggiorna ora', - 'admin.update.installing': 'Aggiornamento in corso…', - 'admin.update.success': 'Aggiornamento installato! Il server si sta riavviando…', - 'admin.update.failed': 'Aggiornamento non riuscito', - 'admin.update.backupHint': 'Ti consigliamo di creare un backup prima di aggiornare.', - 'admin.update.backupLink': 'Vai a Backup', - 'admin.update.howTo': 'Come aggiornare', - 'admin.update.dockerText': 'La tua istanza TREK è in esecuzione in Docker. Per aggiornare alla versione {version}, esegui i seguenti comandi sul tuo server:', - 'admin.update.reloadHint': 'Ricarica la pagina tra qualche secondo.', - - // Vacay addon - 'vacay.subtitle': 'Pianifica e gestisci i giorni di ferie', - 'vacay.settings': 'Impostazioni', - 'vacay.year': 'Anno', - 'vacay.addYear': 'Aggiungi anno successivo', - 'vacay.addPrevYear': 'Aggiungi anno precedente', - 'vacay.removeYear': 'Rimuovi anno', - 'vacay.removeYearConfirm': 'Rimuovere {year}?', - 'vacay.removeYearHint': 'Tutte le voci delle ferie e le ferie aziendali di questo anno verranno eliminate in modo permanente.', - 'vacay.remove': 'Rimuovi', - 'vacay.persons': 'Persone', - 'vacay.noPersons': 'Nessuna persona aggiunta', - 'vacay.addPerson': 'Aggiungi persona', - 'vacay.editPerson': 'Modifica persona', - 'vacay.removePerson': 'Rimuovi persona', - 'vacay.removePersonConfirm': 'Rimuovere {name}?', - 'vacay.removePersonHint': 'Tutte le voci delle ferie per questa persona verranno eliminate in modo permanente.', - 'vacay.personName': 'Nome', - 'vacay.personNamePlaceholder': 'Inserisci nome', - 'vacay.color': 'Colore', - 'vacay.add': 'Aggiungi', - 'vacay.legend': 'Legenda', - 'vacay.publicHoliday': 'Festività pubblica', - 'vacay.companyHoliday': 'Ferie aziendali', - 'vacay.weekend': 'Weekend', - 'vacay.modeVacation': 'Ferie', - 'vacay.modeCompany': 'Ferie aziendali', - 'vacay.entitlement': 'Disponibilità', - 'vacay.entitlementDays': 'Giorni', - 'vacay.used': 'Usati', - 'vacay.remaining': 'Rimanenti', - 'vacay.carriedOver': 'dal {year}', - 'vacay.blockWeekends': 'Blocca weekend', - 'vacay.blockWeekendsHint': 'Impedisci le voci ferie nei giorni del weekend', - 'vacay.weekendDays': 'Giorni del weekend', - 'vacay.mon': 'Lun', - 'vacay.tue': 'Mar', - 'vacay.wed': 'Mer', - 'vacay.thu': 'Gio', - 'vacay.fri': 'Ven', - 'vacay.sat': 'Sab', - 'vacay.sun': 'Dom', - 'vacay.publicHolidays': 'Festività pubbliche', - 'vacay.publicHolidaysHint': 'Segna le festività pubbliche nel calendario', - 'vacay.selectCountry': 'Seleziona paese', - 'vacay.selectRegion': 'Seleziona regione (opzionale)', - 'vacay.addCalendar': 'Aggiungi calendario', - 'vacay.calendarLabel': 'Etichetta (opzionale)', - 'vacay.calendarColor': 'Colore', - 'vacay.noCalendars': 'Ancora nessun calendario delle festività aggiunto', - 'vacay.companyHolidays': 'Ferie aziendali', - 'vacay.companyHolidaysHint': 'Consenti di segnare giorni di ferie aziendali', - 'vacay.companyHolidaysNoDeduct': 'Le ferie aziendali non vengono conteggiate nei giorni di ferie.', - 'vacay.weekStart': 'La settimana inizia il', - 'vacay.weekStartHint': 'Scegli se la settimana inizia il lunedì o la domenica', - 'vacay.carryOver': 'Riporto', - 'vacay.carryOverHint': 'Riporta automaticamente i giorni di ferie rimanenti all\'anno successivo', - 'vacay.sharing': 'Condivisione', - 'vacay.sharingHint': 'Condividi il tuo piano ferie con altri utenti TREK', - 'vacay.owner': 'Proprietario', - 'vacay.shareEmailPlaceholder': 'Email dell\'utente TREK', - 'vacay.shareSuccess': 'Piano condiviso con successo', - 'vacay.shareError': 'Impossibile condividere il piano', - 'vacay.dissolve': 'Sciogli unione', - 'vacay.dissolveHint': 'Separa di nuovo i calendari. Le tue voci verranno mantenute.', - 'vacay.dissolveAction': 'Sciogli', - 'vacay.dissolved': 'Calendario separato', - 'vacay.fusedWith': 'Unito con', - 'vacay.you': 'tu', - 'vacay.noData': 'Nessun dato', - 'vacay.changeColor': 'Cambia colore', - 'vacay.inviteUser': 'Invita utente', - 'vacay.inviteHint': 'Invita un altro utente TREK a condividere un calendario ferie combinato.', - 'vacay.selectUser': 'Seleziona utente', - 'vacay.sendInvite': 'Invia invito', - 'vacay.inviteSent': 'Invito inviato', - 'vacay.inviteError': 'Impossibile inviare l\'invito', - 'vacay.pending': 'in attesa', - 'vacay.noUsersAvailable': 'Nessun utente disponibile', - 'vacay.accept': 'Accetta', - 'vacay.decline': 'Rifiuta', - 'vacay.acceptFusion': 'Accetta e unisci', - 'vacay.inviteTitle': 'Richiesta di unione', - 'vacay.inviteWantsToFuse': 'vuole condividere con te un calendario ferie.', - 'vacay.fuseInfo1': 'Entrambi vedrete tutte le voci ferie in un unico calendario condiviso.', - 'vacay.fuseInfo2': 'Entrambe le parti possono creare e modificare le voci reciproche.', - 'vacay.fuseInfo3': 'Entrambe le parti possono eliminare le voci e modificare le disponibilità ferie.', - 'vacay.fuseInfo4': 'Le impostazioni come festività pubbliche e ferie aziendali sono condivise.', - 'vacay.fuseInfo5': 'L\'unione può essere sciolta in qualsiasi momento da una delle due parti. Le tue voci verranno preservate.', - 'nav.myTrips': 'I miei viaggi', - - // Atlas addon - 'atlas.subtitle': 'La tua impronta di viaggio nel mondo', - 'atlas.countries': 'Paesi', - 'atlas.trips': 'Viaggi', - 'atlas.places': 'Luoghi', - 'atlas.unmark': 'Rimuovi', - 'atlas.confirmMark': 'Segnare questo paese come visitato?', - 'atlas.confirmUnmark': 'Rimuovere questo paese dalla tua lista dei visitati?', - 'atlas.confirmUnmarkRegion': 'Rimuovere questa regione dalla tua lista dei visitati?', - 'atlas.markVisited': 'Segna come visitato', - 'atlas.markVisitedHint': 'Aggiungi questo paese alla tua lista dei visitati', - 'atlas.markRegionVisitedHint': 'Aggiungi questa regione alla tua lista dei visitati', - 'atlas.addToBucket': 'Aggiungi alla lista desideri', - 'atlas.addPoi': 'Aggiungi luogo', - 'atlas.bucketNamePlaceholder': 'Nome (paese, città, luogo...)', - 'atlas.month': 'Mese', - 'atlas.addToBucketHint': 'Salvalo come luogo che vuoi visitare', - 'atlas.bucketWhen': 'Quando pensi di visitarlo?', - 'atlas.statsTab': 'Statistiche', - 'atlas.bucketTab': 'Lista desideri', - 'atlas.addBucket': 'Aggiungi alla lista desideri', - 'atlas.bucketNotesPlaceholder': 'Note (opzionale)', - 'atlas.bucketEmpty': 'La tua lista desideri è vuota', - 'atlas.bucketEmptyHint': 'Aggiungi luoghi che sogni di visitare', - 'atlas.days': 'Giorni', - 'atlas.visitedCountries': 'Paesi visitati', - 'atlas.cities': 'Città', - 'atlas.noData': 'Ancora nessun dato di viaggio', - 'atlas.noDataHint': 'Crea un viaggio e aggiungi luoghi per vedere la tua mappa del mondo', - 'atlas.lastTrip': 'Ultimo viaggio', - 'atlas.nextTrip': 'Prossimo viaggio', - 'atlas.daysLeft': 'giorni rimasti', - 'atlas.streak': 'Serie', - 'atlas.year': 'anno', - 'atlas.years': 'anni', - 'atlas.yearInRow': 'anno consecutivo', - 'atlas.yearsInRow': 'anni consecutivi', - 'atlas.tripIn': 'viaggio in', - 'atlas.tripsIn': 'viaggi in', - 'atlas.since': 'dal', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Asia', - 'atlas.northAmerica': 'Nord America', - 'atlas.southAmerica': 'Sud America', - 'atlas.africa': 'Africa', - 'atlas.oceania': 'Oceania', - 'atlas.other': 'Altro', - 'atlas.firstVisit': 'Primo viaggio', - 'atlas.lastVisitLabel': 'Ultimo viaggio', - 'atlas.tripSingular': 'Viaggio', - 'atlas.tripPlural': 'Viaggi', - 'atlas.placeVisited': 'Luogo visitato', - 'atlas.placesVisited': 'Luoghi visitati', - 'atlas.searchCountry': 'Cerca un paese...', - - // Trip Planner - 'trip.tabs.plan': 'Programma', - 'trip.tabs.transports': 'Trasporti', - 'trip.tabs.reservations': 'Prenotazioni', - 'trip.tabs.reservationsShort': 'Pren.', - 'trip.tabs.packing': 'Lista valigia', - 'trip.tabs.packingShort': 'Valigia', - 'trip.tabs.lists': 'Liste', - 'trip.tabs.listsShort': 'Liste', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'File', - 'trip.loading': 'Caricamento viaggio...', - 'trip.mobilePlan': 'Programma', - 'trip.mobilePlaces': 'Luoghi', - 'trip.toast.placeUpdated': 'Luogo aggiornato', - 'trip.toast.placeAdded': 'Luogo aggiunto', - 'trip.toast.placeDeleted': 'Luogo eliminato', - 'trip.toast.selectDay': 'Seleziona prima un giorno', - 'trip.toast.assignedToDay': 'Luogo assegnato al giorno', - 'trip.toast.reorderError': 'Impossibile riordinare', - 'trip.toast.reservationUpdated': 'Prenotazione aggiornata', - 'trip.toast.reservationAdded': 'Prenotazione aggiunta', - 'trip.toast.deleted': 'Eliminato', - 'trip.confirm.deletePlace': 'Sei sicuro di voler eliminare questo luogo?', - 'trip.confirm.deletePlaces': 'Eliminare {count} luoghi?', - 'trip.toast.placesDeleted': '{count} luoghi eliminati', - 'trip.loadingPhotos': 'Caricamento foto dei luoghi...', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Nessun luogo programmato per questo giorno', - 'dayplan.addNote': 'Aggiungi nota', - 'dayplan.editNote': 'Modifica nota', - 'dayplan.noteAdd': 'Aggiungi nota', - 'dayplan.noteEdit': 'Modifica nota', - 'dayplan.noteTitle': 'Nota', - 'dayplan.noteSubtitle': 'Nota giornaliera', - 'dayplan.totalCost': 'Costo totale', - 'dayplan.days': 'Giorni', - 'dayplan.dayN': 'Giorno {n}', - 'dayplan.calculating': 'Calcolo in corso...', - 'dayplan.route': 'Percorso', - 'dayplan.optimize': 'Ottimizza', - 'dayplan.optimized': 'Percorso ottimizzato', - 'dayplan.routeError': 'Impossibile calcolare il percorso', - 'dayplan.toast.needTwoPlaces': 'Servono almeno due luoghi per l\'ottimizzazione del percorso', - 'dayplan.toast.routeOptimized': 'Percorso ottimizzato', - 'dayplan.toast.noGeoPlaces': 'Nessun luogo con coordinate trovato per il calcolo del percorso', - 'dayplan.confirmed': 'Confermata', - 'dayplan.pendingRes': 'In attesa', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Esporta il programma del giorno come PDF', - 'dayplan.pdfError': 'Impossibile esportare il PDF', - 'dayplan.cannotReorderTransport': 'Le prenotazioni con un orario fisso non possono essere riordinate', - 'dayplan.confirmRemoveTimeTitle': 'Rimuovere l\'orario?', - 'dayplan.confirmRemoveTimeBody': 'Questo luogo ha un orario fisso ({time}). Spostarlo rimuoverà l\'orario e consentirà l\'ordinamento libero.', - 'dayplan.confirmRemoveTimeAction': 'Rimuovi orario e sposta', - 'dayplan.cannotDropOnTimed': 'Gli elementi non possono essere posizionati tra voci con orario fisso', - 'dayplan.cannotBreakChronology': 'Ciò interromperebbe l\'ordine cronologico degli elementi e delle prenotazioni pianificati', - - // Places Sidebar - 'places.addPlace': 'Aggiungi Luogo/Attività', - 'places.importFile': 'Importa file', - 'places.sidebarDrop': 'Rilascia per importare', - 'places.importFileHint': 'Importa file .gpx, .kml o .kmz da strumenti come Google My Maps, Google Earth o un tracker GPS.', - 'places.importFileDropHere': 'Clicca per selezionare un file o trascina e rilascia qui', - 'places.importFileDropActive': 'Rilascia il file per selezionarlo', - 'places.importFileUnsupported': 'Tipo di file non supportato. Usa .gpx, .kml o .kmz.', - 'places.importFileTooLarge': 'Il file è troppo grande. La dimensione massima di caricamento è {maxMb} MB.', - 'places.importFileError': 'Importazione non riuscita', - 'places.importAllSkipped': 'Tutti i luoghi erano già nel viaggio.', - 'places.gpxImported': '{count} luoghi importati da GPX', - 'places.gpxImportTypes': 'Cosa vuoi importare?', - 'places.gpxImportWaypoints': 'Waypoint', - 'places.gpxImportRoutes': 'Percorsi', - 'places.gpxImportTracks': 'Tracce (con geometria percorso)', - 'places.gpxImportNoneSelected': 'Seleziona almeno un tipo da importare.', - 'places.kmlImportTypes': 'Cosa vuoi importare?', - 'places.kmlImportPoints': 'Punti (Placemarks)', - 'places.kmlImportPaths': 'Percorsi (LineStrings)', - 'places.kmlImportNoneSelected': 'Seleziona almeno un tipo.', - 'places.selectionCount': '{count} selezionato/i', - 'places.deleteSelected': 'Elimina selezionati', - 'places.kmlKmzImported': '{count} luoghi importati da KMZ/KML', - 'places.urlResolved': 'Luogo importato dall\'URL', - 'places.importList': 'Importa lista', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Importati: {created} • Saltati: {skipped}', - 'places.importGoogleList': 'Lista Google', - 'places.importNaverList': 'Lista Naver', - 'places.googleListHint': 'Incolla un link condiviso di una lista Google Maps per importare tutti i luoghi.', - 'places.googleListImported': '{count} luoghi importati da "{list}"', - 'places.googleListError': 'Importazione lista Google Maps non riuscita', - 'places.naverListHint': 'Incolla un link condiviso di una lista Naver Maps per importare tutti i luoghi.', - 'places.naverListImported': '{count} luoghi importati da "{list}"', - 'places.naverListError': 'Importazione lista Naver Maps non riuscita', - 'places.viewDetails': 'Visualizza dettagli', - 'places.assignToDay': 'A quale giorno aggiungere?', - 'places.all': 'Tutti', - 'places.unplanned': 'Non pianificati', - 'places.filterTracks': 'Tracce', - 'places.search': 'Cerca luoghi...', - 'places.allCategories': 'Tutte le categorie', - 'places.categoriesSelected': 'categorie', - 'places.clearFilter': 'Cancella filtro', - 'places.count': '{count} luoghi', - 'places.countSingular': '1 luogo', - 'places.allPlanned': 'Tutti i luoghi sono programmati', - 'places.noneFound': 'Nessun luogo trovato', - 'places.editPlace': 'Modifica luogo', - 'places.formName': 'Nome', - 'places.formNamePlaceholder': 'es. Torre Eiffel', - 'places.formDescription': 'Descrizione', - 'places.formDescriptionPlaceholder': 'Breve descrizione...', - 'places.formAddress': 'Indirizzo', - 'places.formAddressPlaceholder': 'Via, Città, Paese', - 'places.formLat': 'Latitudine (es. 48.8566)', - 'places.formLng': 'Longitudine (es. 2.3522)', - 'places.formCategory': 'Categoria', - 'places.noCategory': 'Nessuna categoria', - 'places.categoryNamePlaceholder': 'Nome categoria', - 'places.formTime': 'Ora', - 'places.startTime': 'Inizio', - 'places.endTime': 'Fine', - 'places.endTimeBeforeStart': 'L\'ora di fine è precedente all\'ora di inizio', - 'places.timeCollision': 'Sovrapposizione di orario con:', - 'places.formWebsite': 'Sito web', - 'places.formNotes': 'Note', - 'places.formNotesPlaceholder': 'Note personali...', - 'places.formReservation': 'Prenotazione', - 'places.reservationNotesPlaceholder': 'Note della prenotazione, numero di conferma...', - 'places.mapsSearchPlaceholder': 'Cerca luoghi...', - 'places.mapsSearchError': 'Impossibile cercare i luoghi.', - 'places.loadingDetails': 'Caricamento dettagli del luogo…', - 'places.osmHint': 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.', - 'places.osmActive': 'Ricerca tramite OpenStreetMap (senza foto, valutazioni o orari di apertura). Aggiungi una chiave API Google nelle Impostazioni per dati avanzati.', - 'places.categoryCreateError': 'Impossibile creare la categoria', - 'places.nameRequired': 'Inserisci un nome', - 'places.saveError': 'Impossibile salvare', - // Place Inspector - 'inspector.opened': 'Aperto', - 'inspector.closed': 'Chiuso', - 'inspector.openingHours': 'Orari di apertura', - 'inspector.showHours': 'Mostra orari di apertura', - 'inspector.files': 'File', - 'inspector.filesCount': '{count} file', - 'inspector.removeFromDay': 'Rimuovi dal giorno', - 'inspector.remove': 'Rimuovi', - 'inspector.addToDay': 'Aggiungi al giorno', - 'inspector.confirmedRes': 'Prenotazione confermata', - 'inspector.pendingRes': 'Prenotazione in attesa', - 'inspector.google': 'Apri in Google Maps', - 'inspector.website': 'Apri sito web', - 'inspector.addRes': 'Prenotazione', - 'inspector.editRes': 'Modifica prenotazione', - 'inspector.participants': 'Partecipanti', - 'inspector.trackStats': 'Dati del percorso', - - // Reservations - 'reservations.title': 'Prenotazioni', - 'reservations.empty': 'Ancora nessuna prenotazione', - 'reservations.emptyHint': 'Aggiungi prenotazioni per voli, alloggi e altro', - 'reservations.add': 'Aggiungi prenotazione', - 'reservations.addManual': 'Prenotazione manuale', - 'reservations.placeHint': 'Suggerimento: è meglio creare le prenotazioni direttamente da un luogo per collegarle al tuo programma del giorno.', - 'reservations.confirmed': 'Confermata', - 'reservations.pending': 'In attesa', - 'reservations.summary': '{confirmed} confermate, {pending} in attesa', - 'reservations.fromPlan': 'Dal programma', - 'reservations.showFiles': 'Mostra file', - 'reservations.editTitle': 'Modifica prenotazione', - 'reservations.status': 'Stato', - 'reservations.datetime': 'Data e ora', - 'reservations.startTime': 'Ora di inizio', - 'reservations.endTime': 'Ora di fine', - 'reservations.date': 'Data', - 'reservations.time': 'Ora', - 'reservations.timeAlt': 'Ora (alternativa, es. 19:30)', - 'reservations.notes': 'Note', - 'reservations.notesPlaceholder': 'Note aggiuntive...', - 'reservations.meta.airline': 'Compagnia aerea', - 'reservations.meta.flightNumber': 'N. volo', - 'reservations.meta.from': 'Da', - 'reservations.meta.to': 'A', - 'reservations.needsReview': 'Verifica', - 'reservations.needsReviewHint': 'L\'aeroporto non è stato riconosciuto automaticamente — conferma la posizione.', - 'reservations.searchLocation': 'Cerca stazione, porto, indirizzo...', - 'airport.searchPlaceholder': 'Codice o città dell\'aeroporto (es. FRA)', - 'map.connections': 'Connessioni', - 'map.showConnections': 'Mostra percorsi prenotati', - 'map.hideConnections': 'Nascondi percorsi prenotati', - 'settings.bookingLabels': 'Etichette percorsi prenotati', - 'settings.bookingLabelsHint': 'Mostra i nomi di stazioni / aeroporti sulla mappa. Se disattivato, viene mostrata solo l\'icona.', - 'reservations.meta.trainNumber': 'N. treno', - 'reservations.meta.platform': 'Binario', - 'reservations.meta.seat': 'Posto', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in fino a', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Alloggio', - 'reservations.meta.pickAccommodation': 'Collega a un alloggio', - 'reservations.meta.noAccommodation': 'Nessuno', - 'reservations.meta.hotelPlace': 'Alloggio', - 'reservations.meta.pickHotel': 'Seleziona alloggio', - 'reservations.meta.fromDay': 'Da', - 'reservations.meta.toDay': 'A', - 'reservations.meta.selectDay': 'Seleziona giorno', - 'reservations.type.flight': 'Volo', - 'reservations.type.hotel': 'Alloggio', - 'reservations.type.restaurant': 'Ristorante', - 'reservations.type.train': 'Treno', - 'reservations.type.car': 'Auto', - 'reservations.type.cruise': 'Crociera', - 'reservations.type.event': 'Evento', - 'reservations.type.tour': 'Tour', - 'reservations.type.other': 'Altro', - 'reservations.confirm.delete': 'Sei sicuro di voler eliminare la prenotazione "{name}"?', - 'reservations.confirm.deleteTitle': 'Eliminare la prenotazione?', - 'reservations.confirm.deleteBody': '"{name}" verrà eliminato in modo permanente.', - 'reservations.toast.updated': 'Prenotazione aggiornata', - 'reservations.toast.removed': 'Prenotazione eliminata', - 'reservations.toast.fileUploaded': 'File caricato', - 'reservations.toast.uploadError': 'Impossibile caricare', - 'reservations.newTitle': 'Nuova prenotazione', - 'reservations.bookingType': 'Tipo di prenotazione', - 'reservations.titleLabel': 'Titolo', - 'reservations.titlePlaceholder': 'es. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Posizione / Indirizzo', - 'reservations.locationPlaceholder': 'Indirizzo, aeroporto, hotel...', - 'reservations.confirmationCode': 'Codice prenotazione', - 'reservations.confirmationPlaceholder': 'es. ABC12345', - 'reservations.day': 'Giorno', - 'reservations.noDay': 'Nessun giorno', - 'reservations.place': 'Luogo', - 'reservations.noPlace': 'Nessun luogo', - 'reservations.pendingSave': 'verrà salvato…', - 'reservations.uploading': 'Caricamento...', - 'reservations.attachFile': 'Allega file', - 'reservations.linkExisting': 'Collega file esistente', - 'reservations.toast.saveError': 'Impossibile salvare', - 'reservations.toast.updateError': 'Impossibile aggiornare', - 'reservations.toast.deleteError': 'Impossibile eliminare', - 'reservations.confirm.remove': 'Rimuovere la prenotazione per "{name}"?', - 'reservations.linkAssignment': 'Collega all\'assegnazione del giorno', - 'reservations.pickAssignment': 'Seleziona un\'assegnazione dal tuo programma...', - 'reservations.noAssignment': 'Nessun collegamento (autonomo)', - 'reservations.price': 'Prezzo', - 'reservations.budgetCategory': 'Categoria budget', - 'reservations.budgetCategoryPlaceholder': 'es. Trasporto, Alloggio', - 'reservations.budgetCategoryAuto': 'Auto (dal tipo di prenotazione)', - 'reservations.budgetHint': 'Una voce di budget verrà creata automaticamente al salvataggio.', - 'reservations.departureDate': 'Partenza', - 'reservations.arrivalDate': 'Arrivo', - 'reservations.departureTime': 'Ora part.', - 'reservations.arrivalTime': 'Ora arr.', - 'reservations.pickupDate': 'Ritiro', - 'reservations.returnDate': 'Riconsegna', - 'reservations.pickupTime': 'Ora ritiro', - 'reservations.returnTime': 'Ora riconsegna', - 'reservations.endDate': 'Data fine', - 'reservations.meta.departureTimezone': 'TZ part.', - 'reservations.meta.arrivalTimezone': 'TZ arr.', - 'reservations.span.departure': 'Partenza', - 'reservations.span.arrival': 'Arrivo', - 'reservations.span.inTransit': 'In transito', - 'reservations.span.pickup': 'Ritiro', - 'reservations.span.return': 'Riconsegna', - 'reservations.span.active': 'Attivo', - 'reservations.span.start': 'Inizio', - 'reservations.span.end': 'Fine', - 'reservations.span.ongoing': 'In corso', - 'reservations.validation.endBeforeStart': 'La data/ora di fine deve essere successiva alla data/ora di inizio', - 'reservations.addBooking': 'Aggiungi prenotazione', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'Esporta CSV', - 'budget.emptyTitle': 'Ancora nessun budget creato', - 'budget.emptyText': 'Crea categorie e voci per pianificare il budget del tuo viaggio', - 'budget.emptyPlaceholder': 'Inserisci nome categoria...', - 'budget.createCategory': 'Crea categoria', - 'budget.category': 'Categoria', - 'budget.categoryName': 'Nome categoria', - 'budget.table.name': 'Nome', - 'budget.table.total': 'Totale', - 'budget.table.persons': 'Persone', - 'budget.table.days': 'Giorni', - 'budget.table.perPerson': 'Per persona', - 'budget.table.perDay': 'Per giorno', - 'budget.table.perPersonDay': 'P. p / gio.', - 'budget.table.note': 'Nota', - 'budget.table.date': 'Data', - 'budget.newEntry': 'Nuova voce', - 'budget.defaultEntry': 'Nuova voce', - 'budget.defaultCategory': 'Nuova categoria', - 'budget.total': 'Totale', - 'budget.totalBudget': 'Budget totale', - 'budget.byCategory': 'Per categoria', - 'budget.editTooltip': 'Clicca per modificare', - 'budget.linkedToReservation': 'Collegato a una prenotazione — modifica il nome lì', - 'budget.confirm.deleteCategory': 'Sei sicuro di voler eliminare la categoria "{name}" con {count} voci?', - 'budget.deleteCategory': 'Elimina categoria', - 'budget.perPerson': 'Per persona', - 'budget.paid': 'Pagato', - 'budget.open': 'Aperto', - 'budget.noMembers': 'Nessun membro assegnato', - 'budget.settlement': 'Regolamento', - 'budget.settlementInfo': 'Clicca sull\'avatar di un membro su una voce di budget per contrassegnarlo in verde — significa che ha pagato. Il regolamento mostra poi chi deve quanto a chi.', - 'budget.netBalances': 'Saldi netti', - - // Files - 'files.title': 'File', - 'files.pageTitle': 'File e documenti', - 'files.subtitle': '{count} file per {trip}', - 'files.download': 'Scarica', - 'files.openError': 'Impossibile aprire il file', - 'files.downloadPdf': 'Scarica PDF', - 'files.count': '{count} file', - 'files.countSingular': '1 documento', - 'files.uploaded': '{count} caricati', - 'files.uploadError': 'Caricamento non riuscito', - 'files.dropzone': 'Trascina qui i file', - 'files.dropzoneHint': 'oppure clicca per sfogliare', - 'files.allowedTypes': 'Immagini, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Caricamento...', - 'files.filterAll': 'Tutti', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Immagini', - 'files.filterDocs': 'Documenti', - 'files.filterCollab': 'Note Collaborazione', - 'files.sourceCollab': 'Da Note Collaborazione', - 'files.empty': 'Ancora nessun file', - 'files.emptyHint': 'Carica file per allegarli al tuo viaggio', - 'files.openTab': 'Apri in una nuova scheda', - 'files.confirm.delete': 'Sei sicuro di voler eliminare questo file?', - 'files.toast.deleted': 'File eliminato', - 'files.toast.deleteError': 'Impossibile eliminare il file', - 'files.sourcePlan': 'Programma giornaliero', - 'files.sourceBooking': 'Prenotazione', - 'files.sourceTransport': 'Trasporto', - 'files.attach': 'Allega', - 'files.pasteHint': 'Puoi anche incollare immagini dagli appunti (Ctrl+V)', - 'files.trash': 'Cestino', - 'files.trashEmpty': 'Il cestino è vuoto', - 'files.emptyTrash': 'Svuota cestino', - 'files.restore': 'Ripristina', - 'files.star': 'Aggiungi ai preferiti', - 'files.unstar': 'Rimuovi dai preferiti', - 'files.assign': 'Assegna', - 'files.assignTitle': 'Assegna file', - 'files.assignPlace': 'Luogo', - 'files.assignBooking': 'Prenotazione', - 'files.assignTransport': 'Trasporto', - 'files.unassigned': 'Non assegnato', - 'files.unlink': 'Rimuovi collegamento', - 'files.toast.trashed': 'Spostato nel cestino', - 'files.toast.restored': 'File ripristinato', - 'files.toast.trashEmptied': 'Cestino svuotato', - 'files.toast.assigned': 'File assegnato', - 'files.toast.assignError': 'Assegnazione fallita', - 'files.toast.restoreError': 'Ripristino fallito', - 'files.confirm.permanentDelete': 'Eliminare questo file in modo permanente? Questa operazione non può essere annullata.', - 'files.confirm.emptyTrash': 'Eliminare in modo permanente tutti i file nel cestino? Questa operazione non può essere annullata.', - 'files.noteLabel': 'Nota', - 'files.notePlaceholder': 'Aggiungi una nota...', - - // Packing - 'packing.title': 'Lista valigia', - 'packing.empty': 'La lista valigia è vuota', - 'packing.import': 'Importa', - 'packing.importTitle': 'Importa lista valigia', - 'packing.importHint': 'Un elemento per riga. Formato: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale)', - 'packing.importPlaceholder': 'Igiene, Spazzolino\nAbbigliamento, Magliette, 200\nDocumenti, Passaporto, , Bagaglio a mano\nElettronica, Caricabatterie, 50, Valigia, checked', - 'packing.importCsv': 'Carica CSV/TXT', - 'packing.importAction': 'Importa {count}', - 'packing.importSuccess': '{count} elementi importati', - 'packing.importError': 'Importazione non riuscita', - 'packing.importEmpty': 'Nessun elemento da importare', - 'packing.progress': '{packed} di {total} in valigia ({percent}%)', - 'packing.clearChecked': 'Rimuovi {count} spuntati', - 'packing.clearCheckedShort': 'Rimuovi {count}', - 'packing.suggestions': 'Suggerimenti', - 'packing.suggestionsTitle': 'Aggiungi suggerimenti', - 'packing.allSuggested': 'Tutti i suggerimenti aggiunti', - 'packing.allPacked': 'Tutto in valigia!', - 'packing.addPlaceholder': 'Aggiungi nuovo elemento...', - 'packing.categoryPlaceholder': 'Categoria...', - 'packing.filterAll': 'Tutti', - 'packing.filterOpen': 'Da fare', - 'packing.filterDone': 'Fatto', - 'packing.emptyTitle': 'La lista valigia è vuota', - 'packing.emptyHint': 'Aggiungi elementi o usa i suggerimenti', - 'packing.emptyFiltered': 'Nessun elemento corrisponde a questo filtro', - 'packing.menuRename': 'Rinomina', - 'packing.menuCheckAll': 'Seleziona tutti', - 'packing.menuUncheckAll': 'Deseleziona tutti', - 'packing.menuDeleteCat': 'Elimina categoria', - 'packing.noMembers': 'Nessun membro del viaggio', - 'packing.addItem': 'Aggiungi elemento', - 'packing.addItemPlaceholder': 'Nome elemento...', - 'packing.addCategory': 'Aggiungi categoria', - 'packing.newCategoryPlaceholder': 'Nome categoria (es. Abbigliamento)', - 'packing.applyTemplate': 'Applica modello', - 'packing.template': 'Modello', - 'packing.templateApplied': '{count} elementi aggiunti dal modello', - 'packing.templateError': 'Impossibile applicare il modello', - 'packing.saveAsTemplate': 'Salva come modello', - 'packing.templateName': 'Nome modello', - 'packing.templateSaved': 'Lista bagagli salvata come modello', - 'packing.bags': 'Valigie', - 'packing.noBag': 'Non assegnato', - 'packing.totalWeight': 'Peso totale', - 'packing.bagName': 'Nome valigia...', - 'packing.addBag': 'Aggiungi valigia', - 'packing.changeCategory': 'Cambia categoria', - 'packing.confirm.clearChecked': 'Sei sicuro di voler rimuovere {count} elementi spuntati?', - 'packing.confirm.deleteCat': 'Sei sicuro di voler eliminare la categoria "{name}" con {count} elementi?', - 'packing.defaultCategory': 'Altro', - 'packing.toast.saveError': 'Impossibile salvare', - 'packing.toast.deleteError': 'Impossibile eliminare', - 'packing.toast.renameError': 'Impossibile rinominare', - 'packing.toast.addError': 'Impossibile aggiungere', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Passaporto', category: 'Documenti' }, - { name: 'Carta d\'identità', category: 'Documenti' }, - { name: 'Assicurazione di viaggio', category: 'Documenti' }, - { name: 'Biglietti aerei', category: 'Documenti' }, - { name: 'Carta di credito', category: 'Finanze' }, - { name: 'Contanti', category: 'Finanze' }, - { name: 'Visto', category: 'Documenti' }, - { name: 'Magliette', category: 'Abbigliamento' }, - { name: 'Pantaloni', category: 'Abbigliamento' }, - { name: 'Intimo', category: 'Abbigliamento' }, - { name: 'Calzini', category: 'Abbigliamento' }, - { name: 'Giacca', category: 'Abbigliamento' }, - { name: 'Pigiama', category: 'Abbigliamento' }, - { name: 'Costume da bagno', category: 'Abbigliamento' }, - { name: 'Giacca a vento', category: 'Abbigliamento' }, - { name: 'Scarpe comode', category: 'Abbigliamento' }, - { name: 'Spazzolino da denti', category: 'Igiene personale' }, - { name: 'Dentifricio', category: 'Igiene personale' }, - { name: 'Shampoo', category: 'Igiene personale' }, - { name: 'Deodorante', category: 'Igiene personale' }, - { name: 'Crema solare', category: 'Igiene personale' }, - { name: 'Rasoio', category: 'Igiene personale' }, - { name: 'Caricabatterie', category: 'Elettronica' }, - { name: 'Power bank', category: 'Elettronica' }, - { name: 'Cuffie', category: 'Elettronica' }, - { name: 'Adattatore da viaggio', category: 'Elettronica' }, - { name: 'Macchina fotografica', category: 'Elettronica' }, - { name: 'Antidolorifici', category: 'Salute' }, - { name: 'Cerotti', category: 'Salute' }, - { name: 'Disinfettante', category: 'Salute' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Condividi viaggio', - 'members.inviteUser': 'Invita utente', - 'members.selectUser': 'Seleziona utente...', - 'members.invite': 'Invita', - 'members.allHaveAccess': 'Tutti gli utenti hanno già accesso.', - 'members.access': 'Accesso', - 'members.person': 'persona', - 'members.persons': 'persone', - 'members.you': 'tu', - 'members.owner': 'Proprietario', - 'members.leaveTrip': 'Abbandona viaggio', - 'members.removeAccess': 'Rimuovi accesso', - 'members.confirmLeave': 'Abbandonare il viaggio? Perderai l\'accesso.', - 'members.confirmRemove': 'Rimuovere l\'accesso per questo utente?', - 'members.loadError': 'Impossibile caricare i membri', - 'members.added': 'aggiunto', - 'members.addError': 'Impossibile aggiungere', - 'members.removed': 'Membro rimosso', - 'members.removeError': 'Impossibile rimuovere', - - // Categories (Admin) - 'categories.title': 'Categorie', - 'categories.subtitle': 'Gestisci le categorie per i luoghi', - 'categories.new': 'Nuova categoria', - 'categories.empty': 'Ancora nessuna categoria', - 'categories.namePlaceholder': 'Nome categoria', - 'categories.icon': 'Icona', - 'categories.color': 'Colore', - 'categories.customColor': 'Scegli colore personalizzato', - 'categories.preview': 'Anteprima', - 'categories.defaultName': 'Categoria', - 'categories.update': 'Aggiorna', - 'categories.create': 'Crea', - 'categories.confirm.delete': 'Eliminare la categoria? I luoghi in questa categoria non verranno eliminati.', - 'categories.toast.loadError': 'Impossibile caricare le categorie', - 'categories.toast.nameRequired': 'Inserisci un nome', - 'categories.toast.updated': 'Categoria aggiornata', - 'categories.toast.created': 'Categoria creata', - 'categories.toast.saveError': 'Impossibile salvare', - 'categories.toast.deleted': 'Categoria eliminata', - 'categories.toast.deleteError': 'Impossibile eliminare', - - // Backup (Admin) - 'backup.title': 'Backup dati', - 'backup.subtitle': 'Database e tutti i file caricati', - 'backup.refresh': 'Aggiorna', - 'backup.upload': 'Carica backup', - 'backup.uploading': 'Caricamento...', - 'backup.create': 'Crea backup', - 'backup.creating': 'Creazione...', - 'backup.empty': 'Ancora nessun backup', - 'backup.createFirst': 'Crea primo backup', - 'backup.download': 'Scarica', - 'backup.restore': 'Ripristina', - 'backup.confirm.restore': 'Ripristinare il backup "{name}"?\n\nTutti i dati attuali verranno sostituiti con il backup.', - 'backup.confirm.uploadRestore': 'Scaricare e ripristinare il file di backup "{name}"?\n\nTutti i dati attuali verranno sovrascritti.', - 'backup.confirm.delete': 'Eliminare il backup "{name}"?', - 'backup.toast.loadError': 'Impossibile caricare i backup', - 'backup.toast.created': 'Backup creato con successo', - 'backup.toast.createError': 'Impossibile creare il backup', - 'backup.toast.restored': 'Backup ripristinato. La pagina verrà ricaricata...', - 'backup.toast.restoreError': 'Impossibile ripristinare', - 'backup.toast.uploadError': 'Impossibile caricare', - 'backup.toast.deleted': 'Backup eliminato', - 'backup.toast.deleteError': 'Impossibile eliminare', - 'backup.toast.downloadError': 'Download non riuscito', - 'backup.toast.settingsSaved': 'Impostazioni auto-backup salvate', - 'backup.toast.settingsError': 'Impossibile salvare le impostazioni', - 'backup.auto.title': 'Auto-Backup', - 'backup.auto.subtitle': 'Backup automatico pianificato', - 'backup.auto.enable': 'Abilita auto-backup', - 'backup.auto.enableHint': 'I backup verranno creati automaticamente in base alla pianificazione scelta', - 'backup.auto.interval': 'Intervallo', - 'backup.auto.hour': 'Esegui all\'ora', - 'backup.auto.hourHint': 'Ora locale del server (formato {format})', - 'backup.auto.dayOfWeek': 'Giorno della settimana', - 'backup.auto.dayOfMonth': 'Giorno del mese', - 'backup.auto.dayOfMonthHint': 'Limitato a 1–28 per compatibilità con tutti i mesi', - 'backup.auto.scheduleSummary': 'Pianificazione', - 'backup.auto.summaryDaily': 'Ogni giorno alle {hour}:00', - 'backup.auto.summaryWeekly': 'Ogni {day} alle {hour}:00', - 'backup.auto.summaryMonthly': 'Giorno {day} di ogni mese alle {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'L\'auto-backup è configurato tramite variabili d\'ambiente Docker. Per modificare queste impostazioni, aggiorna il tuo docker-compose.yml e riavvia il container.', - 'backup.auto.copyEnv': 'Copia variabili env Docker', - 'backup.auto.envCopied': 'Variabili env Docker copiate negli appunti', - 'backup.auto.keepLabel': 'Elimina i vecchi backup dopo', - 'backup.dow.sunday': 'Dom', - 'backup.dow.monday': 'Lun', - 'backup.dow.tuesday': 'Mar', - 'backup.dow.wednesday': 'Mer', - 'backup.dow.thursday': 'Gio', - 'backup.dow.friday': 'Ven', - 'backup.dow.saturday': 'Sab', - 'backup.interval.hourly': 'Ogni ora', - 'backup.interval.daily': 'Giornaliero', - 'backup.interval.weekly': 'Settimanale', - 'backup.interval.monthly': 'Mensile', - 'backup.keep.1day': '1 giorno', - 'backup.keep.3days': '3 giorni', - 'backup.keep.7days': '7 giorni', - 'backup.keep.14days': '14 giorni', - 'backup.keep.30days': '30 giorni', - 'backup.keep.forever': 'Conserva per sempre', - - // Photos - 'photos.title': 'Foto', - 'photos.subtitle': '{count} foto per {trip}', - 'photos.dropHere': 'Trascina le foto qui...', - 'photos.dropHereActive': 'Trascina le foto qui', - 'photos.captionForAll': 'Didascalia (per tutti)', - 'photos.captionPlaceholder': 'Didascalia opzionale...', - 'photos.addCaption': 'Aggiungi didascalia...', - 'photos.allDays': 'Tutti i giorni', - 'photos.noPhotos': 'Ancora nessuna foto', - 'photos.uploadHint': 'Carica le foto del tuo viaggio', - 'photos.clickToSelect': 'o clicca per selezionare', - 'photos.linkPlace': 'Collega luogo', - 'photos.noPlace': 'Nessun luogo', - 'photos.uploadN': 'Caricamento di {n} foto', - 'photos.linkDay': 'Collega giorno', - 'photos.noDay': 'Nessun giorno', - 'photos.dayLabel': 'Giorno {number}', - 'photos.photoSelected': 'Foto selezionata', - 'photos.photosSelected': 'Foto selezionate', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · fino a 30 foto', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Ripristinare il backup?', - 'backup.restoreWarning': 'Tutti i dati attuali (viaggi, luoghi, utenti, caricamenti) verranno sostituiti in modo permanente dal backup. Questa azione non può essere annullata.', - 'backup.restoreTip': 'Suggerimento: Crea un backup dello stato attuale prima di ripristinare.', - 'backup.restoreConfirm': 'Sì, ripristina', - - // PDF - 'pdf.travelPlan': 'Programma di viaggio', - 'pdf.planned': 'Programmato', - 'pdf.costLabel': 'Costo EUR', - 'pdf.preview': 'Anteprima PDF', - 'pdf.saveAsPdf': 'Salva come PDF', - - // Planner - 'planner.places': 'Luoghi', - 'planner.bookings': 'Prenotazioni', - 'planner.packingList': 'Lista valigia', - 'planner.documents': 'Documenti', - 'planner.dayPlan': 'Programma giornaliero', - 'planner.reservations': 'Prenotazioni', - 'planner.minTwoPlaces': 'Servono almeno 2 luoghi con coordinate', - 'planner.noGeoPlaces': 'Nessun luogo con coordinate disponibile', - 'planner.routeCalculated': 'Percorso calcolato', - 'planner.routeCalcFailed': 'Il percorso non è stato calcolato', - 'planner.routeError': 'Errore nel calcolo del percorso', - 'planner.icsExportFailed': 'Esportazione ICS non riuscita', - 'planner.routeOptimized': 'Percorso ottimizzato', - 'planner.reservationUpdated': 'Prenotazione aggiornata', - 'planner.reservationAdded': 'Prenotazione aggiunta', - 'planner.confirmDeleteReservation': 'Eliminare la prenotazione?', - 'planner.reservationDeleted': 'Prenotazione eliminata', - 'planner.days': 'Giorni', - 'planner.allPlaces': 'Tutti i luoghi', - 'planner.totalPlaces': '{n} luoghi in totale', - 'planner.noDaysPlanned': 'Nessun giorno ancora programmato', - 'planner.editTrip': 'Modifica viaggio \u2192', - 'planner.placeOne': '1 luogo', - 'planner.placeN': '{n} luoghi', - 'planner.addNote': 'Aggiungi nota', - 'planner.noEntries': 'Nessuna voce per questo giorno', - 'planner.addPlace': 'Aggiungi luogo/attività', - 'planner.addPlaceShort': '+ Aggiungi luogo/attività', - 'planner.resPending': 'Prenotazione in attesa \u00B7 ', - 'planner.resConfirmed': 'Prenotazione confermata \u00B7 ', - 'planner.notePlaceholder': 'Nota\u2026', - 'planner.noteTimePlaceholder': 'Ora (opzionale)', - 'planner.noteExamplePlaceholder': 'es. S3 alle 14:30 dalla stazione centrale, traghetto dal molo 7, pausa pranzo\u2026', - 'planner.totalCost': 'Costo totale', - 'planner.searchPlaces': 'Cerca luoghi\u2026', - 'planner.allCategories': 'Tutte le categorie', - 'planner.noPlacesFound': 'Nessun luogo trovato', - 'planner.addFirstPlace': 'Aggiungi primo luogo', - 'planner.noReservations': 'Nessuna prenotazione', - 'planner.addFirstReservation': 'Aggiungi prima prenotazione', - 'planner.new': 'Nuovo', - 'planner.addToDay': '+ Giorno', - 'planner.calculating': 'Calcolo in corso\u2026', - 'planner.route': 'Percorso', - 'planner.optimize': 'Ottimizza', - 'planner.openGoogleMaps': 'Apri in Google Maps', - 'planner.selectDayHint': 'Seleziona un giorno dall\'elenco a sinistra per vedere il programma', - 'planner.noPlacesForDay': 'Ancora nessun luogo per questo giorno', - 'planner.addPlacesLink': 'Aggiungi luoghi \u2192', - 'planner.minTotal': 'min. totali', - 'planner.noReservation': 'Nessuna prenotazione', - 'planner.removeFromDay': 'Rimuovi dal giorno', - 'planner.addToThisDay': 'Aggiungi al giorno', - 'planner.overview': 'Panoramica', - 'planner.noDays': 'Ancora nessun giorno', - 'planner.editTripToAddDays': 'Modifica viaggio per aggiungere giorni', - 'planner.dayCount': '{n} Giorni', - 'planner.clickToUnlock': 'Clicca per sbloccare', - 'planner.keepPosition': 'Mantieni la posizione durante l\'ottimizzazione del percorso', - 'planner.dayDetails': 'Dettagli del giorno', - 'planner.dayN': 'Giorno {n}', - - // Dashboard Stats - 'stats.countries': 'Paesi', - 'stats.cities': 'Città', - 'stats.trips': 'Viaggi', - 'stats.places': 'Luoghi', - 'stats.worldProgress': 'Progresso nel mondo', - 'stats.visited': 'visitati', - 'stats.remaining': 'rimanenti', - 'stats.visitedCountries': 'Paesi visitati', - - // Day Detail Panel - 'day.precipProb': 'Probabilità di pioggia', - 'day.precipitation': 'Precipitazioni', - 'day.wind': 'Vento', - 'day.sunrise': 'Alba', - 'day.sunset': 'Tramonto', - 'day.hourlyForecast': 'Previsione oraria', - 'day.climateHint': 'Medie storiche \u2014 previsioni reali disponibili entro 16 giorni da questa data.', - 'day.noWeather': 'Nessun dato meteo disponibile. Aggiungi un luogo con coordinate.', - 'day.overview': 'Panoramica giornaliera', - 'day.accommodation': 'Alloggio', - 'day.addAccommodation': 'Aggiungi alloggio', - 'day.hotelDayRange': 'Applica ai giorni', - 'day.noPlacesForHotel': 'Aggiungi prima i luoghi al tuo viaggio', - 'day.allDays': 'Tutti', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Fino a', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Conferma', - 'day.editAccommodation': 'Modifica alloggio', - 'day.reservations': 'Prenotazioni', - - // Photos / Immich - 'memories.title': 'Foto', - 'memories.notConnected': 'Immich non connesso', - 'memories.notConnectedHint': 'Connetti la tua istanza Immich nelle Impostazioni per vedere qui le foto del tuo viaggio.', - 'memories.notConnectedMultipleHint': 'Collega uno di questi provider di foto: {provider_names} nelle Impostazioni per poter aggiungere foto a questo viaggio.', - 'memories.noDates': 'Aggiungi le date al tuo viaggio per caricare le foto.', - 'memories.noPhotos': 'Nessuna foto trovata', - 'memories.noPhotosHint': 'Nessuna foto trovata in Immich per l\'intervallo di date di questo viaggio.', - 'memories.photosFound': 'foto', - 'memories.fromOthers': 'da altri', - 'memories.sharePhotos': 'Condividi foto', - 'memories.sharing': 'Condivisione', - 'memories.reviewTitle': 'Rivedi le tue foto', - 'memories.reviewHint': 'Clicca sulle foto per escluderle dalla condivisione.', - 'memories.shareCount': 'Condividi {count} foto', - 'memories.providerUrl': 'URL del server', - 'memories.providerApiKey': 'Chiave API', - 'memories.providerUsername': 'Nome utente', - 'memories.providerPassword': 'Password', - 'memories.providerOTP': 'Codice MFA (se abilitato)', - 'memories.skipSSLVerification': 'Ignora la verifica del certificato SSL', - 'memories.immichAutoUpload': 'Rispecchia le foto del journey su Immich al caricamento', - 'memories.providerUrlHintSynology': 'Includi il percorso dell\'app Foto nell\'URL, es. https://nas:5001/photo', - 'memories.testConnection': 'Test connessione', - 'memories.testShort': 'Prova', - 'memories.testFirst': 'Testa prima la connessione', - 'memories.connected': 'Connesso', - 'memories.disconnected': 'Non connesso', - 'memories.connectionSuccess': 'Connesso a Immich', - 'memories.connectionError': 'Impossibile connettersi a Immich', - 'memories.saved': 'Impostazioni {provider_name} salvate', - 'memories.providerDisconnectedBanner': 'La connessione a {provider_name} è persa. Riconnetti nelle Impostazioni per visualizzare le foto.', - 'memories.saveError': 'Impossibile salvare le impostazioni di {provider_name}', - 'memories.saveRouteNotConfigured': 'La route di salvataggio non è configurata per questo provider', - 'memories.testRouteNotConfigured': 'La route di test non è configurata per questo provider', - 'memories.fillRequiredFields': 'Per favore compila tutti i campi obbligatori', - 'memories.addPhotos': 'Aggiungi foto', - 'memories.linkAlbum': 'Collega album', - 'memories.selectAlbum': 'Seleziona album Immich', - 'memories.selectAlbumMultiple': 'Seleziona album', - 'memories.noAlbums': 'Nessun album trovato', - 'memories.syncAlbum': 'Sincronizza album', - 'memories.unlinkAlbum': 'Scollega', - 'memories.photos': 'foto', - 'memories.selectPhotos': 'Seleziona foto da Immich', - 'memories.selectPhotosMultiple': 'Seleziona foto', - 'memories.selectHint': 'Tocca le foto per selezionarle.', - 'memories.selected': 'selezionate', - 'memories.addSelected': 'Aggiungi {count} foto', - 'memories.alreadyAdded': 'Aggiunta', - 'memories.private': 'Privato', - 'memories.stopSharing': 'Interrompi condivisione', - 'memories.oldest': 'Prima le più vecchie', - 'memories.newest': 'Prima le più recenti', - 'memories.allLocations': 'Tutte le posizioni', - 'memories.tripDates': 'Date del viaggio', - 'memories.allPhotos': 'Tutte le foto', - 'memories.confirmShareTitle': 'Condividere con i membri del viaggio?', - 'memories.confirmShareHint': '{count} foto saranno visibili a tutti i membri di questo viaggio. Potrai rendere private le singole foto in seguito.', - 'memories.confirmShareButton': 'Condividi foto', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Note', - 'collab.tabs.polls': 'Sondaggi', - 'collab.whatsNext.title': "Cosa c'è dopo", - 'collab.whatsNext.today': 'Oggi', - 'collab.whatsNext.tomorrow': 'Domani', - 'collab.whatsNext.empty': 'Nessuna attività imminente', - 'collab.whatsNext.until': 'a', - 'collab.whatsNext.emptyHint': 'Le attività con orari appariranno qui', - 'collab.chat.send': 'Invia', - 'collab.chat.placeholder': 'Scrivi un messaggio...', - 'collab.chat.empty': 'Inizia la conversazione', - 'collab.chat.emptyHint': 'I messaggi sono condivisi con tutti i membri del viaggio', - 'collab.chat.emptyDesc': 'Condividi idee, programmi e aggiornamenti con il tuo gruppo di viaggio', - 'collab.chat.today': 'Oggi', - 'collab.chat.yesterday': 'Ieri', - 'collab.chat.deletedMessage': 'ha eliminato un messaggio', - 'collab.chat.reply': 'Rispondi', - 'collab.chat.loadMore': 'Carica messaggi precedenti', - 'collab.chat.justNow': 'ora', - 'collab.chat.minutesAgo': '{n}m fa', - 'collab.chat.hoursAgo': '{n}h fa', - 'collab.notes.title': 'Note', - 'collab.notes.new': 'Nuova nota', - 'collab.notes.empty': 'Ancora nessuna nota', - 'collab.notes.emptyHint': 'Inizia a raccogliere idee e programmi', - 'collab.notes.all': 'Tutte', - 'collab.notes.titlePlaceholder': 'Titolo della nota', - 'collab.notes.contentPlaceholder': 'Scrivi qualcosa...', - 'collab.notes.categoryPlaceholder': 'Categoria', - 'collab.notes.newCategory': 'Nuova categoria...', - 'collab.notes.category': 'Categoria', - 'collab.notes.noCategory': 'Nessuna categoria', - 'collab.notes.color': 'Colore', - 'collab.notes.save': 'Salva', - 'collab.notes.cancel': 'Annulla', - 'collab.notes.edit': 'Modifica', - 'collab.notes.delete': 'Elimina', - 'collab.notes.pin': 'Fissa', - 'collab.notes.unpin': 'Rimuovi', - 'collab.notes.daysAgo': '{n}g fa', - 'collab.notes.categorySettings': 'Gestisci categorie', - 'collab.notes.create': 'Crea', - 'collab.notes.website': 'Sito web', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Allega file', - 'collab.notes.noCategoriesYet': 'Ancora nessuna categoria', - 'collab.notes.emptyDesc': 'Crea una nota per iniziare', - 'collab.polls.title': 'Sondaggi', - 'collab.polls.new': 'Nuovo sondaggio', - 'collab.polls.empty': 'Ancora nessun sondaggio', - 'collab.polls.emptyHint': 'Chiedi al gruppo e votate insieme', - 'collab.polls.question': 'Domanda', - 'collab.polls.questionPlaceholder': 'Cosa dovremmo fare?', - 'collab.polls.addOption': '+ Aggiungi opzione', - 'collab.polls.optionPlaceholder': 'Opzione {n}', - 'collab.polls.create': 'Crea sondaggio', - 'collab.polls.close': 'Chiudi', - 'collab.polls.closed': 'Chiuso', - 'collab.polls.votes': '{n} voti', - 'collab.polls.vote': '{n} voto', - 'collab.polls.multipleChoice': 'Scelta multipla', - 'collab.polls.multiChoice': 'Scelta multipla', - 'collab.polls.deadline': 'Scadenza', - 'collab.polls.option': 'Opzione', - 'collab.polls.options': 'Opzioni', - 'collab.polls.delete': 'Elimina', - 'collab.polls.closedSection': 'Chiusi', - - // Permissions - 'admin.tabs.permissions': 'Permessi', - 'perm.title': 'Impostazioni dei permessi', - 'perm.subtitle': 'Controlla chi può eseguire azioni nell\'applicazione', - 'perm.saved': 'Impostazioni dei permessi salvate', - 'perm.resetDefaults': 'Ripristina predefiniti', - 'perm.customized': 'personalizzato', - 'perm.level.admin': 'Solo amministratore', - 'perm.level.tripOwner': 'Proprietario del viaggio', - 'perm.level.tripMember': 'Membri del viaggio', - 'perm.level.everybody': 'Tutti', - 'perm.cat.trip': 'Gestione viaggi', - 'perm.cat.members': 'Gestione membri', - 'perm.cat.files': 'File', - 'perm.cat.content': 'Contenuti e programma', - 'perm.cat.extras': 'Budget, bagagli e collaborazione', - 'perm.action.trip_create': 'Creare viaggi', - 'perm.action.trip_edit': 'Modificare dettagli del viaggio', - 'perm.action.trip_delete': 'Eliminare viaggi', - 'perm.action.trip_archive': 'Archiviare / dearchiviare viaggi', - 'perm.action.trip_cover_upload': 'Caricare immagine di copertina', - 'perm.action.member_manage': 'Aggiungere / rimuovere membri', - 'perm.action.file_upload': 'Caricare file', - 'perm.action.file_edit': 'Modificare metadati dei file', - 'perm.action.file_delete': 'Eliminare file', - 'perm.action.place_edit': 'Aggiungere / modificare / eliminare luoghi', - 'perm.action.day_edit': 'Modificare giorni, note e assegnazioni', - 'perm.action.reservation_edit': 'Gestire prenotazioni', - 'perm.action.budget_edit': 'Gestire budget', - 'perm.action.packing_edit': 'Gestire liste bagagli', - 'perm.action.collab_edit': 'Collaborazione (note, sondaggi, chat)', - 'perm.action.share_manage': 'Gestire link di condivisione', - 'perm.actionHint.trip_create': 'Chi può creare nuovi viaggi', - 'perm.actionHint.trip_edit': 'Chi può modificare nome, date, descrizione e valuta del viaggio', - 'perm.actionHint.trip_delete': 'Chi può eliminare definitivamente un viaggio', - 'perm.actionHint.trip_archive': 'Chi può archiviare o dearchiviare un viaggio', - 'perm.actionHint.trip_cover_upload': 'Chi può caricare o modificare l\'immagine di copertina', - 'perm.actionHint.member_manage': 'Chi può invitare o rimuovere membri del viaggio', - 'perm.actionHint.file_upload': 'Chi può caricare file in un viaggio', - 'perm.actionHint.file_edit': 'Chi può modificare descrizioni e link dei file', - 'perm.actionHint.file_delete': 'Chi può spostare file nel cestino o eliminarli definitivamente', - 'perm.actionHint.place_edit': 'Chi può aggiungere, modificare o eliminare luoghi', - 'perm.actionHint.day_edit': 'Chi può modificare giorni, note dei giorni e assegnazioni dei luoghi', - 'perm.actionHint.reservation_edit': 'Chi può creare, modificare o eliminare prenotazioni', - 'perm.actionHint.budget_edit': 'Chi può creare, modificare o eliminare voci di budget', - 'perm.actionHint.packing_edit': 'Chi può gestire articoli da bagaglio e borse', - 'perm.actionHint.collab_edit': 'Chi può creare note, sondaggi e inviare messaggi', - 'perm.actionHint.share_manage': 'Chi può creare o eliminare link di condivisione pubblici', - - // Undo - 'undo.button': 'Annulla', - 'undo.tooltip': 'Annulla: {action}', - 'undo.assignPlace': 'Luogo assegnato al giorno', - 'undo.removeAssignment': 'Luogo rimosso dal giorno', - 'undo.reorder': 'Luoghi riordinati', - 'undo.optimize': 'Percorso ottimizzato', - 'undo.deletePlace': 'Luogo eliminato', - 'undo.deletePlaces': 'Luoghi eliminati', - 'undo.moveDay': 'Luogo spostato in altro giorno', - 'undo.lock': 'Blocco luogo modificato', - 'undo.importGpx': 'Importazione GPX', - 'undo.importKeyholeMarkup': 'Importazione KMZ/KML', - 'undo.importGoogleList': 'Importazione Google Maps', - 'undo.importNaverList': 'Importazione Naver Maps', - 'undo.addPlace': 'Luogo aggiunto', - 'undo.done': 'Annullato: {action}', - // Notifications - 'notifications.title': 'Notifiche', - 'notifications.markAllRead': 'Segna tutto come letto', - 'notifications.deleteAll': 'Elimina tutto', - 'notifications.showAll': 'Vedi tutte le notifiche', - 'notifications.empty': 'Nessuna notifica', - 'notifications.emptyDescription': 'Sei aggiornato!', - 'notifications.all': 'Tutte', - 'notifications.unreadOnly': 'Non lette', - 'notifications.markRead': 'Segna come letto', - 'notifications.markUnread': 'Segna come non letto', - 'notifications.delete': 'Elimina', - 'notifications.system': 'Sistema', - 'notifications.synologySessionCleared.title': 'Synology Photos disconnesso', - 'notifications.synologySessionCleared.text': 'Il server o l\'account è cambiato — vai alle Impostazioni per testare nuovamente la connessione.', - 'memories.error.loadAlbums': 'Caricamento album non riuscito', - 'memories.error.linkAlbum': 'Collegamento album non riuscito', - 'memories.error.unlinkAlbum': 'Scollegamento album non riuscito', - 'memories.error.syncAlbum': 'Sincronizzazione album non riuscita', - 'memories.error.loadPhotos': 'Caricamento foto non riuscito', - 'memories.error.addPhotos': 'Aggiunta foto non riuscita', - 'memories.error.removePhoto': 'Rimozione foto non riuscita', - 'memories.error.toggleSharing': 'Aggiornamento condivisione non riuscito', - 'notifications.test.title': 'Notifica di test da {actor}', - 'notifications.test.text': 'Questa è una semplice notifica di test.', - 'notifications.test.booleanTitle': '{actor} richiede la tua approvazione', - 'notifications.test.booleanText': 'Notifica di test con risposta.', - 'notifications.test.accept': 'Approva', - 'notifications.test.decline': 'Rifiuta', - 'notifications.test.navigateTitle': 'Dai un\'occhiata', - 'notifications.test.navigateText': 'Notifica di test con navigazione.', - 'notifications.test.goThere': 'Vai', - 'notifications.test.adminTitle': 'Comunicazione admin', - 'notifications.test.adminText': '{actor} ha inviato una notifica di test a tutti gli amministratori.', - 'notifications.test.tripTitle': '{actor} ha pubblicato nel tuo viaggio', - 'notifications.test.tripText': 'Notifica di test per il viaggio "{trip}".', - - // Todo - 'todo.subtab.packing': 'Lista di imballaggio', - 'todo.subtab.todo': 'Da fare', - 'todo.completed': 'completato/i', - 'todo.filter.all': 'Tutti', - 'todo.filter.open': 'Aperto', - 'todo.filter.done': 'Fatto', - 'todo.uncategorized': 'Senza categoria', - 'todo.namePlaceholder': 'Nome attività', - 'todo.descriptionPlaceholder': 'Descrizione (facoltativa)', - 'todo.unassigned': 'Non assegnato', - 'todo.noCategory': 'Nessuna categoria', - 'todo.hasDescription': 'Ha descrizione', - 'todo.addItem': 'Nuova attività', - 'todo.sidebar.sortBy': 'Ordina per', - 'todo.priority': 'Priorità', - 'todo.newCategoryLabel': 'nuova', - 'budget.categoriesLabel': 'categorie', - 'todo.newCategory': 'Nome categoria', - 'todo.addCategory': 'Aggiungi categoria', - 'todo.newItem': 'Nuova attività', - 'todo.empty': 'Nessuna attività ancora. Aggiungi un\'attività per iniziare!', - 'todo.filter.my': 'Le mie attività', - 'todo.filter.overdue': 'Scaduta', - 'todo.sidebar.tasks': 'Attività', - 'todo.sidebar.categories': 'Categorie', - 'todo.detail.title': 'Attività', - 'todo.detail.description': 'Descrizione', - 'todo.detail.category': 'Categoria', - 'todo.detail.dueDate': 'Scadenza', - 'todo.detail.assignedTo': 'Assegnato a', - 'todo.detail.delete': 'Elimina', - 'todo.detail.save': 'Salva modifiche', - 'todo.detail.create': 'Crea attività', - 'todo.detail.priority': 'Priorità', - 'todo.detail.noPriority': 'Nessuna', - 'todo.sortByPrio': 'Priorità', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nuova versione disponibile', - 'settings.notificationPreferences.noChannels': 'Nessun canale di notifica configurato. Chiedi a un amministratore di configurare notifiche via e-mail o webhook.', - 'settings.webhookUrl.label': 'URL webhook', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Inserisci il tuo URL webhook Discord, Slack o personalizzato per ricevere notifiche.', - 'settings.webhookUrl.saved': 'URL webhook salvato', - 'settings.webhookUrl.test': 'Test', - 'settings.webhookUrl.testSuccess': 'Webhook di test inviato con successo', - 'settings.webhookUrl.testFailed': 'Invio webhook di test fallito', - 'settings.ntfyUrl.topicLabel': 'Argomento Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL server Ntfy (opzionale)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': "Inserisci il tuo argomento Ntfy per ricevere notifiche push. Lascia il server vuoto per usare il valore predefinito configurato dall'amministratore.", - 'settings.ntfyUrl.tokenLabel': 'Token di accesso (opzionale)', - 'settings.ntfyUrl.tokenHint': 'Richiesto per gli argomenti protetti da password.', - 'settings.ntfyUrl.saved': 'Impostazioni Ntfy salvate', - 'settings.ntfyUrl.test': 'Testa', - 'settings.ntfyUrl.testSuccess': 'Notifica di test Ntfy inviata con successo', - 'settings.ntfyUrl.testFailed': 'Notifica di test Ntfy fallita', - 'settings.ntfyUrl.tokenCleared': 'Token di accesso rimosso', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Le notifiche in-app sono sempre attive e non possono essere disabilitate globalmente.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook admin', - 'admin.notifications.adminWebhookPanel.hint': 'Questo webhook viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dai webhook utente e si attiva automaticamente quando è configurato un URL.', - 'admin.notifications.adminWebhookPanel.saved': 'URL webhook admin salvato', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Webhook di test inviato con successo', - 'admin.notifications.adminWebhookPanel.testFailed': 'Invio webhook di test fallito', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Il webhook admin si attiva automaticamente quando è configurato un URL', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Consente agli utenti di configurare i propri argomenti ntfy per le notifiche push. Imposta il server predefinito di seguito per precompilare le impostazioni utente.', - 'admin.notifications.testNtfy': 'Invia Ntfy di test', - 'admin.notifications.testNtfySuccess': 'Ntfy di test inviato con successo', - 'admin.notifications.testNtfyFailed': 'Invio Ntfy di test fallito', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', - 'admin.notifications.adminNtfyPanel.hint': 'Questo argomento Ntfy viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dagli argomenti per utente e si attiva sempre quando è configurato.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL server Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Usato anche come server predefinito per le notifiche ntfy degli utenti. Lasciare vuoto per usare ntfy.sh. Gli utenti possono sovrascriverlo nelle proprie impostazioni.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Argomento admin', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token di accesso (opzionale)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Token di accesso admin rimosso', - 'admin.notifications.adminNtfyPanel.saved': 'Impostazioni Ntfy admin salvate', - 'admin.notifications.adminNtfyPanel.test': 'Invia Ntfy di test', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Ntfy di test inviato con successo', - 'admin.notifications.adminNtfyPanel.testFailed': 'Invio Ntfy di test fallito', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Il Ntfy admin si attiva sempre quando un argomento è configurato', - 'admin.notifications.adminNotificationsHint': 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.', - 'admin.notifications.tripReminders.title': 'Promemoria viaggio', - 'admin.notifications.tripReminders.hint': 'Invia una notifica promemoria prima dell\'inizio di un viaggio (richiede giorni di promemoria impostati sul viaggio).', - 'admin.notifications.tripReminders.enabled': 'Promemoria viaggio attivati', - 'admin.notifications.tripReminders.disabled': 'Promemoria viaggio disattivati', - 'admin.tabs.notifications': 'Notifiche', - 'notifications.versionAvailable.title': 'Aggiornamento disponibile', - 'notifications.versionAvailable.text': 'TREK {version} è ora disponibile.', - 'notifications.versionAvailable.button': 'Visualizza dettagli', - 'notif.test.title': '[Test] Notifica', - 'notif.test.simple.text': 'Questa è una semplice notifica di test.', - 'notif.test.boolean.text': 'Accetti questa notifica di test?', - 'notif.test.navigate.text': 'Clicca qui sotto per accedere alla dashboard.', - - // Notifications - 'notif.trip_invite.title': 'Invito al viaggio', - 'notif.trip_invite.text': '{actor} ti ha invitato a {trip}', - 'notif.booking_change.title': 'Prenotazione aggiornata', - 'notif.booking_change.text': '{actor} ha aggiornato una prenotazione in {trip}', - 'notif.trip_reminder.title': 'Promemoria viaggio', - 'notif.trip_reminder.text': 'Il tuo viaggio {trip} si avvicina!', - 'notif.todo_due.title': 'Attività in scadenza', - 'notif.todo_due.text': '{todo} in {trip} scade il {due}', - 'notif.vacay_invite.title': 'Invito Vacay Fusion', - 'notif.vacay_invite.text': '{actor} ti ha invitato a fondere i piani vacanza', - 'notif.photos_shared.title': 'Foto condivise', - 'notif.photos_shared.text': '{actor} ha condiviso {count} foto in {trip}', - 'notif.collab_message.title': 'Nuovo messaggio', - 'notif.collab_message.text': '{actor} ha inviato un messaggio in {trip}', - 'notif.packing_tagged.title': 'Assegnazione bagagli', - 'notif.packing_tagged.text': '{actor} ti ha assegnato a {category} in {trip}', - 'notif.version_available.title': 'Nuova versione disponibile', - 'notif.version_available.text': 'TREK {version} è ora disponibile', - 'notif.action.view_trip': 'Vedi viaggio', - 'notif.action.view_collab': 'Vedi messaggi', - 'notif.action.view_packing': 'Vedi bagagli', - 'notif.action.view_photos': 'Vedi foto', - 'notif.action.view_vacay': 'Vedi Vacay', - 'notif.action.view_admin': 'Vai all\'admin', - 'notif.action.view': 'Vedi', - 'notif.action.accept': 'Accetta', - 'notif.action.decline': 'Rifiuta', - 'notif.generic.title': 'Notifica', - 'notif.generic.text': 'Hai una nuova notifica', - 'notif.dev.unknown_event.title': '[DEV] Evento sconosciuto', - 'notif.dev.unknown_event.text': 'Il tipo di evento "{event}" non è registrato in EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'proprio ora', - 'common.hoursAgo': '{count}h fa', - 'common.daysAgo': '{count}g fa', - 'journey.search.placeholder': 'Cerca viaggi…', - 'journey.search.noResults': 'Nessun viaggio corrisponde a "{query}"', - 'journey.title': 'Diario di viaggio', - 'journey.subtitle': 'Segui i tuoi viaggi in tempo reale', - 'journey.new': 'Nuovo diario', - 'journey.create': 'Crea', - 'journey.titlePlaceholder': 'Dove stai andando?', - 'journey.empty': 'Nessun diario ancora', - 'journey.emptyHint': 'Inizia a documentare il tuo prossimo viaggio', - 'journey.deleted': 'Diario eliminato', - 'journey.createError': 'Impossibile creare il diario', - 'journey.deleteError': 'Impossibile eliminare il diario', - 'journey.deleteConfirmTitle': 'Elimina', - 'journey.deleteConfirmMessage': 'Eliminare "{title}"? Questa azione non può essere annullata.', - 'journey.deleteConfirmGeneric': 'Sei sicuro di voler eliminare questo?', - 'journey.notFound': 'Diario non trovato', - 'journey.photos': 'Foto', - 'journey.timelineEmpty': 'Nessuna tappa ancora', - 'journey.timelineEmptyHint': 'Aggiungi un check-in o scrivi una voce di diario per iniziare', - 'journey.status.draft': 'Bozza', - 'journey.status.active': 'Attivo', - 'journey.status.completed': 'Completato', - 'journey.status.upcoming': 'In arrivo', - 'journey.status.archived': 'Archiviato', - 'journey.checkin.add': 'Check-in', - 'journey.checkin.namePlaceholder': 'Nome del luogo', - 'journey.checkin.notesPlaceholder': 'Note (facoltativo)', - 'journey.checkin.save': 'Salva', - 'journey.checkin.error': 'Impossibile salvare il check-in', - 'journey.entry.add': 'Diario', - 'journey.entry.edit': 'Modifica voce', - 'journey.entry.titlePlaceholder': 'Titolo (facoltativo)', - 'journey.entry.bodyPlaceholder': 'Cosa è successo oggi?', - 'journey.entry.save': 'Salva', - 'journey.entry.error': 'Impossibile salvare la voce', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Caricamento fallito', - 'journey.share.share': 'Condividi', - 'journey.share.public': 'Pubblico', - 'journey.share.linkCopied': 'Link pubblico copiato', - 'journey.share.disabled': 'Condivisione pubblica disattivata', - 'journey.editor.titlePlaceholder': 'Dai un nome a questo momento...', - 'journey.editor.bodyPlaceholder': 'Racconta la storia di questa giornata...', - 'journey.editor.placePlaceholder': 'Luogo (facoltativo)', - 'journey.editor.tagsPlaceholder': 'Tag: gioiello nascosto, miglior pasto, da rivisitare...', - 'journey.visibility.private': 'Privato', - 'journey.visibility.shared': 'Condiviso', - 'journey.visibility.public': 'Pubblico', - 'journey.emptyState.title': 'La tua storia inizia qui', - 'journey.emptyState.subtitle': 'Fai un check-in o scrivi la tua prima voce di diario', - 'journey.frontpage.subtitle': 'Trasforma i tuoi viaggi in storie indimenticabili', - 'journey.frontpage.createJourney': 'Crea diario', - 'journey.frontpage.activeJourney': 'Diario attivo', - 'journey.frontpage.allJourneys': 'Tutti i diari', - 'journey.frontpage.journeys': 'diari', - 'journey.frontpage.createNew': 'Crea un nuovo diario', - 'journey.frontpage.createNewSub': 'Scegli viaggi, scrivi storie, condividi le tue avventure', - 'journey.frontpage.live': 'In diretta', - 'journey.frontpage.synced': 'Sincronizzato', - 'journey.frontpage.continueWriting': 'Continua a scrivere', - 'journey.frontpage.updated': 'Aggiornato {time}', - 'journey.frontpage.suggestionLabel': 'Viaggio appena terminato', - 'journey.frontpage.suggestionText': 'Trasforma {title} in un diario di viaggio', - 'journey.frontpage.dismiss': 'Ignora', - 'journey.frontpage.journeyName': 'Nome del diario', - 'journey.frontpage.namePlaceholder': 'es. Sud-est asiatico 2026', - 'journey.frontpage.selectTrips': 'Seleziona viaggi', - 'journey.frontpage.tripsSelected': 'viaggi selezionati', - 'journey.frontpage.trips': 'viaggi', - 'journey.frontpage.placesImported': 'luoghi saranno importati', - 'journey.frontpage.places': 'luoghi', - 'journey.detail.backToJourney': 'Torna al diario', - 'journey.detail.syncedWithTrips': 'Sincronizzato con i viaggi', - 'journey.detail.addEntry': 'Aggiungi voce', - 'journey.detail.newEntry': 'Nuova voce', - 'journey.detail.editEntry': 'Modifica voce', - 'journey.detail.noEntries': 'Nessuna voce ancora', - 'journey.detail.noEntriesHint': 'Aggiungi un viaggio per iniziare con voci precompilate', - 'journey.detail.noPhotos': 'Nessuna foto ancora', - 'journey.detail.noPhotosHint': 'Carica foto nelle voci o sfoglia la tua libreria Immich/Synology', - 'journey.detail.journeyStats': 'Statistiche del diario', - 'journey.detail.syncedTrips': 'Viaggi sincronizzati', - 'journey.detail.noTripsLinked': 'Nessun viaggio collegato ancora', - 'journey.detail.contributors': 'Contributori', - 'journey.detail.readMore': 'Leggi di più', - 'journey.detail.prosCons': 'Pro e contro', - 'journey.detail.photos': 'foto', - 'journey.detail.day': 'Giorno {number}', - 'journey.detail.places': 'luoghi', - 'journey.stats.days': 'Giorni', - 'journey.stats.cities': 'Città', - 'journey.stats.entries': 'Voci', - 'journey.stats.photos': 'Foto', - 'journey.stats.places': 'Luoghi', - 'journey.skeletons.show': 'Mostra suggerimenti', - 'journey.skeletons.hide': 'Nascondi suggerimenti', - 'journey.verdict.lovedIt': 'Adorato', - 'journey.verdict.couldBeBetter': 'Potrebbe essere meglio', - 'journey.synced.places': 'luoghi', - 'journey.synced.synced': 'sincronizzato', - 'journey.editor.discardChangesConfirm': 'Hai modifiche non salvate. Vuoi scartarle?', - 'journey.editor.uploadFailed': 'Caricamento foto non riuscito', - 'journey.editor.uploadPhotos': 'Carica foto', - 'journey.editor.uploading': 'Caricamento...', - 'journey.editor.uploadingProgress': 'Caricamento {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} di {total} foto non riuscite — salva di nuovo per riprovare', - 'journey.editor.fromGallery': 'Dalla galleria', - 'journey.editor.allPhotosAdded': 'Tutte le foto sono già state aggiunte', - 'journey.editor.writeStory': 'Scrivi la tua storia...', - 'journey.editor.prosCons': 'Pro e contro', - 'journey.editor.pros': 'Pro', - 'journey.editor.cons': 'Contro', - 'journey.editor.proPlaceholder': 'Qualcosa di fantastico...', - 'journey.editor.conPlaceholder': 'Non così fantastico...', - 'journey.editor.addAnother': 'Aggiungi un altro', - 'journey.editor.date': 'Data', - 'journey.editor.location': 'Luogo', - 'journey.editor.searchLocation': 'Cerca luogo...', - 'journey.editor.mood': 'Umore', - 'journey.editor.weather': 'Meteo', - 'journey.editor.photoFirst': '1°', - 'journey.editor.makeFirst': 'Metti 1°', - 'journey.editor.searching': 'Ricerca...', - 'journey.mood.amazing': 'Fantastico', - 'journey.mood.good': 'Buono', - 'journey.mood.neutral': 'Neutro', - 'journey.mood.rough': 'Difficile', - 'journey.weather.sunny': 'Soleggiato', - 'journey.weather.partly': 'Parzialmente nuvoloso', - 'journey.weather.cloudy': 'Nuvoloso', - 'journey.weather.rainy': 'Piovoso', - 'journey.weather.stormy': 'Temporalesco', - 'journey.weather.cold': 'Nevoso', - 'journey.trips.linkTrip': 'Collega viaggio', - 'journey.trips.searchTrip': 'Cerca viaggio', - 'journey.trips.searchPlaceholder': 'Nome del viaggio o destinazione...', - 'journey.trips.noTripsAvailable': 'Nessun viaggio disponibile', - 'journey.trips.link': 'Collega', - 'journey.trips.tripLinked': 'Viaggio collegato', - 'journey.trips.linkFailed': 'Collegamento del viaggio fallito', - 'journey.trips.addTrip': 'Aggiungi viaggio', - 'journey.trips.unlinkTrip': 'Scollega viaggio', - 'journey.trips.unlinkMessage': 'Scollegare "{title}"? Tutte le voci e le foto sincronizzate da questo viaggio saranno eliminate definitivamente. Questa azione non può essere annullata.', - 'journey.trips.unlink': 'Scollega', - 'journey.trips.tripUnlinked': 'Viaggio scollegato', - 'journey.trips.unlinkFailed': 'Scollegamento del viaggio fallito', - 'journey.trips.noTripsLinkedSettings': 'Nessun viaggio collegato', - 'journey.contributors.invite': 'Invita contributore', - 'journey.contributors.searchUser': 'Cerca utente', - 'journey.contributors.searchPlaceholder': 'Nome utente o e-mail...', - 'journey.contributors.noUsers': 'Nessun utente trovato', - 'journey.contributors.role': 'Ruolo', - 'journey.contributors.added': 'Contributore aggiunto', - 'journey.contributors.addFailed': 'Impossibile aggiungere il contributore', - 'journey.share.publicShare': 'Condivisione pubblica', - 'journey.share.createLink': 'Crea link di condivisione', - 'journey.share.linkCreated': 'Link di condivisione creato', - 'journey.share.createFailed': 'Creazione del link fallita', - 'journey.share.copy': 'Copia', - 'journey.share.copied': 'Copiato!', - 'journey.share.timeline': 'Cronologia', - 'journey.share.gallery': 'Galleria', - 'journey.share.map': 'Mappa', - 'journey.share.removeLink': 'Rimuovi link di condivisione', - 'journey.share.linkDeleted': 'Link di condivisione eliminato', - 'journey.share.deleteFailed': 'Eliminazione fallita', - 'journey.share.updateFailed': 'Aggiornamento fallito', - - // Journey — Invite - 'journey.invite.role': 'Ruolo', - 'journey.invite.viewer': 'Visualizzatore', - 'journey.invite.editor': 'Editore', - 'journey.invite.invite': 'Invita', - 'journey.invite.inviting': 'Invito in corso...', - 'journey.settings.title': 'Impostazioni del diario', - 'journey.settings.coverImage': 'Immagine di copertina', - 'journey.settings.changeCover': 'Cambia copertina', - 'journey.settings.addCover': 'Aggiungi immagine di copertina', - 'journey.settings.name': 'Nome', - 'journey.settings.subtitle': 'Sottotitolo', - 'journey.settings.subtitlePlaceholder': 'es. Thailandia, Vietnam e Cambogia', - 'journey.settings.endJourney': 'Archivia il viaggio', - 'journey.settings.reopenJourney': 'Ripristina il viaggio', - 'journey.settings.archived': 'Viaggio archiviato', - 'journey.settings.reopened': 'Viaggio riaperto', - 'journey.settings.endDescription': 'Nasconde il badge In diretta. Puoi riaprire in qualsiasi momento.', - 'journey.settings.delete': 'Elimina', - 'journey.settings.deleteJourney': 'Elimina diario', - 'journey.settings.deleteMessage': 'Eliminare "{title}"? Tutte le voci e le foto andranno perse.', - 'journey.settings.saved': 'Impostazioni salvate', - 'journey.settings.saveFailed': 'Salvataggio fallito', - 'journey.settings.coverUpdated': 'Copertina aggiornata', - 'journey.settings.coverFailed': 'Caricamento fallito', - 'journey.settings.failedToDelete': 'Eliminazione non riuscita', - 'journey.entries.deleteTitle': 'Elimina voce', - 'journey.photosUploaded': '{count} foto caricate', - 'journey.photosUploadFailed': 'Alcune foto non sono state caricate', - 'journey.photosAdded': '{count} foto aggiunte', - 'journey.public.notFound': 'Non trovato', - 'journey.public.notFoundMessage': 'Questo diario non esiste o il link è scaduto.', - 'journey.public.readOnly': 'Sola lettura · Diario pubblico', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Condiviso tramite', - 'journey.public.madeWith': 'Creato con', - 'journey.pdf.journeyBook': 'Diario di viaggio', - 'journey.pdf.madeWith': 'Creato con TREK', - 'journey.pdf.day': 'Giorno', - 'journey.pdf.theEnd': 'Fine', - 'journey.pdf.saveAsPdf': 'Salva come PDF', - 'journey.pdf.pages': 'pagine', - 'journey.picker.tripPeriod': 'Periodo del viaggio', - 'journey.picker.dateRange': 'Intervallo di date', - 'journey.picker.allPhotos': 'Tutte le foto', - 'journey.picker.albums': 'Album', - 'journey.picker.selected': 'selezionati', - 'journey.picker.addTo': 'Aggiungi a', - 'journey.picker.newGallery': 'Nuova galleria', - 'journey.picker.selectAll': 'Seleziona tutto', - 'journey.picker.deselectAll': 'Deseleziona tutto', - 'journey.picker.noAlbums': 'Nessun album trovato', - 'journey.picker.selectDate': 'Seleziona data', - 'journey.picker.search': 'Cerca', - 'dashboard.greeting.morning': 'Buongiorno,', - 'dashboard.greeting.afternoon': 'Buon pomeriggio,', - 'dashboard.greeting.evening': 'Buonasera,', - 'dashboard.mobile.liveNow': 'In diretta', - 'dashboard.mobile.tripProgress': 'Progresso del viaggio', - 'dashboard.mobile.daysLeft': '{count} giorni rimanenti', - 'dashboard.mobile.places': 'Luoghi', - 'dashboard.mobile.buddies': 'Compagni', - 'dashboard.mobile.newTrip': 'Nuovo viaggio', - 'dashboard.mobile.currency': 'Valuta', - 'dashboard.mobile.timezone': 'Fuso orario', - 'dashboard.mobile.upcomingTrips': 'Viaggi in arrivo', - 'dashboard.mobile.yourTrips': 'I tuoi viaggi', - 'dashboard.mobile.trips': 'viaggi', - 'dashboard.mobile.starts': 'Inizio', - 'dashboard.mobile.duration': 'Durata', - 'dashboard.mobile.day': 'giorno', - 'dashboard.mobile.days': 'giorni', - 'dashboard.mobile.ongoing': 'In corso', - 'dashboard.mobile.startsToday': 'Inizia oggi', - 'dashboard.mobile.tomorrow': 'Domani', - 'dashboard.mobile.inDays': 'Tra {count} giorni', - 'dashboard.mobile.inMonths': 'Tra {count} mesi', - 'dashboard.mobile.completed': 'Completato', - 'dashboard.mobile.currencyConverter': 'Convertitore di valuta', - 'nav.profile': 'Profilo', - 'nav.bottomSettings': 'Impostazioni', - 'nav.bottomAdmin': 'Amministrazione', - 'nav.bottomLogout': 'Disconnetti', - 'nav.bottomAdminBadge': 'Admin', - 'dayplan.mobile.addPlace': 'Aggiungi luogo', - 'dayplan.mobile.searchPlaces': 'Cerca luoghi...', - 'dayplan.mobile.allAssigned': 'Tutti i luoghi assegnati', - 'dayplan.mobile.noMatch': 'Nessun risultato', - 'dayplan.mobile.createNew': 'Crea nuovo luogo', - 'admin.addons.catalog.journey.name': 'Diario di viaggio', - 'admin.addons.catalog.journey.description': 'Tracciamento viaggi e diario con check-in, foto e storie quotidiane', - // OAuth scope groups - 'oauth.scope.group.trips': 'Viaggi', - 'oauth.scope.group.places': 'Luoghi', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Bagagli', - 'oauth.scope.group.todos': 'Attività', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Prenotazioni', - 'oauth.scope.group.collab': 'Collaborazione', - 'oauth.scope.group.notifications': 'Notifiche', - 'oauth.scope.group.vacay': 'Ferie', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Meteo', - 'oauth.scope.group.journey': 'Diario di viaggio', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Visualizza viaggi e itinerari', - 'oauth.scope.trips:read.description': 'Leggi viaggi, giorni, note giornaliere e membri', - 'oauth.scope.trips:write.label': 'Modifica viaggi e itinerari', - 'oauth.scope.trips:write.description': 'Crea e aggiorna viaggi, giorni, note e gestisci membri', - 'oauth.scope.trips:delete.label': 'Elimina viaggi', - 'oauth.scope.trips:delete.description': 'Elimina definitivamente interi viaggi — questa azione è irreversibile', - 'oauth.scope.trips:share.label': 'Gestisci link di condivisione', - 'oauth.scope.trips:share.description': 'Crea, aggiorna e revoca link di condivisione pubblici per i viaggi', - 'oauth.scope.places:read.label': 'Visualizza luoghi e dati mappa', - 'oauth.scope.places:read.description': 'Leggi luoghi, assegnazioni giornaliere, tag e categorie', - 'oauth.scope.places:write.label': 'Gestisci luoghi', - 'oauth.scope.places:write.description': 'Crea, aggiorna ed elimina luoghi, assegnazioni e tag', - 'oauth.scope.atlas:read.label': 'Visualizza Atlas', - 'oauth.scope.atlas:read.description': 'Leggi paesi visitati, regioni e lista dei desideri', - 'oauth.scope.atlas:write.label': 'Gestisci Atlas', - 'oauth.scope.atlas:write.description': 'Segna paesi e regioni come visitati, gestisci la lista dei desideri', - 'oauth.scope.packing:read.label': 'Visualizza liste bagagli', - 'oauth.scope.packing:read.description': 'Leggi articoli, borse e assegnatari di categoria', - 'oauth.scope.packing:write.label': 'Gestisci liste bagagli', - 'oauth.scope.packing:write.description': 'Aggiungi, aggiorna, elimina, spunta e riordina articoli e borse', - 'oauth.scope.todos:read.label': 'Visualizza liste attività', - 'oauth.scope.todos:read.description': 'Leggi attività del viaggio e assegnatari di categoria', - 'oauth.scope.todos:write.label': 'Gestisci liste attività', - 'oauth.scope.todos:write.description': 'Crea, aggiorna, spunta, elimina e riordina attività', - 'oauth.scope.budget:read.label': 'Visualizza budget', - 'oauth.scope.budget:read.description': 'Leggi voci di budget e ripartizione delle spese', - 'oauth.scope.budget:write.label': 'Gestisci budget', - 'oauth.scope.budget:write.description': 'Crea, aggiorna ed elimina voci di budget', - 'oauth.scope.reservations:read.label': 'Visualizza prenotazioni', - 'oauth.scope.reservations:read.description': 'Leggi prenotazioni e dettagli alloggio', - 'oauth.scope.reservations:write.label': 'Gestisci prenotazioni', - 'oauth.scope.reservations:write.description': 'Crea, aggiorna, elimina e riordina prenotazioni', - 'oauth.scope.collab:read.label': 'Visualizza collaborazione', - 'oauth.scope.collab:read.description': 'Leggi note collaborative, sondaggi e messaggi', - 'oauth.scope.collab:write.label': 'Gestisci collaborazione', - 'oauth.scope.collab:write.description': 'Crea, aggiorna ed elimina note collaborative, sondaggi e messaggi', - 'oauth.scope.notifications:read.label': 'Visualizza notifiche', - 'oauth.scope.notifications:read.description': 'Leggi notifiche in-app e conteggi non letti', - 'oauth.scope.notifications:write.label': 'Gestisci notifiche', - 'oauth.scope.notifications:write.description': 'Segna notifiche come lette e rispondi', - 'oauth.scope.vacay:read.label': 'Visualizza piani ferie', - 'oauth.scope.vacay:read.description': 'Leggi dati di pianificazione ferie, voci e statistiche', - 'oauth.scope.vacay:write.label': 'Gestisci piani ferie', - 'oauth.scope.vacay:write.description': 'Crea e gestisci voci ferie, festività e piani del team', - 'oauth.scope.geo:read.label': 'Mappe e geocodifica', - 'oauth.scope.geo:read.description': 'Cerca luoghi, risolvi URL mappa e geocodifica inversa coordinate', - 'oauth.scope.weather:read.label': 'Previsioni meteo', - 'oauth.scope.weather:read.description': 'Ottieni previsioni meteo per luoghi e date del viaggio', - 'oauth.scope.journey:read.label': 'Visualizza diari di viaggio', - 'oauth.scope.journey:read.description': 'Leggi diari di viaggio, voci e lista dei collaboratori', - 'oauth.scope.journey:write.label': 'Gestisci diari di viaggio', - 'oauth.scope.journey:write.description': 'Crea, aggiorna ed elimina diari di viaggio e le loro voci', - 'oauth.scope.journey:share.label': 'Gestisci link diari di viaggio', - 'oauth.scope.journey:share.description': 'Crea, aggiorna e revoca link di condivisione pubblici per i diari di viaggio', - - // System notices - 'system_notice.welcome_v1.title': 'Benvenuto su TREK', - 'system_notice.welcome_v1.body': 'Il tuo pianificatore di viaggi tutto in uno. Crea itinerari, condividi viaggi con gli amici e rimani organizzato — online e offline.', - 'system_notice.welcome_v1.cta_label': 'Pianifica un viaggio', - 'system_notice.welcome_v1.hero_alt': 'Destinazione di viaggio panoramica con l\'interfaccia TREK', - 'system_notice.welcome_v1.highlight_plan': 'Itinerari giorno per giorno', - 'system_notice.welcome_v1.highlight_share': 'Collabora con i tuoi compagni di viaggio', - 'system_notice.welcome_v1.highlight_offline': 'Funziona offline su mobile', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Avviso precedente', - 'system_notice.pager.next': 'Avviso successivo', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': "Vai all'avviso {n}", - 'system_notice.pager.position': 'Avviso {current} di {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Le foto sono spostate nella 3.0', - 'system_notice.v3_photos.body': '**Foto** nel Pianificatore di Viaggio sono state rimosse. Le tue foto sono al sicuro — TREK non ha mai modificato la tua libreria Immich o Synology.\n\nLe foto ora si trovano nel componente aggiuntivo **Journey**. Journey è opzionale — se non è ancora disponibile, chiedi al tuo admin di abilitarlo in Admin → Addon.', - 'system_notice.v3_journey.title': 'Scopri Journey — diario di viaggio', - 'system_notice.v3_journey.body': 'Documenta i tuoi viaggi come storie ricche con cronologie, gallerie fotografiche e mappe interattive.', - 'system_notice.v3_journey.cta_label': 'Apri Journey', - 'system_notice.v3_journey.highlight_timeline': 'Cronologia e galleria giornaliera', - 'system_notice.v3_journey.highlight_photos': 'Importa da Immich o Synology', - 'system_notice.v3_journey.highlight_share': 'Condividi pubblicamente — senza accesso', - 'system_notice.v3_journey.highlight_export': 'Esporta come libro fotografico PDF', - 'system_notice.v3_features.title': 'Altri punti salienti nel 3.0', - 'system_notice.v3_features.body': 'Altre novità da conoscere in questa versione.', - 'system_notice.v3_features.highlight_dashboard': 'Dashboard ridisegnata mobile-first', - 'system_notice.v3_features.highlight_offline': 'Modalità offline completa come PWA', - 'system_notice.v3_features.highlight_search': 'Completamento automatico luoghi in tempo reale', - 'system_notice.v3_features.highlight_import': 'Importa luoghi da file KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: aggiornamento OAuth 2.1', - 'system_notice.v3_mcp.body': "L'integrazione MCP è stata completamente rinnovata. OAuth 2.1 è ora il metodo di autenticazione consigliato. I token statici (trek_\u2026) sono deprecati e verranno rimossi in una versione futura.", - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 consigliato (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 scope di autorizzazione granulari', - 'system_notice.v3_mcp.highlight_deprecated': 'Token statici trek_ deprecati', - 'system_notice.v3_mcp.highlight_tools': 'Strumenti e prompt estesi', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Una nota personale da parte mia', - 'system_notice.v3_thankyou.body': 'Prima di andare avanti — voglio prendermi un momento.\n\nTREK è nato come un progetto secondario che ho costruito per i miei viaggi. Non avrei mai immaginato che sarebbe cresciuto fino a diventare qualcosa di cui 4.000 di voi si fidano per pianificare le proprie avventure. Ogni stella, ogni issue, ogni richiesta di funzionalità — le leggo tutte, e sono loro a tenermi in piedi nelle notti tarde tra un lavoro a tempo pieno e l\'università.\n\nVoglio che sappiate: TREK sarà sempre open source, sempre self-hosted, sempre vostro. Nessun tracciamento, nessun abbonamento, nessuna fregatura. Solo uno strumento creato da qualcuno che ama viaggiare tanto quanto voi.\n\nUn ringraziamento speciale a [jubnl](https://github.com/jubnl) — sei diventato un collaboratore incredibile. Molto di ciò che rende la 3.0 fantastica porta la tua impronta. Grazie per aver creduto in questo progetto quando era ancora acerbo.\n\nE a ognuno di voi che ha segnalato un bug, tradotto una stringa, condiviso TREK con un amico o semplicemente lo ha usato per pianificare un viaggio — **grazie**. Voi siete il motivo per cui tutto questo esiste.\n\nA molte altre avventure insieme.\n\n— Maurice\n\n---\n\n[Unisciti alla community su Discord](https://discord.gg/7Q6M6jDwzf)\n\nSe TREK rende i tuoi viaggi migliori, un [piccolo caffè](https://ko-fi.com/mauriceboe) aiuta sempre a tenere le luci accese.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Azione richiesta: conflitto di account utente', - 'system_notice.v3014_whitespace_collision.body': "L'aggiornamento 3.0.14 ha rilevato uno o più conflitti di nome utente o e-mail causati da spazi iniziali o finali nei valori memorizzati. Gli account interessati sono stati rinominati automaticamente. Controlla i log del server per le righe che iniziano con **[migration] WHITESPACE COLLISION** per identificare quali account richiedono revisione.", - 'transport.addTransport': 'Aggiungi trasporto', - 'transport.modalTitle.create': 'Aggiungi trasporto', - 'transport.modalTitle.edit': 'Modifica trasporto', - 'transport.title': 'Trasporti', - 'transport.addManual': 'Trasporto manuale', -} - -export default it - diff --git a/client/src/i18n/translations/ja.ts b/client/src/i18n/translations/ja.ts deleted file mode 100644 index a431db16..00000000 --- a/client/src/i18n/translations/ja.ts +++ /dev/null @@ -1,2431 +0,0 @@ -const ja: Record = { - // Common - 'common.save': '保存', - 'common.showMore': 'もっと見る', - 'common.showLess': '閉じる', - 'common.cancel': 'キャンセル', - 'common.clear': 'クリア', - 'common.delete': '削除', - 'common.edit': '編集', - 'common.add': '追加', - 'common.loading': '読み込み中…', - 'common.import': 'インポート', - 'common.select': '選択', - 'common.selectAll': 'すべて選択', - 'common.deselectAll': 'すべて解除', - 'common.error': 'エラー', - 'common.unknownError': '不明なエラー', - 'common.tooManyAttempts': '試行回数が多すぎます。時間をおいて再度お試しください。', - 'common.back': '戻る', - 'common.all': 'すべて', - 'common.close': '閉じる', - 'common.open': '開く', - 'common.upload': 'アップロード', - 'common.search': '検索', - 'common.confirm': '確認', - 'common.ok': 'OK', - 'common.yes': 'はい', - 'common.no': 'いいえ', - 'common.or': 'または', - 'common.none': 'なし', - 'common.date': '日付', - 'common.rename': '名前を変更', - 'common.discardChanges': '変更をキャンセル', - 'common.discard': 'キャンセル', - 'common.name': '名前', - 'common.email': 'メールアドレス', - 'common.password': 'パスワード', - 'common.saving': '保存中…', - 'common.justNow': 'たった今', - 'common.hoursAgo': '{count}時間前', - 'common.daysAgo': '{count}日前', - 'common.saved': '保存しました', - 'trips.memberRemoved': '{username} を削除しました', - 'trips.memberRemoveError': '削除に失敗しました', - 'trips.memberAdded': '{username} を追加しました', - 'trips.memberAddError': '追加に失敗しました', - 'trips.reminder': 'リマインダー', - 'trips.reminderNone': 'なし', - 'trips.reminderDay': '日', - 'trips.reminderDays': '日', - 'trips.reminderCustom': 'カスタム', - 'trips.reminderDaysBefore': '出発前', - 'trips.reminderDisabledHint': '旅行のリマインダーは無効です。管理 > 設定 > 通知から有効にしてください。', - 'common.update': '更新', - 'common.change': '変更', - 'common.uploading': 'アップロード中…', - 'common.backToPlanning': 'プランに戻る', - 'common.reset': 'リセット', - 'common.expand': '展開', - 'common.collapse': '折りたたむ', - - // Navbar - 'nav.trip': '旅行', - 'nav.share': '共有', - 'nav.settings': '設定', - 'nav.admin': '管理', - 'nav.logout': 'ログアウト', - 'nav.lightMode': 'ライトモード', - 'nav.darkMode': 'ダークモード', - 'nav.autoMode': '自動', - 'nav.administrator': '管理者', - -// Dashboard - 'dashboard.title': 'マイ旅行', - 'dashboard.subtitle.loading': '旅行を読み込み中...', - 'dashboard.subtitle.trips': '{count}件の旅行({archived}件アーカイブ)', - 'dashboard.subtitle.empty': '最初の旅行を始めましょう', - 'dashboard.subtitle.activeOne': '進行中の旅行 {count}件', - 'dashboard.subtitle.activeMany': '進行中の旅行 {count}件', - 'dashboard.subtitle.archivedSuffix': ' · アーカイブ {count}件', - 'dashboard.newTrip': '新しい旅行', - 'dashboard.gridView': 'グリッド表示', - 'dashboard.listView': 'リスト表示', - 'dashboard.currency': '通貨', - 'dashboard.timezone': 'タイムゾーン', - 'dashboard.localTime': '現地', - 'dashboard.timezoneCustomTitle': 'カスタムタイムゾーン', - 'dashboard.timezoneCustomLabelPlaceholder': 'ラベル(任意)', - 'dashboard.timezoneCustomTzPlaceholder': '例:America/New_York', - 'dashboard.timezoneCustomAdd': '追加', - 'dashboard.timezoneCustomErrorEmpty': 'タイムゾーンIDを入力してください', - 'dashboard.timezoneCustomErrorInvalid': '無効なタイムゾーンです(例:Europe/Berlin)', - 'dashboard.timezoneCustomErrorDuplicate': 'すでに追加されています', - 'dashboard.emptyTitle': '旅行はまだありません', - 'dashboard.emptyText': '最初の旅行を作成して計画を始めましょう!', - 'dashboard.emptyButton': '最初の旅行を作成', - 'dashboard.nextTrip': '次の旅行', - 'dashboard.shared': '共有済み', - 'dashboard.sharedBy': '{name}さんが共有', - 'dashboard.days': '日数', - 'dashboard.places': '場所', - 'dashboard.members': '同行者', - 'dashboard.archive': 'アーカイブ', - 'dashboard.copyTrip': 'コピー', - 'dashboard.copySuffix': 'コピー', - 'dashboard.restore': '復元', - 'dashboard.archived': 'アーカイブ済み', - 'dashboard.status.ongoing': '進行中', - 'dashboard.status.today': '今日', - 'dashboard.status.tomorrow': '明日', - 'dashboard.status.past': '過去', - 'dashboard.status.daysLeft': '残り{count}日', - 'dashboard.toast.loadError': '旅行の読み込みに失敗しました', - 'dashboard.toast.created': '旅行を作成しました!', - 'dashboard.toast.createError': '旅行の作成に失敗しました', - 'dashboard.toast.updated': '旅行を更新しました!', - 'dashboard.toast.updateError': '旅行の更新に失敗しました', - 'dashboard.toast.deleted': '旅行を削除しました', - 'dashboard.toast.deleteError': '旅行の削除に失敗しました', - 'dashboard.toast.archived': '旅行をアーカイブしました', - 'dashboard.toast.archiveError': '旅行のアーカイブに失敗しました', - 'dashboard.toast.restored': '旅行を復元しました', - 'dashboard.toast.restoreError': '旅行の復元に失敗しました', - 'dashboard.toast.copied': '旅行をコピーしました!', - 'dashboard.toast.copyError': '旅行のコピーに失敗しました', - 'dashboard.confirm.delete': '旅行「{title}」を削除しますか?すべての場所と計画は完全に削除されます。', - 'dashboard.confirm.copy.title': 'この旅行をコピーしますか?', - 'dashboard.confirm.copy.willCopy': 'コピーされる内容', - 'dashboard.confirm.copy.will1': '日数、訪問先 & 日ごとの割り当て', - 'dashboard.confirm.copy.will2': '宿泊施設 & 予約', - 'dashboard.confirm.copy.will3': '予算項目 & カテゴリの順序', - 'dashboard.confirm.copy.will4': '持ち物リスト(未チェックのみ)', - 'dashboard.confirm.copy.will5': 'TODO(未割り当て & 未チェック)', - 'dashboard.confirm.copy.will6': '日ごとのメモ', - 'dashboard.confirm.copy.wontCopy': 'コピーされない内容', - 'dashboard.confirm.copy.wont1': '共同編集者 & メンバー割り当て', - 'dashboard.confirm.copy.wont2': '共同ノート、投票 & メッセージ', - 'dashboard.confirm.copy.wont3': 'ファイル & 写真', - 'dashboard.confirm.copy.wont4': '共有トークン', - 'dashboard.confirm.copy.confirm': '旅行をコピー', - 'dashboard.editTrip': '旅行を編集', - 'dashboard.createTrip': '新しい旅行を作成', - 'dashboard.tripTitle': 'タイトル', - 'dashboard.tripTitlePlaceholder': '例:日本の夏', - 'dashboard.tripDescription': '説明', - 'dashboard.tripDescriptionPlaceholder': 'この旅行について', - 'dashboard.startDate': '開始日', - 'dashboard.endDate': '終了日', - 'dashboard.dayCount': '日数', - 'dashboard.dayCountHint': '日付が未設定の場合に作成する日数です。', - 'dashboard.noDateHint': '日付未設定 — 既定で7日分が作成されます。後で変更できます。', - 'dashboard.coverImage': 'カバー画像', - 'dashboard.addCoverImage': 'カバー画像を追加(またはドラッグ&ドロップ)', - 'dashboard.addMembers': '同行者', - 'dashboard.addMember': 'メンバーを追加', - 'dashboard.coverSaved': 'カバー画像を保存しました', - 'dashboard.coverUploadError': 'アップロードに失敗しました', - 'dashboard.coverRemoveError': '削除に失敗しました', - 'dashboard.titleRequired': 'タイトルは必須です', - 'dashboard.endDateError': '終了日は開始日より後にしてください', - - // Settings - 'settings.title': '設定', - 'settings.subtitle': '個人設定を管理', - 'settings.tabs.display': '表示', - 'settings.tabs.map': '地図', - 'settings.tabs.notifications': '通知', - 'settings.tabs.integrations': '連携', - 'settings.tabs.account': 'アカウント', - 'settings.tabs.offline': 'オフライン', - 'settings.tabs.about': '情報', - 'settings.map': '地図', - 'settings.mapTemplate': '地図テンプレート', - 'settings.mapTemplatePlaceholder.select': 'テンプレートを選択…', - 'settings.mapDefaultHint': '空欄の場合は OpenStreetMap(既定)を使用', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': '地図タイルのURLテンプレート', - 'settings.mapProvider': '地図プロバイダー', - 'settings.mapProviderHint': '旅程プランナーと日記地図に影響します。Atlas は常に Leaflet を使用します。', - 'settings.mapLeafletSubtitle': 'クラシックな2D、任意のラスタータイル', - 'settings.mapMapboxSubtitle': 'ベクタータイル、3D建物・地形', - 'settings.mapExperimental': '実験的', - 'settings.mapMapboxToken': 'Mapbox アクセストークン', - 'settings.mapMapboxTokenHint': 'mapbox.com の公開トークン(pk.*)', - 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', - 'settings.mapStyle': '地図スタイル', - 'settings.mapStylePlaceholder': 'Mapboxスタイルを選択', - 'settings.mapStyleHint': 'プリセットまたは mapbox://styles/USER/ID のURL', - 'settings.map3dBuildings': '3D建物・地形', - 'settings.map3dHint': 'ピッチ+実際の3D押し出し表示。衛星含む全スタイルで動作。', - 'settings.mapHighQuality': '高品質モード', - 'settings.mapHighQualityHint': 'アンチエイリアス+地球投影で、より鮮明でリアルに表示。', - 'settings.mapHighQualityWarning': '低性能デバイスではパフォーマンスに影響する場合があります。', - 'settings.mapTipLabel': 'ヒント:', - 'settings.mapTip': '右クリック+ドラッグで回転/傾き。中クリックで場所を追加(右クリックは回転用)。', - 'settings.latitude': '緯度', - 'settings.longitude': '経度', - 'settings.saveMap': '地図を保存', - 'settings.apiKeys': 'APIキー', - 'settings.mapsKey': 'Google Maps APIキー', - 'settings.mapsKeyHint': '場所検索用。Places API(新)が必要。console.cloud.google.com で取得', - 'settings.weatherKey': 'OpenWeatherMap APIキー', - 'settings.weatherKeyHint': '天気情報用。openweathermap.org/api で無料取得', - 'settings.keyPlaceholder': 'キーを入力…', - 'settings.configured': '設定済み', - 'settings.saveKeys': 'キーを保存', - 'settings.display': '表示', - 'settings.colorMode': 'カラーモード', - 'settings.light': 'ライト', - 'settings.dark': 'ダーク', - 'settings.auto': '自動', - 'settings.language': '言語', - 'settings.temperature': '温度単位', - 'settings.timeFormat': '時刻形式', - 'settings.bookingLabels': '予約ルートのラベル', - 'settings.bookingLabelsHint': '地図に駅・空港名を表示。オフ時はアイコンのみ。', - 'settings.blurBookingCodes': '予約コードをぼかす', - 'settings.notifications': '通知', - 'settings.notifyTripInvite': '旅行の招待', - 'settings.notifyBookingChange': '予約の変更', - 'settings.notifyTripReminder': '旅行リマインダー', - 'settings.notifyTodoDue': 'ToDoの期限', - 'settings.notifyVacayInvite': 'Vacay fusion の招待', - 'settings.notifyPhotosShared': '共有写真(Immich)', - 'settings.notifyCollabMessage': 'チャットメッセージ(Collab)', - 'settings.notifyPackingTagged': '持ち物リスト:割り当て', - 'settings.notifyWebhook': 'Webhook通知', - 'settings.notifyVersionAvailable': '新しいバージョン', - 'settings.notificationPreferences.email': 'メール', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.inapp': 'アプリ内', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': '通知チャネルが未設定です。管理者に設定を依頼してください。', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Discord、Slack、または独自のWebhook URLを入力してください。', - 'settings.webhookUrl.saved': 'Webhook URLを保存しました', - 'settings.webhookUrl.test': 'テスト', - 'settings.webhookUrl.testSuccess': 'テストWebhookを送信しました', - 'settings.webhookUrl.testFailed': 'テストWebhookに失敗しました', - 'settings.ntfyUrl.topicLabel': 'Ntfy トピック', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy サーバーURL(任意)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'ntfyトピックを入力してください。サーバー未入力時は管理者設定の既定値を使用します。', - 'settings.ntfyUrl.tokenLabel': 'アクセストークン(任意)', - 'settings.ntfyUrl.tokenHint': 'パスワード保護トピックに必要です。', - 'settings.ntfyUrl.saved': 'Ntfy設定を保存しました', - 'settings.ntfyUrl.test': 'テスト', - 'settings.ntfyUrl.testSuccess': 'テスト通知を送信しました', - 'settings.ntfyUrl.testFailed': 'テスト通知に失敗しました', - 'settings.ntfyUrl.tokenCleared': 'アクセストークンを削除しました', - 'admin.notifications.title': '通知', - 'admin.notifications.hint': '通知チャネルを1つ選択してください。同時に有効にできるのは1つだけです。', - 'admin.notifications.none': '無効', - 'admin.notifications.email': 'メール(SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'ユーザーが独自のntfyトピックを設定できるようにします。下で既定サーバーを設定してください。', - 'admin.notifications.save': '通知設定を保存', - 'admin.notifications.saved': '通知設定を保存しました', - 'admin.notifications.testWebhook': 'Webhookテスト送信', - 'admin.notifications.testWebhookSuccess': 'テストWebhookを送信しました', - 'admin.notifications.testWebhookFailed': 'テストWebhookに失敗しました', - 'admin.notifications.testNtfy': 'ntfyテスト送信', - 'admin.notifications.testNtfySuccess': 'テストntfyを送信しました', - 'admin.notifications.testNtfyFailed': 'テストntfyに失敗しました', - 'admin.notifications.emailPanel.title': 'メール(SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'アプリ内', - 'admin.notifications.inappPanel.hint': 'アプリ内通知は常に有効で、全体では無効にできません。', - 'admin.notifications.adminWebhookPanel.title': '管理者Webhook', - 'admin.notifications.adminWebhookPanel.hint': '管理者通知専用のWebhookです(例:バージョン通知)。常に送信されます。', - 'admin.notifications.adminWebhookPanel.saved': '管理者Webhook URLを保存しました', - 'admin.notifications.adminWebhookPanel.testSuccess': 'テストWebhookを送信しました', - 'admin.notifications.adminWebhookPanel.testFailed': 'テストWebhookに失敗しました', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'URLが設定されていると常に送信されます', - 'admin.notifications.adminNtfyPanel.title': '管理者Ntfy', - 'admin.notifications.adminNtfyPanel.hint': '管理者通知専用のntfyトピックです。', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy サーバーURL', - 'admin.notifications.adminNtfyPanel.serverHint': 'ユーザー通知の既定サーバーとしても使用されます。', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': '管理者トピック', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'アクセストークン(任意)', - 'admin.notifications.adminNtfyPanel.tokenCleared': '管理者アクセストークンを削除しました', - 'admin.notifications.adminNtfyPanel.saved': '管理者ntfy設定を保存しました', - 'admin.notifications.adminNtfyPanel.test': 'ntfyテスト送信', - 'admin.notifications.adminNtfyPanel.testSuccess': 'テストntfyを送信しました', - 'admin.notifications.adminNtfyPanel.testFailed': 'テストntfyに失敗しました', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'トピック設定時は常に送信されます', - 'admin.notifications.adminNotificationsHint': '管理者専用通知の配信先を設定します。', - 'admin.notifications.tripReminders.title': '旅行リマインダー', - 'admin.notifications.tripReminders.hint': '旅行開始前に通知を送信します(旅行側の設定が必要)。', - 'admin.notifications.tripReminders.enabled': '旅行リマインダー有効', - 'admin.notifications.tripReminders.disabled': '旅行リマインダー無効', - 'admin.smtp.title': 'メール・通知', - 'admin.smtp.hint': 'メール通知送信用のSMTP設定。', - 'admin.smtp.testButton': 'テストメール送信', - 'admin.webhook.hint': 'ユーザーが独自のWebhook URLを設定できるようにします。', - 'admin.smtp.testSuccess': 'テストメールを送信しました', - 'admin.smtp.testFailed': 'テストメールに失敗しました', - 'settings.notificationsDisabled': '通知が未設定です。管理者に有効化を依頼してください。', - 'settings.notificationsActive': '有効なチャネル', - 'settings.notificationsManagedByAdmin': '通知イベントは管理者が設定します。', - 'dayplan.icsTooltip': 'カレンダーを書き出し(ICS)', - 'share.linkTitle': '公開リンク', - 'share.linkHint': 'ログイン不要で閲覧できるリンクを作成します(閲覧のみ)。', - 'share.createLink': 'リンク作成', - 'share.deleteLink': 'リンク削除', - 'share.createError': 'リンクを作成できませんでした', - 'common.copy': 'コピー', - 'common.copied': 'コピーしました', - 'share.permMap': '地図・プラン', - 'share.permBookings': '予約', - 'share.permPacking': '持ち物', - 'shared.expired': 'リンクが無効', - 'shared.expiredHint': 'この共有リンクは無効です。', - 'shared.readOnly': '読み取り専用', - 'shared.tabPlan': 'プラン', - 'shared.tabBookings': '予約', - 'shared.tabPacking': '持ち物', - 'shared.tabBudget': '予算', - 'shared.tabChat': 'チャット', - 'shared.days': '日', - 'shared.places': '場所', - 'shared.other': 'その他', - 'shared.totalBudget': '合計予算', - 'shared.messages': 'メッセージ', - 'shared.sharedVia': '共有元', - 'shared.confirmed': '確定', - 'shared.pending': '保留', - 'share.permBudget': '予算', - 'share.permCollab': 'チャット', - 'settings.on': 'オン', - 'settings.off': 'オフ', - 'settings.mcp.title': 'MCP設定', - 'settings.mcp.endpoint': 'MCPエンドポイント', - 'settings.mcp.clientConfig': 'クライアント設定', - 'settings.mcp.clientConfigHint': ' を下のAPIトークンに置き換えてください。', - 'settings.mcp.clientConfigHintOAuth': ' をOAuth 2.1の認証情報に置き換えてください。', - 'settings.mcp.copy': 'コピー', - 'settings.mcp.copied': 'コピーしました!', - 'settings.mcp.apiTokens': 'APIトークン', - 'settings.mcp.createToken': '新しいトークン', - 'settings.mcp.noTokens': 'トークンがありません。作成してください。', - 'settings.mcp.tokenCreatedAt': '作成日', - 'settings.mcp.tokenUsedAt': '最終使用', - 'settings.mcp.deleteTokenTitle': 'トークン削除', - 'settings.mcp.deleteTokenMessage': 'このトークンは即時無効になります。', - 'settings.mcp.modal.createTitle': 'APIトークン作成', - 'settings.mcp.modal.tokenName': 'トークン名', - 'settings.mcp.modal.tokenNamePlaceholder': '例:Claude Desktop', - 'settings.mcp.modal.creating': '作成中…', - 'settings.mcp.modal.create': '作成', - 'settings.mcp.modal.createdTitle': 'トークン作成完了', - 'settings.mcp.modal.createdWarning': '表示は一度きりです。今すぐ保存してください。', - 'settings.mcp.modal.done': '完了', - 'settings.mcp.toast.created': 'トークンを作成しました', - 'settings.mcp.toast.createError': 'トークン作成に失敗しました', - 'settings.mcp.toast.deleted': 'トークンを削除しました', - 'settings.mcp.toast.deleteError': 'トークン削除に失敗しました', - 'settings.mcp.apiTokensDeprecated': 'APIトークンは非推奨です。OAuth 2.1 クライアントを使用してください。', - 'settings.oauth.clients': 'OAuth 2.1 クライアント', - 'settings.oauth.clientsHint': '第三者アプリが接続できるよう登録します。', - 'settings.oauth.createClient': '新規クライアント', - 'settings.oauth.noClients': '登録されたクライアントはありません。', - 'settings.oauth.clientId': 'クライアントID', - 'settings.oauth.clientSecret': 'クライアントシークレット', - 'settings.oauth.deleteClient': 'クライアント削除', - 'settings.oauth.deleteClientMessage': 'このクライアントは完全に削除されます。', - 'settings.oauth.rotateSecret': 'シークレット更新', - 'settings.oauth.rotateSecretMessage': '新しいシークレットを生成します。', - 'settings.oauth.rotateSecretConfirm': '更新', - 'settings.oauth.rotateSecretConfirming': '更新中…', - 'settings.oauth.rotateSecretDoneTitle': '新しいシークレット', - 'settings.oauth.rotateSecretDoneWarning': '表示は一度きりです。今すぐ保存してください。', - 'settings.oauth.activeSessions': '有効なセッション', - 'settings.oauth.sessionScopes': 'スコープ', - 'settings.oauth.sessionExpires': '有効期限', - 'settings.oauth.revoke': '取り消し', - 'settings.oauth.revokeSession': 'セッション取り消し', - 'settings.oauth.revokeSessionMessage': 'このセッションのアクセスを即時無効にします。', - 'settings.oauth.modal.createTitle': 'OAuthクライアント登録', - 'settings.oauth.modal.presets': '簡単設定', - 'settings.oauth.modal.clientName': 'アプリ名', - 'settings.oauth.modal.clientNamePlaceholder': '例:Claude Web', - 'settings.oauth.modal.redirectUris': 'リダイレクトURI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback', - 'settings.oauth.modal.redirectUrisHint': '1行につき1つ。HTTPS必須。', - 'settings.oauth.modal.scopes': '許可スコープ', - 'settings.oauth.modal.scopesHint': 'list_trips と get_trip_summary は常に利用可能です。', - 'settings.oauth.modal.selectAll': 'すべて選択', - 'settings.oauth.modal.deselectAll': 'すべて解除', - 'settings.oauth.modal.creating': '登録中…', - 'settings.oauth.modal.create': '登録', - 'settings.oauth.modal.createdTitle': '登録完了', - 'settings.oauth.modal.createdWarning': 'シークレットは一度しか表示されません。', - 'settings.oauth.toast.createError': '登録に失敗しました', - 'settings.oauth.toast.deleted': 'クライアントを削除しました', - 'settings.oauth.toast.deleteError': '削除に失敗しました', - 'settings.oauth.toast.revoked': 'セッションを取り消しました', - 'settings.oauth.toast.revokeError': '取り消しに失敗しました', - 'settings.oauth.toast.rotateError': '更新に失敗しました', - 'settings.account': 'アカウント', - 'settings.about': '情報', - 'settings.about.reportBug': '不具合報告', - 'settings.about.reportBugHint': '問題を見つけたらお知らせください', - 'settings.about.featureRequest': '機能リクエスト', - 'settings.about.featureRequestHint': '新機能を提案', - 'settings.about.wikiHint': 'ドキュメント・ガイド', - 'settings.about.supporters.badge': '月額サポーター', - 'settings.about.supporters.title': 'TREKの旅仲間', - 'settings.about.supporters.subtitle': '皆さんの支援がTREKの未来を支えています。', - 'settings.about.supporters.since': '{date}からサポート', - 'settings.about.supporters.tierEmpty': '最初の一人に', - 'settings.about.supporter.tier.noReturnTicket': '片道切符', - 'settings.about.supporter.tier.lostLuggageVip': 'ロストラゲージVIP', - 'settings.about.supporter.tier.businessClassDreamer': 'ビジネスクラスの夢', - 'settings.about.supporter.tier.budgetTraveller': '節約トラベラー', - 'settings.about.supporter.tier.hostelBunkmate': 'ホステル仲間', - 'settings.about.description': 'TREKはセルフホスト型の旅行プランナーです。', - 'settings.about.madeWith': 'Made with', - 'settings.about.madeBy': 'by Maurice とオープンソースコミュニティ。', - 'settings.username': 'ユーザー名', - 'settings.email': 'メール', - 'settings.role': '役割', - 'settings.roleAdmin': '管理者', - 'settings.oidcLinked': '連携先', - 'settings.changePassword': 'パスワード変更', - 'settings.currentPassword': '現在のパスワード', - 'settings.currentPasswordRequired': '現在のパスワードが必要です', - 'settings.newPassword': '新しいパスワード', - 'settings.confirmPassword': '新しいパスワード(確認)', - 'settings.updatePassword': 'パスワード更新', - 'settings.passwordRequired': '現在と新しいパスワードを入力してください', - 'settings.passwordTooShort': '8文字以上必要です', - 'settings.passwordMismatch': 'パスワードが一致しません', - 'settings.passwordWeak': '大文字・小文字・数字・記号を含めてください', - 'settings.passwordChanged': 'パスワードを変更しました', - 'settings.mustChangePassword': '続行するにはパスワード変更が必要です。', - 'settings.deleteAccount': 'アカウント削除', - 'settings.deleteAccountTitle': 'アカウントを削除しますか?', - 'settings.deleteAccountWarning': 'すべてのデータが完全に削除されます。', - 'settings.deleteAccountConfirm': '完全に削除', - 'settings.deleteBlockedTitle': '削除できません', - 'settings.deleteBlockedMessage': '唯一の管理者です。別のユーザーを管理者にしてください。', - 'settings.roleUser': 'ユーザー', - 'settings.saveProfile': 'プロフィールを保存', - 'settings.toast.mapSaved': '地図設定を保存しました', - 'settings.toast.keysSaved': 'APIキーを保存しました', - 'settings.toast.displaySaved': '表示設定を保存しました', - 'settings.toast.profileSaved': 'プロフィールを保存しました', - 'settings.uploadAvatar': 'プロフィール画像をアップロード', - 'settings.removeAvatar': 'プロフィール画像を削除', - 'settings.avatarUploaded': 'プロフィール画像を更新しました', - 'settings.avatarRemoved': 'プロフィール画像を削除しました', - 'settings.avatarError': 'アップロードに失敗しました', - 'settings.mfa.title': '二要素認証(2FA)', - 'settings.mfa.description': 'サインイン時に追加の認証を行います。', - 'settings.mfa.requiredByPolicy': '管理者により2FAが必須です。', - 'settings.mfa.backupTitle': 'バックアップコード', - 'settings.mfa.backupDescription': '認証アプリが使えない場合に使用します。', - 'settings.mfa.backupWarning': '今すぐ保存してください。各コードは1回限りです。', - 'settings.mfa.backupCopy': 'コードをコピー', - 'settings.mfa.backupDownload': 'TXTでダウンロード', - 'settings.mfa.backupPrint': '印刷 / PDF', - 'settings.mfa.backupCopied': 'バックアップコードをコピーしました', - 'settings.mfa.enabled': '2FAは有効です。', - 'settings.mfa.disabled': '2FAは無効です。', - 'settings.mfa.setup': '認証アプリを設定', - 'settings.mfa.scanQr': 'QRコードをスキャンするか、手動で入力してください。', - 'settings.mfa.secretLabel': 'シークレットキー(手動入力)', - 'settings.mfa.codePlaceholder': '6桁コード', - 'settings.mfa.enable': '2FAを有効化', - 'settings.mfa.cancelSetup': 'キャンセル', - 'settings.mfa.disableTitle': '2FAを無効化', - 'settings.mfa.disableHint': 'パスワードと現在のコードを入力してください。', - 'settings.mfa.disable': '2FAを無効化', - 'settings.mfa.toastEnabled': '2FAを有効にしました', - 'settings.mfa.toastDisabled': '2FAを無効にしました', - 'settings.mfa.demoBlocked': 'デモモードでは利用できません', - - // Login - 'login.error': 'ログインに失敗しました。認証情報を確認してください。', - 'login.tagline': 'あなたの旅行。\nあなたの計画。', - 'login.description': 'インタラクティブなマップ、予算、リアルタイム同期で、みんなで旅行を計画。', - 'login.features.maps': 'インタラクティブマップ', - 'login.features.mapsDesc': 'Google Places、経路、クラスタリング', - 'login.features.realtime': 'リアルタイム同期', - 'login.features.realtimeDesc': 'WebSocketで共同計画', - 'login.features.budget': '予算管理', - 'login.features.budgetDesc': 'カテゴリ、グラフ、人数別費用', - 'login.features.collab': 'コラボレーション', - 'login.features.collabDesc': '複数ユーザーで旅行を共有', - 'login.features.packing': '持ち物リスト', - 'login.features.packingDesc': 'カテゴリ、進捗、提案', - 'login.features.bookings': '予約', - 'login.features.bookingsDesc': '航空券、ホテル、レストランなど', - 'login.features.files': 'ドキュメント', - 'login.features.filesDesc': '書類のアップロードと管理', - 'login.features.routes': 'スマート経路', - 'login.features.routesDesc': '自動最適化&Google Maps書き出し', - 'login.selfHosted': 'セルフホスト · オープンソース · データはあなたのもの', - 'login.title': 'サインイン', - 'login.subtitle': 'おかえりなさい', - 'login.signingIn': 'サインイン中…', - 'login.signIn': 'サインイン', - 'login.createAdmin': '管理者アカウントを作成', - 'login.createAdminHint': 'TREKの最初の管理者アカウントを設定します。', - 'login.setNewPassword': '新しいパスワードを設定', - 'login.setNewPasswordHint': '続行する前にパスワードを変更してください。', - 'login.createAccount': 'アカウントを作成', - 'login.createAccountHint': '新しいアカウントを登録。', - 'login.creating': '作成中…', - 'login.noAccount': 'アカウントをお持ちでないですか?', - 'login.hasAccount': 'すでにアカウントをお持ちですか?', - 'login.register': '登録', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': 'ユーザー名', - 'login.oidc.registrationDisabled': '登録は無効です。管理者に連絡してください。', - 'login.oidc.noEmail': 'プロバイダーからメールが取得できませんでした。', - 'login.oidc.tokenFailed': '認証に失敗しました。', - 'login.oidc.invalidState': 'セッションが無効です。もう一度お試しください。', - 'login.demoFailed': 'デモログインに失敗しました', - 'login.oidcSignIn': '{name}でサインイン', - 'login.oidcOnly': 'パスワード認証は無効です。SSOプロバイダーでサインインしてください。', - 'login.oidcLoggedOut': 'ログアウトしました。SSOプロバイダーで再度サインインしてください。', - 'login.demoHint': 'デモを試す — 登録不要', - 'login.mfaTitle': '二要素認証', - 'login.mfaSubtitle': '認証アプリの6桁コードを入力してください。', - 'login.mfaCodeLabel': '確認コード', - 'login.mfaCodeRequired': '認証アプリのコードを入力してください。', - 'login.mfaHint': 'Google Authenticator、Authy などのTOTPアプリを開いてください。', - 'login.mfaBack': '← サインインに戻る', - 'login.mfaVerify': '確認', - 'login.invalidInviteLink': '無効または期限切れの招待リンクです', - 'login.oidcFailed': 'OIDCログインに失敗しました', - 'login.usernameRequired': 'ユーザー名を入力してください', - 'login.passwordMinLength': 'パスワードは8文字以上である必要があります', - 'login.forgotPassword': 'パスワードを忘れた場合', - 'login.forgotPasswordTitle': 'パスワードをリセット', - 'login.forgotPasswordBody': '登録時のメールアドレスを入力してください。アカウントが存在する場合、リセット用リンクを送信します。', - 'login.forgotPasswordSubmit': 'リセットリンクを送信', - 'login.forgotPasswordSentTitle': 'メールを確認してください', - 'login.forgotPasswordSentBody': '該当するアカウントがある場合、リセットリンクを送信しました。リンクの有効期限は60分です。', - 'login.forgotPasswordSmtpHintOff': '注意:管理者がSMTPを設定していないため、リセットリンクはメールではなくサーバーコンソールに出力されます。', - 'login.backToLogin': 'ログインに戻る', - 'login.newPassword': '新しいパスワード', - 'login.confirmPassword': '新しいパスワード(確認)', - 'login.passwordsDontMatch': 'パスワードが一致しません', - 'login.mfaCode': '2FAコード', - 'login.resetPasswordTitle': '新しいパスワードを設定', - 'login.resetPasswordBody': '以前使用していない強力なパスワードを設定してください(8文字以上)。', - 'login.resetPasswordMfaBody': '2FAコードまたはバックアップコードを入力してリセットを完了してください。', - 'login.resetPasswordSubmit': 'パスワードをリセット', - 'login.resetPasswordVerify': '確認してリセット', - 'login.resetPasswordSuccessTitle': 'パスワードを更新しました', - 'login.resetPasswordSuccessBody': '新しいパスワードでログインできます。', - 'login.resetPasswordInvalidLink': '無効なリセットリンク', - 'login.resetPasswordInvalidLinkBody': 'リンクが無効または破損しています。新しいリンクをリクエストしてください。', - 'login.resetPasswordFailed': 'リセットに失敗しました。リンクの有効期限が切れている可能性があります。', - - // Register - 'register.passwordMismatch': 'パスワードが一致しません', - 'register.passwordTooShort': 'パスワードは8文字以上必要です', - 'register.failed': '登録に失敗しました', - 'register.getStarted': '始める', - 'register.subtitle': 'アカウントを作成して、理想の旅行計画を始めましょう。', - 'register.feature1': '無制限の旅行プラン', - 'register.feature2': 'インタラクティブなマップ表示', - 'register.feature3': '場所とカテゴリを管理', - 'register.feature4': '予約を管理', - 'register.feature5': '持ち物リストを作成', - 'register.feature6': '写真・ファイルを保存', - 'register.createAccount': 'アカウントを作成', - 'register.startPlanning': '旅行計画を始める', - 'register.minChars': '最小6文字', - 'register.confirmPassword': 'パスワード確認', - 'register.repeatPassword': 'パスワードを再入力', - 'register.registering': '登録中...', - 'register.register': '登録', - 'register.hasAccount': 'すでにアカウントをお持ちですか?', - 'register.signIn': 'サインイン', - - // Admin - 'admin.title': '管理', - 'admin.subtitle': 'ユーザー管理とシステム設定', - 'admin.tabs.users': 'ユーザー', - 'admin.tabs.categories': 'カテゴリ', - 'admin.tabs.backup': 'バックアップ', - 'admin.tabs.notifications': '通知', - 'admin.tabs.audit': '監査', - 'admin.stats.users': 'ユーザー', - 'admin.stats.trips': '旅行', - 'admin.stats.places': '場所', - 'admin.stats.photos': '写真', - 'admin.stats.files': 'ファイル', - 'admin.table.user': 'ユーザー', - 'admin.table.email': 'メール', - 'admin.table.role': '権限', - 'admin.table.created': '作成日', - 'admin.table.lastLogin': '最終ログイン', - 'admin.table.actions': '操作', - 'admin.you': '(あなた)', - 'admin.editUser': 'ユーザーを編集', - 'admin.newPassword': '新しいパスワード', - 'admin.newPasswordHint': '空欄の場合は現在のパスワードを維持', - 'admin.deleteUser': 'ユーザー「{name}」を削除しますか?すべての旅行が完全に削除されます。', - 'admin.deleteUserTitle': 'ユーザー削除', - 'admin.newPasswordPlaceholder': '新しいパスワードを入力…', - 'admin.toast.loadError': '管理データの読み込みに失敗しました', - 'admin.toast.userUpdated': 'ユーザーを更新しました', - 'admin.toast.updateError': '更新に失敗しました', - 'admin.toast.userDeleted': 'ユーザーを削除しました', - 'admin.toast.deleteError': '削除に失敗しました', - 'admin.toast.cannotDeleteSelf': '自分のアカウントは削除できません', - 'admin.toast.userCreated': 'ユーザーを作成しました', - 'admin.toast.createError': 'ユーザー作成に失敗しました', - 'admin.toast.fieldsRequired': 'ユーザー名、メール、パスワードは必須です', - 'admin.createUser': 'ユーザーを作成', - 'admin.invite.title': '招待リンク', - 'admin.invite.subtitle': '使い切り登録リンクを作成', - 'admin.invite.create': 'リンク作成', - 'admin.invite.createAndCopy': '作成してコピー', - 'admin.invite.empty': '招待リンクがまだありません', - 'admin.invite.maxUses': '最大使用回数', - 'admin.invite.expiry': '有効期限', - 'admin.invite.uses': '使用済み', - 'admin.invite.expiresAt': '期限', - 'admin.invite.createdBy': '作成者', - 'admin.invite.active': '有効', - 'admin.invite.expired': '期限切れ', - 'admin.invite.usedUp': '使用済み', - 'admin.invite.copied': '招待リンクをコピーしました', - 'admin.invite.copyLink': 'リンクをコピー', - 'admin.invite.deleted': '招待リンクを削除しました', - 'admin.invite.createError': '招待リンクの作成に失敗しました', - 'admin.invite.deleteError': '招待リンクの削除に失敗しました', - 'admin.tabs.settings': '設定', - 'admin.allowRegistration': '登録を許可', - 'admin.allowRegistrationHint': '新規ユーザーの自己登録を許可します', - 'admin.authMethods': '認証方法', - 'admin.passwordLogin': 'パスワードログイン', - 'admin.passwordLoginHint': 'メールとパスワードでのログインを許可', - 'admin.passwordRegistration': 'パスワード登録', - 'admin.passwordRegistrationHint': 'メールとパスワードでの新規登録を許可', - 'admin.oidcLogin': 'SSOログイン', - 'admin.oidcLoginHint': 'SSOでのログインを許可', - 'admin.oidcRegistration': 'SSO自動登録', - 'admin.oidcRegistrationHint': '新しいSSOユーザーを自動作成', - 'admin.envOverrideHint': 'パスワードログイン設定は OIDC_ONLY 環境変数で制御されています。', - 'admin.lockoutWarning': '少なくとも1つのログイン方法を有効にしてください', - 'admin.requireMfa': '二要素認証(2FA)を必須にする', - 'admin.requireMfaHint': '2FA未設定のユーザーは、利用前に設定が必要です。', - 'admin.apiKeys': 'APIキー', - 'admin.apiKeysHint': '任意。写真や天気などの拡張データを有効化します。', - 'admin.mapsKey': 'Google Maps APIキー', - 'admin.mapsKeyHint': '場所検索に必要。console.cloud.google.com で取得', - 'admin.mapsKeyHintLong': 'APIキーなしではOpenStreetMapを使用します。Google APIキーがあれば写真、評価、営業時間も表示できます。', - 'admin.recommended': '推奨', - 'admin.weatherKey': 'OpenWeatherMap APIキー', - 'admin.weatherKeyHint': '天気データ用。openweathermap.org で無料', - 'admin.validateKey': 'テスト', - 'admin.keyValid': '接続済み', - 'admin.keyInvalid': '無効', - 'admin.keySaved': 'APIキーを保存しました', - 'admin.oidcTitle': 'シングルサインオン(OIDC)', - 'admin.oidcSubtitle': 'Google、Apple、Authentik、Keycloak などでログインを許可します。', - 'admin.oidcDisplayName': '表示名', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'OpenID ConnectのIssuer URL(例:https://accounts.google.com)', - 'admin.oidcSaved': 'OIDC設定を保存しました', - 'admin.oidcOnlyMode': 'パスワード認証を無効化', - 'admin.oidcOnlyModeHint': '有効にするとSSOのみ使用可能になり、パスワードログインと登録は無効になります。', - - // File Types - 'admin.fileTypes': '許可するファイル形式', - 'admin.fileTypesHint': 'ユーザーがアップロードできるファイル形式を設定します。', - 'admin.fileTypesFormat': '拡張子をカンマ区切り(例:jpg,png,pdf,doc)。すべて許可する場合は *。', - 'admin.fileTypesSaved': 'ファイル形式の設定を保存しました', - - 'admin.placesPhotos.title': '場所の写真', - 'admin.placesPhotos.subtitle': 'Google Places APIから写真を取得します。APIクォータ節約のため無効にできます。Wikimediaの写真には影響しません。', - 'admin.placesAutocomplete.title': '場所のオートコンプリート', - 'admin.placesAutocomplete.subtitle': '検索候補にGoogle Places APIを使用します。APIクォータ節約のため無効にできます。', - 'admin.placesDetails.title': '場所の詳細', - 'admin.placesDetails.subtitle': 'Google Places APIから営業時間、評価、ウェブサイトなどの詳細情報を取得します。APIクォータ節約のため無効にできます。', -// Packing Templates & Bag Tracking - 'admin.bagTracking.title': 'バッグ管理', - 'admin.bagTracking.subtitle': '持ち物の重量とバッグ割り当てを有効化', - 'admin.collab.chat.title': 'チャット', - 'admin.collab.chat.subtitle': '旅行の共同作業用リアルタイムメッセージ', - 'admin.collab.notes.title': 'ノート', - 'admin.collab.notes.subtitle': '共有ノートとドキュメント', - 'admin.collab.polls.title': '投票', - 'admin.collab.polls.subtitle': 'グループ投票・アンケート', - 'admin.collab.whatsnext.title': '次にすること', - 'admin.collab.whatsnext.subtitle': 'アクティビティ提案と次のステップ', - 'admin.tabs.config': 'カスタマイズ', - 'admin.tabs.defaults': 'ユーザーのデフォルト', - 'admin.defaultSettings.title': '既定のユーザー設定', - 'admin.defaultSettings.description': 'インスタンス全体の既定値を設定します。設定を変更していないユーザーにはこれらの値が適用されます。各ユーザーの設定変更は常に優先されます。', - 'admin.defaultSettings.saved': '既定値を保存しました', - 'admin.defaultSettings.reset': '組み込みの既定値に戻す', - 'admin.defaultSettings.resetToBuiltIn': 'リセット', - 'admin.tabs.templates': '持ち物テンプレート', - 'admin.packingTemplates.title': '持ち物テンプレート', - 'admin.packingTemplates.subtitle': '旅行で使い回せる持ち物リストを作成', - 'admin.packingTemplates.create': '新規テンプレート', - 'admin.packingTemplates.namePlaceholder': 'テンプレート名(例:ビーチ旅行)', - 'admin.packingTemplates.empty': 'テンプレートはまだありません', - 'admin.packingTemplates.items': 'アイテム', - 'admin.packingTemplates.categories': 'カテゴリ', - 'admin.packingTemplates.itemName': 'アイテム名', - 'admin.packingTemplates.itemCategory': 'カテゴリ', - 'admin.packingTemplates.categoryName': 'カテゴリ名(例:衣類)', - 'admin.packingTemplates.addCategory': 'カテゴリを追加', - 'admin.packingTemplates.created': 'テンプレートを作成しました', - 'admin.packingTemplates.deleted': 'テンプレートを削除しました', - 'admin.packingTemplates.loadError': 'テンプレートの読み込みに失敗しました', - 'admin.packingTemplates.createError': 'テンプレートの作成に失敗しました', - 'admin.packingTemplates.deleteError': 'テンプレートの削除に失敗しました', - 'admin.packingTemplates.saveError': '保存に失敗しました', - -// Addons - 'admin.tabs.addons': 'アドオン', - 'admin.addons.title': 'アドオン', - 'admin.addons.subtitle': '機能を有効/無効にしてTREKをカスタマイズします。', - 'admin.addons.catalog.packing.name': 'リスト', - 'admin.addons.catalog.packing.description': '旅行用の持ち物リストとToDo', - 'admin.addons.catalog.budget.name': '予算', - 'admin.addons.catalog.budget.description': '支出の管理と予算計画', - 'admin.addons.catalog.documents.name': 'ドキュメント', - 'admin.addons.catalog.documents.description': '旅行書類の保存・管理', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'カレンダー表示の個人休暇プランナー', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': '訪問国と旅行統計の世界地図', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'リアルタイムのメモ、投票、チャット', - 'admin.addons.catalog.memories.name': '写真(Immich)', - 'admin.addons.catalog.memories.description': 'Immichで旅行写真を共有', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'AI連携のためのModel Context Protocol', - 'admin.addons.subtitleBefore': '機能を有効/無効にして ', - 'admin.addons.subtitleAfter': ' をカスタマイズします。', - 'admin.addons.enabled': '有効', - 'admin.addons.disabled': '無効', - 'admin.addons.type.trip': '旅行', - 'admin.addons.type.global': '全体', - 'admin.addons.type.integration': '連携', - 'admin.addons.tripHint': '各旅行内のタブとして利用可能', - 'admin.addons.globalHint': 'メインナビの独立セクションとして利用可能', - 'admin.addons.integrationHint': '専用ページのないバックエンド/API連携', - 'admin.addons.toast.updated': 'アドオンを更新しました', - 'admin.addons.toast.error': 'アドオンの更新に失敗しました', - 'admin.addons.noAddons': '利用可能なアドオンはありません', -// Weather info - 'admin.weather.title': '天気データ', - 'admin.weather.badge': '2026年3月24日以降', - 'admin.weather.description': 'TREKは天気データにOpen‑Meteoを使用しています。無料でオープンソース、APIキーは不要です。', - 'admin.weather.forecast': '16日間予報', - 'admin.weather.forecastDesc': '以前は5日(OpenWeatherMap)', - 'admin.weather.climate': '過去の気候データ', - 'admin.weather.climateDesc': '16日以降は過去85年の平均値', - 'admin.weather.requests': '1日10,000リクエスト', - 'admin.weather.requestsDesc': '無料、APIキー不要', - 'admin.weather.locationHint': '各日の座標付き最初の場所を基準にします。場所が未割り当ての場合は、場所一覧の任意の場所を参照します。', - - // GitHub - 'admin.tabs.mcpTokens': 'MCPトークン', - 'admin.mcpTokens.title': 'MCPトークン', - 'admin.mcpTokens.subtitle': 'すべてのユーザーのAPIトークンを管理', - 'admin.mcpTokens.sectionTitle': 'API トークン', - 'admin.mcpTokens.owner': '所有者', - 'admin.mcpTokens.tokenName': 'トークン名', - 'admin.mcpTokens.created': '作成日', - 'admin.mcpTokens.lastUsed': '最終使用', - 'admin.mcpTokens.never': '未使用', - 'admin.mcpTokens.empty': 'MCPトークンはまだ作成されていません', - 'admin.mcpTokens.deleteTitle': 'トークンを削除', - 'admin.mcpTokens.deleteMessage': 'このトークンは即座に失効します。ユーザーはこのトークン経由のMCPアクセスを失います。', - 'admin.mcpTokens.deleteSuccess': 'トークンを削除しました', - 'admin.mcpTokens.deleteError': 'トークンの削除に失敗しました', - 'admin.mcpTokens.loadError': 'トークンの読み込みに失敗しました', - 'admin.oauthSessions.sectionTitle': 'OAuthセッション', - 'admin.oauthSessions.clientName': 'クライアント', - 'admin.oauthSessions.owner': '所有者', - 'admin.oauthSessions.scopes': 'スコープ', - 'admin.oauthSessions.created': '作成日時', - 'admin.oauthSessions.empty': '有効なOAuthセッションはありません', - 'admin.oauthSessions.revokeTitle': 'セッションを無効化', - 'admin.oauthSessions.revokeMessage': 'このOAuthセッションは即時無効化され、クライアントはMCPへのアクセスを失います。', - 'admin.oauthSessions.revokeSuccess': 'セッションを無効化しました', - 'admin.oauthSessions.revokeError': 'セッションの無効化に失敗しました', - 'admin.oauthSessions.loadError': 'OAuthセッションの読み込みに失敗しました', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'セキュリティおよび管理イベント(バックアップ、ユーザー、MFA、設定)。', - 'admin.audit.empty': '監査ログはまだありません。', - 'admin.audit.refresh': '更新', - 'admin.audit.loadMore': 'さらに読み込む', - 'admin.audit.showing': '{count}件表示 · 全{total}件', - 'admin.audit.col.time': '時刻', - 'admin.audit.col.user': 'ユーザー', - 'admin.audit.col.action': '操作', - 'admin.audit.col.resource': '対象', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': '詳細', - 'admin.github.title': 'リリース履歴', - 'admin.github.subtitle': '{repo} の最新アップデート', - 'admin.github.latest': '最新', - 'admin.github.prerelease': 'プレリリース', - 'admin.github.showDetails': '詳細を表示', - 'admin.github.hideDetails': '詳細を非表示', - 'admin.github.loadMore': 'さらに読み込む', - 'admin.github.loading': '読み込み中...', - 'admin.github.error': 'リリースの読み込みに失敗しました', - 'admin.github.by': '作成者', - 'admin.github.support': 'TREKの開発を支援', - - 'admin.update.available': '更新があります', - 'admin.update.text': 'TREK {version} が利用可能です。現在は {current} を使用しています。', - 'admin.update.button': 'GitHubで見る', - 'admin.update.install': '更新をインストール', - 'admin.update.confirmTitle': '更新をインストールしますか?', - 'admin.update.confirmText': 'TREKを {current} から {version} に更新します。更新後、サーバーは自動的に再起動します。', - 'admin.update.dataInfo': 'すべてのデータ(旅行、ユーザー、APIキー、アップロード、Vacay、Atlas、予算)は保持されます。', - 'admin.update.warning': '再起動中、アプリは短時間利用できません。', - 'admin.update.confirm': '今すぐ更新', - 'admin.update.installing': '更新中…', - 'admin.update.success': '更新が完了しました!サーバーを再起動しています…', - 'admin.update.failed': '更新に失敗しました', - 'admin.update.backupHint': '更新前にバックアップを作成することをおすすめします。', - 'admin.update.backupLink': 'バックアップへ', - 'admin.update.howTo': '更新方法', - 'admin.update.dockerText': 'TREKはDockerで実行されています。{version} に更新するには、サーバーで次のコマンドを実行してください:', - 'admin.update.reloadHint': '数秒後にページを再読み込みしてください。', - -// Vacay addon - 'vacay.subtitle': '休暇日数の計画と管理', - 'vacay.settings': '設定', - 'vacay.year': '年', - 'vacay.addYear': '翌年を追加', - 'vacay.addPrevYear': '前年を追加', - 'vacay.removeYear': '年を削除', - 'vacay.removeYearConfirm': '{year}年を削除しますか?', - 'vacay.removeYearHint': 'この年の休暇データと会社休日はすべて完全に削除されます。', - 'vacay.remove': '削除', - 'vacay.persons': '人物', - 'vacay.noPersons': '人物が追加されていません', - 'vacay.addPerson': '人物を追加', - 'vacay.editPerson': '人物を編集', - 'vacay.removePerson': '人物を削除', - 'vacay.removePersonConfirm': '{name}を削除しますか?', - 'vacay.removePersonHint': 'この人物のすべての休暇データが完全に削除されます。', - 'vacay.personName': '名前', - 'vacay.personNamePlaceholder': '名前を入力', - 'vacay.color': '色', - 'vacay.add': '追加', - 'vacay.legend': '凡例', - 'vacay.publicHoliday': '祝日', - 'vacay.companyHoliday': '会社休日', - 'vacay.weekend': '週末', - 'vacay.modeVacation': '休暇', - 'vacay.modeCompany': '会社休日', - 'vacay.entitlement': '付与日数', - 'vacay.entitlementDays': '日', - 'vacay.used': '使用済み', - 'vacay.remaining': '残り', - 'vacay.carriedOver': '{year}年から繰越', - 'vacay.blockWeekends': '週末を除外', - 'vacay.blockWeekendsHint': '週末に休暇を登録できないようにします', - 'vacay.weekendDays': '週末', - 'vacay.mon': '月', - 'vacay.tue': '火', - 'vacay.wed': '水', - 'vacay.thu': '木', - 'vacay.fri': '金', - 'vacay.sat': '土', - 'vacay.sun': '日', - 'vacay.publicHolidays': '祝日', - 'vacay.publicHolidaysHint': 'カレンダーに祝日を表示', - 'vacay.selectCountry': '国を選択', - 'vacay.selectRegion': '地域を選択(任意)', - 'vacay.addCalendar': 'カレンダーを追加', - 'vacay.calendarLabel': 'ラベル(任意)', - 'vacay.calendarColor': '色', - 'vacay.noCalendars': '祝日カレンダーはまだありません', - 'vacay.companyHolidays': '会社休日', - 'vacay.companyHolidaysHint': '会社全体の休日を設定できます', - 'vacay.companyHolidaysNoDeduct': '会社休日は休暇日数に含まれません。', - 'vacay.weekStart': '週の開始', - 'vacay.weekStartHint': 'カレンダーの週を月曜始まりにするか日曜始まりにするかを選択します', - 'vacay.carryOver': '繰越', - 'vacay.carryOverHint': '残りの休暇日数を翌年に自動で繰り越します', - 'vacay.sharing': '共有', - 'vacay.sharingHint': '他のTREKユーザーと休暇計画を共有', - 'vacay.owner': '所有者', - 'vacay.shareEmailPlaceholder': 'TREKユーザーのメール', - 'vacay.shareSuccess': '計画を共有しました', - 'vacay.shareError': '共有できませんでした', - 'vacay.dissolve': '統合を解除', - 'vacay.dissolveHint': 'カレンダーを分離します。データは保持されます。', - 'vacay.dissolveAction': '解除', - 'vacay.dissolved': 'カレンダーを分離しました', - 'vacay.fusedWith': '統合相手', - 'vacay.you': 'あなた', - 'vacay.noData': 'データなし', - 'vacay.changeColor': '色を変更', - 'vacay.inviteUser': 'ユーザーを招待', - 'vacay.inviteHint': '別のTREKユーザーを招待して、休暇カレンダーを共有します。', - 'vacay.selectUser': 'ユーザーを選択', - 'vacay.sendInvite': '招待を送信', - 'vacay.inviteSent': '招待を送信しました', - 'vacay.inviteError': '招待を送信できませんでした', - 'vacay.pending': '保留中', - 'vacay.noUsersAvailable': '利用可能なユーザーがいません', - 'vacay.accept': '承認', - 'vacay.decline': '拒否', - 'vacay.acceptFusion': '承認して統合', - 'vacay.inviteTitle': '統合の招待', - 'vacay.inviteWantsToFuse': 'が休暇カレンダーの共有を希望しています。', - 'vacay.fuseInfo1': '双方が1つの共有カレンダーですべての休暇を確認できます。', - 'vacay.fuseInfo2': '双方が互いの予定を作成・編集できます。', - 'vacay.fuseInfo3': '双方が予定の削除や付与日数の変更を行えます。', - 'vacay.fuseInfo4': '祝日や会社休日などの設定は共有されます。', - 'vacay.fuseInfo5': '統合はいつでも解除できます。データは保持されます。', - 'nav.myTrips': 'マイ旅行', - - // Atlas addon - 'atlas.subtitle': '世界に広がるあなたの旅の足跡', - 'atlas.countries': '国', - 'atlas.trips': '旅行', - 'atlas.places': '場所', - 'atlas.unmark': '削除', - 'atlas.confirmMark': 'この国を訪問済みにしますか?', - 'atlas.confirmUnmark': 'この国を訪問済みリストから削除しますか?', - 'atlas.confirmUnmarkRegion': 'この地域を訪問済みリストから削除しますか?', - 'atlas.markVisited': '訪問済みにする', - 'atlas.markVisitedHint': 'この国を訪問済みリストに追加', - 'atlas.markRegionVisitedHint': 'この地域を訪問済みリストに追加', - 'atlas.addToBucket': '行きたいリストに追加', - 'atlas.addPoi': '場所を追加', - 'atlas.searchCountry': '国を検索...', - 'atlas.bucketNamePlaceholder': '名前(国・都市・場所など)', - 'atlas.month': '月', - 'atlas.year': '年', - 'atlas.addToBucketHint': '行きたい場所として保存', - 'atlas.bucketWhen': '訪問予定はいつですか?', - 'atlas.statsTab': '統計', - 'atlas.bucketTab': '行きたいリスト', - 'atlas.addBucket': '行きたいリストに追加', - 'atlas.bucketNotesPlaceholder': 'メモ(任意)', - 'atlas.bucketEmpty': '行きたいリストは空です', - 'atlas.bucketEmptyHint': '行ってみたい場所を追加しましょう', - 'atlas.days': '日', - 'atlas.visitedCountries': '訪問国', - 'atlas.cities': '都市', - 'atlas.noData': '旅行データがありません', - 'atlas.noDataHint': '旅行を作成して場所を追加すると、マップに表示されます', - 'atlas.lastTrip': '前回の旅行', - 'atlas.nextTrip': '次の旅行', - 'atlas.daysLeft': '残り日数', - 'atlas.streak': '連続', - 'atlas.years': '年', - 'atlas.yearInRow': '年連続', - 'atlas.yearsInRow': '年連続', - 'atlas.tripIn': '旅行', - 'atlas.tripsIn': '旅行', - 'atlas.since': '開始', - 'atlas.europe': 'ヨーロッパ', - 'atlas.asia': 'アジア', - 'atlas.northAmerica': '北アメリカ', - 'atlas.southAmerica': '南アメリカ', - 'atlas.africa': 'アフリカ', - 'atlas.oceania': 'オセアニア', - 'atlas.other': 'その他', - 'atlas.firstVisit': '最初の旅行', - 'atlas.lastVisitLabel': '最後の旅行', - 'atlas.tripSingular': '旅行', - 'atlas.tripPlural': '旅行', - 'atlas.placeVisited': '訪問した場所', - 'atlas.placesVisited': '訪問した場所', - -// Trip Planner - 'trip.tabs.plan': '計画', - 'trip.tabs.transports': '移動', - 'trip.tabs.reservations': '予約', - 'trip.tabs.reservationsShort': '予約', - 'trip.tabs.packing': '持ち物リスト', - 'trip.tabs.packingShort': '持ち物', - 'trip.tabs.lists': 'リスト', - 'trip.tabs.listsShort': 'リスト', - 'trip.tabs.budget': '予算', - 'trip.tabs.files': 'ファイル', - 'trip.loading': '旅行を読み込み中...', - 'trip.loadingPhotos': '場所の写真を読み込み中...', - 'trip.mobilePlan': '計画', - 'trip.mobilePlaces': '場所', - 'trip.toast.placeUpdated': '場所を更新しました', - 'trip.toast.placeAdded': '場所を追加しました', - 'trip.toast.placeDeleted': '場所を削除しました', - 'trip.toast.selectDay': 'まず日を選択してください', - 'trip.toast.assignedToDay': '場所を日に割り当てました', - 'trip.toast.reorderError': '並び替えに失敗しました', - 'trip.toast.reservationUpdated': '予約を更新しました', - 'trip.toast.reservationAdded': '予約を追加しました', - 'trip.toast.deleted': '削除しました', - 'trip.confirm.deletePlace': 'この場所を削除してもよろしいですか?', - 'trip.confirm.deletePlaces': '{count}件の場所を削除してもよろしいですか?', - 'trip.toast.placesDeleted': '{count}件の場所を削除しました', - -// Day Plan Sidebar - 'dayplan.emptyDay': 'この日の予定はありません', - 'dayplan.cannotReorderTransport': '時刻が固定された予約は並び替えできません', - 'dayplan.confirmRemoveTimeTitle': '時刻を削除しますか?', - 'dayplan.confirmRemoveTimeBody': 'この場所には固定時刻({time})があります。移動すると時刻が削除され、自由に並び替えできます。', - 'dayplan.confirmRemoveTimeAction': '時刻を削除して移動', - 'dayplan.cannotDropOnTimed': '時刻指定の項目の間には配置できません', - 'dayplan.cannotBreakChronology': '時刻指定の項目や予約の時系列が崩れます', - 'dayplan.addNote': 'メモを追加', - 'dayplan.expandAll': 'すべての日を展開', - 'dayplan.collapseAll': 'すべての日を折りたたむ', - 'dayplan.editNote': 'メモを編集', - 'dayplan.noteAdd': 'メモを追加', - 'dayplan.noteEdit': 'メモを編集', - 'dayplan.noteTitle': 'メモ', - 'dayplan.noteSubtitle': '日別メモ', - 'dayplan.totalCost': '合計費用', - 'dayplan.days': '日', - 'dayplan.dayN': '{n}日目', - 'dayplan.calculating': '計算中...', - 'dayplan.route': 'ルート', - 'dayplan.optimize': '最適化', - 'dayplan.optimized': 'ルートを最適化しました', - 'dayplan.routeError': 'ルートの計算に失敗しました', - 'dayplan.toast.needTwoPlaces': 'ルート最適化には2つ以上の場所が必要です', - 'dayplan.toast.routeOptimized': 'ルートを最適化しました', - 'dayplan.toast.noGeoPlaces': '座標付きの場所がありません', - 'dayplan.confirmed': '確定', - 'dayplan.pendingRes': '保留', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': '日別計画をPDFで書き出し', - 'dayplan.pdfError': 'PDFの書き出しに失敗しました', - - // Places Sidebar - 'places.addPlace': '場所/アクティビティを追加', - 'places.importFile': 'ファイルをインポート', - 'places.sidebarDrop': 'ドロップしてインポート', - 'places.importFileHint': 'Google My Maps、Google Earth、GPSトラッカーなどの .gpx、.kml、.kmz ファイルをインポートできます。', - 'places.importFileDropHere': 'クリックしてファイルを選択、またはここにドラッグ&ドロップ', - 'places.importFileDropActive': 'ドロップして選択', - 'places.importFileUnsupported': '対応していないファイル形式です。.gpx、.kml、.kmz を使用してください。', - 'places.importFileTooLarge': 'ファイルが大きすぎます。最大 {maxMb} MB までです。', - 'places.importFileError': 'インポートに失敗しました', - 'places.importAllSkipped': 'すべての場所は既に旅行に含まれています。', - 'places.gpxImported': 'GPXから {count} 件の場所をインポートしました', - 'places.gpxImportTypes': '何をインポートしますか?', - 'places.gpxImportWaypoints': 'ウェイポイント', - 'places.gpxImportRoutes': 'ルート', - 'places.gpxImportTracks': 'トラック(経路付き)', - 'places.gpxImportNoneSelected': '少なくとも1つ選択してください。', - 'places.kmlImportTypes': '何をインポートしますか?', - 'places.kmlImportPoints': 'ポイント(プレースマーク)', - 'places.kmlImportPaths': 'パス(ライン)', - 'places.kmlImportNoneSelected': '少なくとも1つ選択してください。', - 'places.selectionCount': '{count} 件選択中', - 'places.deleteSelected': '選択を削除', - 'places.kmlKmzImported': 'KMZ/KMLから {count} 件の場所をインポートしました', - 'places.urlResolved': 'URLから場所をインポートしました', - 'places.importList': 'リストをインポート', - 'places.kmlKmzSummaryValues': 'プレースマーク: {total} • 追加: {created} • スキップ: {skipped}', - 'places.importGoogleList': 'Google リスト', - 'places.importNaverList': 'Naver リスト', - 'places.googleListHint': '共有されたGoogleマップのリストリンクを貼り付けてください。', - 'places.googleListImported': '「{list}」から {count} 件の場所をインポートしました', - 'places.googleListError': 'Googleマップのリストをインポートできませんでした', - 'places.naverListHint': '共有されたNaverマップのリストリンクを貼り付けてください。', - 'places.naverListImported': '「{list}」から {count} 件の場所をインポートしました', - 'places.naverListError': 'Naverマップのリストをインポートできませんでした', - 'places.viewDetails': '詳細を見る', - 'places.assignToDay': 'どの日に追加しますか?', - 'places.all': 'すべて', - 'places.unplanned': '未計画', - 'places.filterTracks': 'トラック', - 'places.search': '場所を検索…', - 'places.allCategories': 'すべてのカテゴリ', - 'places.categoriesSelected': 'カテゴリ', - 'places.clearFilter': 'フィルター解除', - 'places.count': '{count} 件の場所', - 'places.countSingular': '1 件の場所', - 'places.allPlanned': 'すべての場所が計画済みです', - 'places.noneFound': '場所が見つかりません', - 'places.editPlace': '場所を編集', - 'places.formName': '名前', - 'places.formNamePlaceholder': '例:エッフェル塔', - 'places.formDescription': '説明', - 'places.formDescriptionPlaceholder': '短い説明…', - 'places.formAddress': '住所', - 'places.formAddressPlaceholder': '通り、都市、国', - 'places.formLat': '緯度(例:48.8566)', - 'places.formLng': '経度(例:2.3522)', - 'places.formCategory': 'カテゴリ', - 'places.noCategory': 'カテゴリなし', - 'places.categoryNamePlaceholder': 'カテゴリ名', - 'places.formTime': '時間', - 'places.startTime': '開始', - 'places.endTime': '終了', - 'places.endTimeBeforeStart': '終了時間が開始時間より前です', - 'places.timeCollision': '時間が重複しています:', - 'places.formWebsite': 'ウェブサイト', - 'places.formNotes': 'メモ', - 'places.formNotesPlaceholder': '個人的なメモ…', - 'places.formReservation': '予約', - 'places.reservationNotesPlaceholder': '予約メモ、確認番号など…', - 'places.mapsSearchPlaceholder': '場所を検索…', - 'places.mapsSearchError': '場所の検索に失敗しました。', - 'places.loadingDetails': '詳細を読み込み中…', - 'places.osmHint': 'OpenStreetMapで検索しています(写真・営業時間・評価なし)。設定でGoogle APIキーを追加すると詳細が表示されます。', - 'places.osmActive': 'OpenStreetMapで検索中(写真・評価・営業時間なし)。設定でGoogle APIキーを追加してください。', - 'places.categoryCreateError': 'カテゴリの作成に失敗しました', - 'places.nameRequired': '名前を入力してください', - 'places.saveError': '保存に失敗しました', -// Place Inspector - 'inspector.opened': '営業中', - 'inspector.closed': '営業時間外', - 'inspector.openingHours': '営業時間', - 'inspector.showHours': '営業時間を表示', - 'inspector.files': 'ファイル', - 'inspector.remove': '削除', - 'inspector.filesCount': '{count}件のファイル', - 'inspector.removeFromDay': 'この日から削除', - 'inspector.addToDay': '日に追加', - 'inspector.confirmedRes': '確定済み予約', - 'inspector.pendingRes': '保留中の予約', - 'inspector.google': 'Googleマップで開く', - 'inspector.website': 'Webサイトを開く', - 'inspector.addRes': '予約', - 'inspector.editRes': '予約を編集', - 'inspector.participants': '参加者', - 'inspector.trackStats': '統計を記録', - -// Reservations - 'reservations.title': '予約', - 'reservations.empty': '予約はまだありません', - 'reservations.emptyHint': '航空券、ホテルなどの予約を追加しましょう', - 'reservations.add': '予約を追加', - 'reservations.addManual': '手動予約', - 'reservations.placeHint': 'ヒント:予約は場所から直接作成すると、日別計画に紐づけやすくなります。', - 'reservations.confirmed': '確定', - 'reservations.pending': '保留', - 'reservations.summary': '確定 {confirmed}件、保留 {pending}件', - 'reservations.fromPlan': '計画から', - 'reservations.showFiles': 'ファイルを表示', - 'reservations.editTitle': '予約を編集', - 'reservations.status': 'ステータス', - 'reservations.datetime': '日時', - 'reservations.startTime': '開始時刻', - 'reservations.endTime': '終了時刻', - 'reservations.date': '日付', - 'reservations.time': '時間', - 'reservations.timeAlt': '時間(代替、例:19:30)', - 'reservations.notes': 'メモ', - 'reservations.notesPlaceholder': '追加のメモ...', - 'reservations.meta.airline': '航空会社', - 'reservations.meta.flightNumber': '便名', - 'reservations.meta.from': '出発地', - 'reservations.meta.to': '到着地', - 'reservations.needsReview': '要確認', - 'reservations.needsReviewHint': '空港を自動で特定できませんでした。場所を確認してください。', - 'reservations.searchLocation': '駅・港・住所を検索…', - 'airport.searchPlaceholder': '空港コードまたは都市名(例:FRA)', - 'map.connections': '接続', - 'map.showConnections': '予約ルートを表示', - 'map.hideConnections': '予約ルートを非表示', - 'reservations.meta.trainNumber': '列車番号', - 'reservations.meta.platform': 'ホーム', - 'reservations.meta.seat': '座席', - 'reservations.meta.checkIn': 'チェックイン', - 'reservations.meta.checkOut': 'チェックアウト', - 'reservations.meta.linkAccommodation': '宿泊先', - 'reservations.meta.checkInUntil': 'チェックイン期限', - 'reservations.meta.pickAccommodation': '宿泊先にリンク', - 'reservations.meta.noAccommodation': 'なし', - 'reservations.meta.hotelPlace': '宿泊先', - 'reservations.meta.pickHotel': '宿泊先を選択', - 'reservations.meta.fromDay': '開始', - 'reservations.meta.toDay': '終了', - 'reservations.meta.selectDay': '日を選択', - 'reservations.type.flight': '航空便', - 'reservations.type.hotel': '宿泊', - 'reservations.type.restaurant': 'レストラン', - 'reservations.type.train': '列車', - 'reservations.type.car': 'レンタカー', - 'reservations.type.cruise': 'クルーズ', - 'reservations.type.event': 'イベント', - 'reservations.type.tour': 'ツアー', - 'reservations.type.other': 'その他', - 'reservations.confirm.delete': '予約「{name}」を削除しますか?', - 'reservations.confirm.deleteTitle': '予約を削除しますか?', - 'reservations.confirm.deleteBody': '「{name}」は完全に削除されます。', - 'reservations.toast.updated': '予約を更新しました', - 'reservations.toast.removed': '予約を削除しました', - 'reservations.toast.fileUploaded': 'ファイルをアップロードしました', - 'reservations.toast.uploadError': 'アップロードに失敗しました', - 'reservations.newTitle': '新しい予約', - 'reservations.bookingType': '予約タイプ', - 'reservations.titleLabel': 'タイトル', - 'reservations.titlePlaceholder': '例:Lufthansa LH123、Hotel Adlon', - 'reservations.locationAddress': '場所/住所', - 'reservations.locationPlaceholder': '住所、空港、ホテル...', - 'reservations.confirmationCode': '予約コード', - 'reservations.confirmationPlaceholder': '例:ABC12345', - 'reservations.day': '日', - 'reservations.noDay': '日なし', - 'reservations.place': '場所', - 'reservations.noPlace': '場所なし', - 'reservations.pendingSave': '保存されます…', - 'reservations.uploading': 'アップロード中...', - 'reservations.attachFile': 'ファイルを添付', - 'reservations.linkExisting': '既存ファイルをリンク', - 'reservations.toast.saveError': '保存に失敗しました', - 'reservations.toast.updateError': '更新に失敗しました', - 'reservations.toast.deleteError': '削除に失敗しました', - 'reservations.confirm.remove': '「{name}」の予約を削除しますか?', - 'reservations.linkAssignment': '日への割り当てにリンク', - 'reservations.pickAssignment': '計画から割り当てを選択...', - 'reservations.noAssignment': 'リンクなし(単独)', - 'reservations.price': '価格', - 'reservations.budgetCategory': '予算カテゴリ', - 'reservations.budgetCategoryPlaceholder': '例:交通、宿泊', - 'reservations.budgetCategoryAuto': '自動(予約タイプから)', - 'reservations.budgetHint': '保存時に予算エントリが自動で作成されます。', - 'reservations.departureDate': '出発', - 'reservations.arrivalDate': '到着', - 'reservations.departureTime': '出発時刻', - 'reservations.arrivalTime': '到着時刻', - 'reservations.pickupDate': '受取', - 'reservations.returnDate': '返却', - 'reservations.pickupTime': '受取時刻', - 'reservations.returnTime': '返却時刻', - 'reservations.endDate': '終了日', - 'reservations.meta.departureTimezone': '出発TZ', - 'reservations.meta.arrivalTimezone': '到着TZ', - 'reservations.span.departure': '出発', - 'reservations.span.arrival': '到着', - 'reservations.span.inTransit': '移動中', - 'reservations.span.pickup': '受取', - 'reservations.span.return': '返却', - 'reservations.span.active': '有効', - 'reservations.span.start': '開始', - 'reservations.span.end': '終了', - 'reservations.span.ongoing': '進行中', - 'reservations.validation.endBeforeStart': '終了日時は開始日時より後である必要があります', - 'reservations.addBooking': '予約を追加', - - // Budget - 'budget.title': '予算', - 'budget.exportCsv': 'CSVを書き出し', - 'budget.emptyTitle': '予算はまだありません', - 'budget.emptyText': 'カテゴリと項目を作成して旅行予算を計画しましょう', - 'budget.emptyPlaceholder': 'カテゴリ名を入力...', - 'budget.createCategory': 'カテゴリを作成', - 'budget.category': 'カテゴリ', - 'budget.categoryName': 'カテゴリ名', - 'budget.table.name': '名前', - 'budget.table.total': '合計', - 'budget.table.persons': '人数', - 'budget.table.days': '日数', - 'budget.table.perPerson': '1人あたり', - 'budget.table.perDay': '1日あたり', - 'budget.table.perPersonDay': '人/日', - 'budget.table.note': 'メモ', - 'budget.table.date': '日付', - 'budget.newEntry': '新しい項目', - 'budget.defaultEntry': '新しい項目', - 'budget.defaultCategory': '新しいカテゴリ', - 'budget.total': '合計', - 'budget.totalBudget': '総予算', - 'budget.byCategory': 'カテゴリ別', - 'budget.editTooltip': 'クリックして編集', - 'budget.linkedToReservation': '予約に連携中 — 名前はそちらで編集してください', - 'budget.confirm.deleteCategory': '{count}件の項目があるカテゴリ「{name}」を削除しますか?', - 'budget.deleteCategory': 'カテゴリを削除', - 'budget.perPerson': '1人あたり', - 'budget.paid': '支払済み', - 'budget.open': '未精算', - 'budget.noMembers': 'メンバー未割り当て', - 'budget.settlement': '精算', - 'budget.settlementInfo': '予算項目のメンバーアイコンをクリックして緑にすると、支払い済みを示します。精算では、誰が誰にいくら支払うべきかを表示します。', - 'budget.netBalances': '差引残高', - -// Files - 'files.title': 'ファイル', - 'files.pageTitle': 'ファイル・ドキュメント', - 'files.subtitle': '{trip} のファイル {count} 件', - 'files.download': 'ダウンロード', - 'files.openError': 'ファイルを開けませんでした', - 'files.downloadPdf': 'PDFをダウンロード', - 'files.count': '{count}件のファイル', - 'files.countSingular': '1件のファイル', - 'files.uploaded': '{count}件アップロード', - 'files.uploadError': 'アップロードに失敗しました', - 'files.dropzone': 'ここにファイルをドロップ', - 'files.dropzoneHint': 'またはクリックして参照', - 'files.allowedTypes': '画像、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大50MB', - 'files.uploading': 'アップロード中...', - 'files.filterAll': 'すべて', - 'files.filterPdf': 'PDF', - 'files.filterImages': '画像', - 'files.filterDocs': 'ドキュメント', - 'files.filterCollab': 'Collabメモ', - 'files.sourceCollab': 'Collabメモより', - 'files.empty': 'ファイルはまだありません', - 'files.emptyHint': 'ファイルをアップロードして旅行に添付しましょう', - 'files.openTab': '新しいタブで開く', - 'files.confirm.delete': 'このファイルを削除しますか?', - 'files.toast.deleted': 'ファイルを削除しました', - 'files.toast.deleteError': 'ファイルの削除に失敗しました', - 'files.sourcePlan': '日別計画', - 'files.sourceBooking': '予約', - 'files.sourceTransport': '移動', - 'files.attach': '添付', - 'files.pasteHint': 'クリップボードから画像を貼り付けることもできます(Ctrl+V)', - 'files.trash': 'ゴミ箱', - 'files.trashEmpty': 'ゴミ箱は空です', - 'files.emptyTrash': 'ゴミ箱を空にする', - 'files.restore': '復元', - 'files.star': 'スター', - 'files.unstar': 'スター解除', - 'files.assign': '割り当て', - 'files.assignTitle': 'ファイルを割り当て', - 'files.assignPlace': '場所', - 'files.assignBooking': '予約', - 'files.assignTransport': '移動', - 'files.unassigned': '未割り当て', - 'files.unlink': 'リンクを解除', - 'files.toast.trashed': 'ゴミ箱に移動しました', - 'files.toast.restored': 'ファイルを復元しました', - 'files.toast.trashEmptied': 'ゴミ箱を空にしました', - 'files.toast.assigned': 'ファイルを割り当てました', - 'files.toast.assignError': '割り当てに失敗しました', - 'files.toast.restoreError': '復元に失敗しました', - 'files.confirm.permanentDelete': 'このファイルを完全に削除しますか?元に戻せません。', - 'files.confirm.emptyTrash': 'ゴミ箱内のファイルをすべて完全に削除しますか?元に戻せません。', - 'files.noteLabel': 'メモ', - 'files.notePlaceholder': 'メモを追加...', - -// Packing - 'packing.title': '持ち物リスト', - 'packing.empty': '持ち物リストは空です', - 'packing.import': 'インポート', - 'packing.importTitle': '持ち物リストをインポート', - 'packing.importHint': '1行につき1項目。形式:カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)', - 'packing.importPlaceholder': '衛生用品, 歯ブラシ\n衣類, Tシャツ, 200\n書類, パスポート, , 機内持ち込み\n電子機器, 充電器, 50, スーツケース, checked', - 'packing.importCsv': 'CSV/TXTを読み込む', - 'packing.importAction': '{count}件をインポート', - 'packing.importSuccess': '{count}件インポートしました', - 'packing.importError': 'インポートに失敗しました', - 'packing.importEmpty': 'インポートする項目がありません', - 'packing.progress': '{packed}/{total} 梱包済み({percent}%)', - 'packing.clearChecked': 'チェック済み{count}件を削除', - 'packing.clearCheckedShort': '{count}件を削除', - 'packing.suggestions': 'おすすめ', - 'packing.suggestionsTitle': 'おすすめを追加', - 'packing.allSuggested': 'おすすめはすべて追加済み', - 'packing.allPacked': 'すべて梱包済み!', - 'packing.addPlaceholder': '新しい項目を追加...', - 'packing.categoryPlaceholder': 'カテゴリ...', - 'packing.filterAll': 'すべて', - 'packing.filterOpen': '未完了', - 'packing.filterDone': '完了', - 'packing.emptyTitle': '持ち物リストは空です', - 'packing.emptyHint': '項目を追加するか、おすすめを使いましょう', - 'packing.emptyFiltered': 'このフィルターに一致する項目はありません', - 'packing.menuRename': '名前を変更', - 'packing.menuCheckAll': 'すべてチェック', - 'packing.menuUncheckAll': 'すべて解除', - 'packing.menuDeleteCat': 'カテゴリを削除', - 'packing.noMembers': '旅行メンバーがいません', - 'packing.addItem': '項目を追加', - 'packing.addItemPlaceholder': '項目名...', - 'packing.addCategory': 'カテゴリを追加', - 'packing.newCategoryPlaceholder': 'カテゴリ名(例:衣類)', - 'packing.applyTemplate': 'テンプレートを適用', - 'packing.template': 'テンプレート', - 'packing.templateApplied': 'テンプレートから{count}件追加しました', - 'packing.templateError': 'テンプレートの適用に失敗しました', - 'packing.saveAsTemplate': 'テンプレートとして保存', - 'packing.templateName': 'テンプレート名', - 'packing.templateSaved': '持ち物リストをテンプレートとして保存しました', - 'packing.bags': 'バッグ', - 'packing.noBag': '未割り当て', - 'packing.totalWeight': '総重量', - 'packing.bagName': 'バッグ名...', - 'packing.addBag': 'バッグを追加', - 'packing.changeCategory': 'カテゴリを変更', - 'packing.confirm.clearChecked': 'チェック済み{count}件を削除しますか?', - 'packing.confirm.deleteCat': '{count}件の項目があるカテゴリ「{name}」を削除しますか?', - 'packing.defaultCategory': 'その他', - 'packing.toast.saveError': '保存に失敗しました', - 'packing.toast.deleteError': '削除に失敗しました', - 'packing.toast.renameError': '名前の変更に失敗しました', - 'packing.toast.addError': '追加に失敗しました', - -// Packing suggestions - 'packing.suggestions.items': [ - { name: 'パスポート', category: '書類' }, - { name: '身分証明書', category: '書類' }, - { name: '海外旅行保険', category: '書類' }, - { name: '航空券', category: '書類' }, - { name: 'クレジットカード', category: '金融' }, - { name: '現金', category: '金融' }, - { name: 'ビザ', category: '書類' }, - { name: 'Tシャツ', category: '衣類' }, - { name: 'ズボン', category: '衣類' }, - { name: '下着', category: '衣類' }, - { name: '靴下', category: '衣類' }, - { name: '上着', category: '衣類' }, - { name: '寝間着', category: '衣類' }, - { name: '水着', category: '衣類' }, - { name: 'レインジャケット', category: '衣類' }, - { name: '歩きやすい靴', category: '衣類' }, - { name: '歯ブラシ', category: '洗面用具' }, - { name: '歯磨き粉', category: '洗面用具' }, - { name: 'シャンプー', category: '洗面用具' }, - { name: 'デオドラント', category: '洗面用具' }, - { name: '日焼け止め', category: '洗面用具' }, - { name: 'カミソリ', category: '洗面用具' }, - { name: '充電器', category: '電子機器' }, - { name: 'モバイルバッテリー', category: '電子機器' }, - { name: 'ヘッドホン', category: '電子機器' }, - { name: '変換プラグ', category: '電子機器' }, - { name: 'カメラ', category: '電子機器' }, - { name: '鎮痛薬', category: '健康' }, - { name: '絆創膏', category: '健康' }, - { name: '消毒液', category: '健康' }, - ], - - // Members / Sharing - 'members.shareTrip': '旅行を共有', - 'members.inviteUser': 'ユーザーを招待', - 'members.selectUser': 'ユーザーを選択…', - 'members.invite': '招待', - 'members.allHaveAccess': 'すでに全員がアクセスできます。', - 'members.access': 'アクセス', - 'members.person': '人', - 'members.persons': '人', - 'members.you': 'あなた', - 'members.owner': 'オーナー', - 'members.leaveTrip': '旅行を退出', - 'members.removeAccess': 'アクセスを削除', - 'members.confirmLeave': '旅行を退出しますか?アクセスできなくなります。', - 'members.confirmRemove': 'このユーザーのアクセスを削除しますか?', - 'members.loadError': 'メンバーの読み込みに失敗しました', - 'members.added': '追加しました', - 'members.addError': '追加に失敗しました', - 'members.removed': 'メンバーを削除しました', - 'members.removeError': '削除に失敗しました', - -// Categories (Admin) - 'categories.title': 'カテゴリ', - 'categories.subtitle': '場所のカテゴリを管理', - 'categories.new': '新しいカテゴリ', - 'categories.empty': 'カテゴリはまだありません', - 'categories.namePlaceholder': 'カテゴリ名', - 'categories.icon': 'アイコン', - 'categories.color': '色', - 'categories.customColor': 'カスタムカラーを選択', - 'categories.preview': 'プレビュー', - 'categories.defaultName': 'カテゴリ', - 'categories.update': '更新', - 'categories.create': '作成', - 'categories.confirm.delete': 'カテゴリを削除しますか?このカテゴリの場所は削除されません。', - 'categories.toast.loadError': 'カテゴリの読み込みに失敗しました', - 'categories.toast.nameRequired': '名前を入力してください', - 'categories.toast.updated': 'カテゴリを更新しました', - 'categories.toast.created': 'カテゴリを作成しました', - 'categories.toast.saveError': '保存に失敗しました', - 'categories.toast.deleted': 'カテゴリを削除しました', - 'categories.toast.deleteError': '削除に失敗しました', - -// Backup (Admin) - 'backup.title': 'データバックアップ', - 'backup.subtitle': 'データベースとアップロードされたすべてのファイル', - 'backup.refresh': '更新', - 'backup.upload': 'バックアップをアップロード', - 'backup.uploading': 'アップロード中…', - 'backup.create': 'バックアップを作成', - 'backup.creating': '作成中…', - 'backup.empty': 'バックアップはまだありません', - 'backup.createFirst': '最初のバックアップを作成', - 'backup.download': 'ダウンロード', - 'backup.restore': '復元', - 'backup.confirm.restore': 'バックアップ「{name}」を復元しますか?\n\n現在のすべてのデータはバックアップで置き換えられます。', - 'backup.confirm.uploadRestore': 'バックアップファイル「{name}」をアップロードして復元しますか?\n\n現在のすべてのデータは上書きされます。', - 'backup.confirm.delete': 'バックアップ「{name}」を削除しますか?', - 'backup.toast.loadError': 'バックアップの読み込みに失敗しました', - 'backup.toast.created': 'バックアップを作成しました', - 'backup.toast.createError': 'バックアップの作成に失敗しました', - 'backup.toast.restored': 'バックアップを復元しました。ページを再読み込みします…', - 'backup.toast.restoreError': '復元に失敗しました', - 'backup.toast.uploadError': 'アップロードに失敗しました', - 'backup.toast.deleted': 'バックアップを削除しました', - 'backup.toast.deleteError': '削除に失敗しました', - 'backup.toast.downloadError': 'ダウンロードに失敗しました', - 'backup.toast.settingsSaved': '自動バックアップ設定を保存しました', - 'backup.toast.settingsError': '設定の保存に失敗しました', - 'backup.auto.title': '自動バックアップ', - 'backup.auto.subtitle': 'スケジュールに基づいて自動実行', - 'backup.auto.enable': '自動バックアップを有効化', - 'backup.auto.enableHint': '選択したスケジュールで自動的に作成されます', - 'backup.auto.interval': '間隔', - 'backup.auto.hour': '実行時刻', - 'backup.auto.hourHint': 'サーバーのローカル時刻({format}形式)', - 'backup.auto.dayOfWeek': '曜日', - 'backup.auto.dayOfMonth': '月の日', - 'backup.auto.dayOfMonthHint': 'すべての月に対応するため1~28に制限されています', - 'backup.auto.scheduleSummary': 'スケジュール', - 'backup.auto.summaryDaily': '毎日 {hour}:00', - 'backup.auto.summaryWeekly': '毎週{day} {hour}:00', - 'backup.auto.summaryMonthly': '毎月{day}日 {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': '自動バックアップはDockerの環境変数で設定されています。変更するにはdocker-compose.ymlを更新し、コンテナを再起動してください。', - 'backup.auto.copyEnv': 'Docker環境変数をコピー', - 'backup.auto.envCopied': 'Docker環境変数をコピーしました', - 'backup.auto.keepLabel': '古いバックアップを削除', - 'backup.dow.sunday': '日', - 'backup.dow.monday': '月', - 'backup.dow.tuesday': '火', - 'backup.dow.wednesday': '水', - 'backup.dow.thursday': '木', - 'backup.dow.friday': '金', - 'backup.dow.saturday': '土', - 'backup.interval.hourly': '毎時間', - 'backup.interval.daily': '毎日', - 'backup.interval.weekly': '毎週', - 'backup.interval.monthly': '毎月', - 'backup.keep.1day': '1日', - 'backup.keep.3days': '3日', - 'backup.keep.7days': '7日', - 'backup.keep.14days': '14日', - 'backup.keep.30days': '30日', - 'backup.keep.forever': '無期限', - -// Photos - 'photos.title': '写真', - 'photos.subtitle': '{trip} の写真 {count} 枚', - 'photos.dropHere': 'ここに写真をドロップ…', - 'photos.dropHereActive': 'ここに写真をドロップ', - 'photos.captionForAll': 'キャプション(全体)', - 'photos.captionPlaceholder': '任意のキャプション…', - 'photos.addCaption': 'キャプションを追加…', - 'photos.allDays': 'すべての日', - 'photos.noPhotos': 'まだ写真はありません', - 'photos.uploadHint': '旅行の写真をアップロード', - 'photos.clickToSelect': 'またはクリックして選択', - 'photos.linkPlace': '場所を紐づけ', - 'photos.noPlace': '場所なし', - 'photos.uploadN': '{n} 枚の写真をアップロード', - 'photos.linkDay': '日を紐づけ', - 'photos.noDay': '日付なし', - 'photos.dayLabel': '{number}日目', - 'photos.photoSelected': '写真を選択しました', - 'photos.photosSelected': '写真を選択しました', - 'photos.fileTypeHint': 'JPG、PNG、WebP · 最大 10 MB · 最大 30 枚', - -// Backup restore modal - 'backup.restoreConfirmTitle': 'バックアップを復元しますか?', - 'backup.restoreWarning': '現在のすべてのデータ(旅行、場所、ユーザー、アップロード)はバックアップで完全に置き換えられます。この操作は元に戻せません。', - 'backup.restoreTip': 'ヒント:復元前に現在の状態をバックアップすることをおすすめします。', - 'backup.restoreConfirm': 'はい、復元します', - -// PDF - 'pdf.travelPlan': '旅行計画', - 'pdf.planned': '予定', - 'pdf.costLabel': '費用(EUR)', - 'pdf.preview': 'PDFプレビュー', - 'pdf.saveAsPdf': 'PDFとして保存', - - // Planner - 'planner.places': '場所', - 'planner.bookings': '予約', - 'planner.packingList': '持ち物リスト', - 'planner.documents': 'ドキュメント', - 'planner.dayPlan': '日別計画', - 'planner.reservations': '予約', - 'planner.minTwoPlaces': '座標付きの場所が少なくとも2つ必要です', - 'planner.noGeoPlaces': '座標付きの場所がありません', - 'planner.routeCalculated': 'ルートを計算しました', - 'planner.routeCalcFailed': 'ルートを計算できませんでした', - 'planner.routeError': 'ルート計算中にエラーが発生しました', - 'planner.icsExportFailed': 'ICSの書き出しに失敗しました', - 'planner.routeOptimized': 'ルートを最適化しました', - 'planner.reservationUpdated': '予約を更新しました', - 'planner.reservationAdded': '予約を追加しました', - 'planner.confirmDeleteReservation': '予約を削除しますか?', - 'planner.reservationDeleted': '予約を削除しました', - 'planner.days': '日', - 'planner.allPlaces': 'すべての場所', - 'planner.totalPlaces': '合計{n}件の場所', - 'planner.noDaysPlanned': '計画された日がありません', - 'planner.editTrip': '旅行を編集 →', - 'planner.placeOne': '1件の場所', - 'planner.placeN': '{n}件の場所', - 'planner.addNote': 'メモを追加', - 'planner.noEntries': 'この日の予定はありません', - 'planner.addPlace': '場所/アクティビティを追加', - 'planner.addPlaceShort': '+ 場所/アクティビティ', - 'planner.resPending': '予約保留 · ', - 'planner.resConfirmed': '予約確定 · ', - 'planner.notePlaceholder': 'メモ…', - 'planner.noteTimePlaceholder': '時刻(任意)', - 'planner.noteExamplePlaceholder': '例:中央駅から14:30発のS3、7番桟橋からフェリー、昼食休憩…', - 'planner.totalCost': '合計費用', - 'planner.searchPlaces': '場所を検索…', - 'planner.allCategories': 'すべてのカテゴリ', - 'planner.noPlacesFound': '場所が見つかりません', - 'planner.addFirstPlace': '最初の場所を追加', - 'planner.noReservations': '予約はありません', - 'planner.addFirstReservation': '最初の予約を追加', - 'planner.new': '新規', - 'planner.addToDay': '+ 日', - 'planner.calculating': '計算中…', - 'planner.route': 'ルート', - 'planner.optimize': '最適化', - 'planner.openGoogleMaps': 'Googleマップで開く', - 'planner.selectDayHint': '左の一覧から日を選択すると、日別計画が表示されます', - 'planner.noPlacesForDay': 'この日の場所はまだありません', - 'planner.addPlacesLink': '場所を追加 →', - 'planner.minTotal': '最短合計', - 'planner.noReservation': '予約なし', - 'planner.removeFromDay': 'この日から削除', - 'planner.addToThisDay': 'この日に追加', - 'planner.overview': '概要', - 'planner.noDays': '日がありません', - 'planner.editTripToAddDays': '旅行を編集して日を追加', - 'planner.dayCount': '{n}日間', - 'planner.clickToUnlock': 'クリックして解除', - 'planner.keepPosition': '最適化中も位置を保持', - 'planner.dayDetails': '日詳細', - 'planner.dayN': '{n}日目', - -// Dashboard Stats - 'stats.countries': '国', - 'stats.cities': '都市', - 'stats.trips': '旅行', - 'stats.places': '場所', - 'stats.worldProgress': '世界進捗', - 'stats.visited': '訪問済み', - 'stats.remaining': '未訪問', - 'stats.visitedCountries': '訪問国', - -// Day Detail Panel - 'day.precipProb': '降水確率', - 'day.precipitation': '降水量', - 'day.wind': '風', - 'day.sunrise': '日の出', - 'day.sunset': '日の入り', - 'day.hourlyForecast': '時間別予報', - 'day.climateHint': '過去の平均値 — 実際の予報はこの日付の16日前から表示されます。', - 'day.noWeather': '天気データがありません。座標付きの場所を追加してください。', - 'day.overview': '1日の概要', - 'day.accommodation': '宿泊先', - 'day.addAccommodation': '宿泊先を追加', - 'day.hotelDayRange': '適用日', - 'day.noPlacesForHotel': '先に旅行に場所を追加してください', - 'day.allDays': 'すべて', - 'day.checkIn': 'チェックイン', - 'day.checkInUntil': 'チェックイン期限', - 'day.checkOut': 'チェックアウト', - 'day.confirmation': '確認', - 'day.editAccommodation': '宿泊先を編集', - 'day.reservations': '予約', - -// Photos / Immich - 'memories.title': '写真', - 'memories.notConnected': '{provider_name} が接続されていません', - 'memories.notConnectedHint': '設定で {provider_name} インスタンスを接続すると、この旅行に写真を追加できます。', - 'memories.notConnectedMultipleHint': '設定で次の写真プロバイダーのいずれかを接続してください:{provider_names}', - 'memories.noDates': '写真を読み込むには旅行の日付を追加してください。', - 'memories.noPhotos': '写真が見つかりません', - 'memories.noPhotosHint': '{provider_name} にこの旅行期間の写真がありません。', - 'memories.photosFound': '枚の写真', - 'memories.fromOthers': '他のユーザーから', - 'memories.sharePhotos': '写真を共有', - 'memories.sharing': '共有', - 'memories.reviewTitle': '写真を確認', - 'memories.reviewHint': 'クリックして共有から除外できます。', - 'memories.shareCount': '{count}枚の写真を共有', - //------------------------- - //todo section - 'memories.providerUrl': 'サーバーURL', - 'memories.providerApiKey': 'APIキー', - 'memories.providerUsername': 'ユーザー名', - 'memories.providerPassword': 'パスワード', - 'memories.providerOTP': 'MFAコード(有効な場合)', - 'memories.skipSSLVerification': 'SSL証明書の検証をスキップ', - 'memories.immichAutoUpload': 'アップロード時に旅程の写真をImmichにミラー', - 'memories.providerUrlHintSynology': 'URLにPhotosアプリのパスを含めてください(例:https://nas:5001/photo)', - 'memories.testConnection': '接続をテスト', - 'memories.testFirst': '先に接続をテストしてください', - 'memories.testShort': 'テスト', - 'memories.connected': '接続済み', - 'memories.disconnected': '未接続', - 'memories.connectionSuccess': '{provider_name} に接続しました', - 'memories.connectionError': '{provider_name} に接続できませんでした', - 'memories.saved': '{provider_name} の設定を保存しました', - 'memories.providerDisconnectedBanner': '{provider_name} との接続が切れています。写真を見るには設定で再接続してください。', - 'memories.saveError': '{provider_name} の設定を保存できませんでした', - //------------------------ - 'memories.addPhotos': '写真を追加', - 'memories.linkAlbum': 'アルバムをリンク', - 'memories.selectAlbum': '{provider_name} のアルバムを選択', - 'memories.selectAlbumMultiple': 'アルバムを選択', - 'memories.noAlbums': 'アルバムが見つかりません', - 'memories.syncAlbum': 'アルバムを同期', - 'memories.unlinkAlbum': 'アルバムのリンクを解除', - 'memories.photos': '写真', - 'memories.selectPhotos': '{provider_name} から写真を選択', - 'memories.selectPhotosMultiple': '写真を選択', - 'memories.selectHint': '写真をタップして選択してください。', - 'memories.selected': '選択済み', - 'memories.addSelected': '{count} 枚の写真を追加', - 'memories.alreadyAdded': '追加済み', - 'memories.private': '非公開', - 'memories.stopSharing': '共有を停止', - 'memories.oldest': '古い順', - 'memories.newest': '新しい順', - 'memories.allLocations': 'すべての場所', - 'memories.tripDates': '旅行期間', - 'memories.allPhotos': 'すべての写真', - 'memories.confirmShareTitle': '旅行メンバーと共有しますか?', - 'memories.confirmShareHint': '{count} 枚の写真がこの旅行の全メンバーに表示されます。後から個別に非公開にできます。', - 'memories.confirmShareButton': '写真を共有', - 'memories.error.loadAlbums': 'アルバムの読み込みに失敗しました', - 'memories.error.linkAlbum': 'アルバムのリンクに失敗しました', - 'memories.error.unlinkAlbum': 'アルバムのリンク解除に失敗しました', - 'memories.error.syncAlbum': 'アルバムの同期に失敗しました', - 'memories.error.loadPhotos': '写真の読み込みに失敗しました', - 'memories.error.addPhotos': '写真の追加に失敗しました', - 'memories.error.removePhoto': '写真の削除に失敗しました', - 'memories.error.toggleSharing': '共有設定の更新に失敗しました', - 'memories.saveRouteNotConfigured': 'このプロバイダーでは保存先が設定されていません', - 'memories.testRouteNotConfigured': 'このプロバイダーではテスト用の保存先が設定されていません', - 'memories.fillRequiredFields': '必須項目をすべて入力してください', - - // Collab Addon - 'collab.tabs.chat': 'チャット', - 'collab.tabs.notes': 'ノート', - 'collab.tabs.polls': '投票', -'collab.whatsNext.title': '次にすること', - 'collab.whatsNext.today': '今日', - 'collab.whatsNext.tomorrow': '明日', - 'collab.whatsNext.empty': '予定されたアクティビティはありません', - 'collab.whatsNext.until': '〜', - 'collab.whatsNext.emptyHint': '時間が設定されたアクティビティがここに表示されます', - 'collab.chat.send': '送信', - 'collab.chat.placeholder': 'メッセージを入力…', - 'collab.chat.empty': '会話を始めましょう', - 'collab.chat.emptyHint': 'メッセージは旅行メンバー全員と共有されます', - 'collab.chat.emptyDesc': 'アイデアや計画、最新情報を共有しましょう', - 'collab.chat.today': '今日', - 'collab.chat.yesterday': '昨日', - 'collab.chat.deletedMessage': 'メッセージを削除しました', - 'collab.chat.reply': '返信', - 'collab.chat.loadMore': '以前のメッセージを読み込む', - 'collab.chat.justNow': 'たった今', - 'collab.chat.minutesAgo': '{n}分前', - 'collab.chat.hoursAgo': '{n}時間前', - 'collab.notes.title': 'ノート', - 'collab.notes.new': '新規ノート', - 'collab.notes.empty': 'まだノートがありません', - 'collab.notes.emptyHint': 'アイデアや計画を書き留めましょう', - 'collab.notes.all': 'すべて', - 'collab.notes.titlePlaceholder': 'ノートのタイトル', - 'collab.notes.contentPlaceholder': '内容を入力…', - 'collab.notes.categoryPlaceholder': 'カテゴリ', - 'collab.notes.newCategory': '新しいカテゴリ…', - 'collab.notes.category': 'カテゴリ', - 'collab.notes.noCategory': 'カテゴリなし', - 'collab.notes.color': '色', - 'collab.notes.save': '保存', - 'collab.notes.cancel': 'キャンセル', - 'collab.notes.edit': '編集', - 'collab.notes.delete': '削除', - 'collab.notes.pin': '固定', - 'collab.notes.unpin': '固定を解除', - 'collab.notes.daysAgo': '{n}日前', - 'collab.notes.categorySettings': 'カテゴリ管理', - 'collab.notes.create': '作成', - 'collab.notes.website': 'ウェブサイト', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'ファイルを添付', - 'collab.notes.noCategoriesYet': 'カテゴリがまだありません', - 'collab.notes.emptyDesc': 'ノートを作成して始めましょう', - 'collab.polls.title': '投票', - 'collab.polls.new': '新しい投票', - 'collab.polls.empty': 'まだ投票がありません', - 'collab.polls.emptyHint': '質問してみんなで投票しましょう', - 'collab.polls.question': '質問', - 'collab.polls.questionPlaceholder': '何をしますか?', - 'collab.polls.addOption': '+ 選択肢を追加', - 'collab.polls.optionPlaceholder': '選択肢 {n}', - 'collab.polls.create': '投票を作成', - 'collab.polls.close': '閉じる', - 'collab.polls.closed': '終了', - 'collab.polls.votes': '{n}票', - 'collab.polls.vote': '{n}票', - 'collab.polls.multipleChoice': '複数選択', - 'collab.polls.multiChoice': '複数選択', - 'collab.polls.deadline': '締切', - 'collab.polls.option': '選択肢', - 'collab.polls.options': '選択肢', - 'collab.polls.delete': '削除', - 'collab.polls.closedSection': '終了', - - // Permissions - 'admin.tabs.permissions': '権限', - 'perm.title': '権限設定', - 'perm.subtitle': 'アプリ全体の操作権限を管理', - 'perm.saved': '権限設定を保存しました', - 'perm.resetDefaults': '既定に戻す', - 'perm.customized': 'カスタマイズ済み', - 'perm.level.admin': '管理者のみ', - 'perm.level.tripOwner': '旅行オーナー', - 'perm.level.tripMember': '旅行メンバー', - 'perm.level.everybody': '全員', - 'perm.cat.trip': '旅行管理', - 'perm.cat.members': 'メンバー管理', - 'perm.cat.files': 'ファイル', - 'perm.cat.content': 'コンテンツと予定', - 'perm.cat.extras': '予算・持ち物・コラボ', - 'perm.action.trip_create': '旅行を作成', - 'perm.action.trip_edit': '旅行詳細を編集', - 'perm.action.trip_delete': '旅行を削除', - 'perm.action.trip_archive': '旅行をアーカイブ/復元', - 'perm.action.trip_cover_upload': 'カバー画像をアップロード', - 'perm.action.member_manage': 'メンバーを追加/削除', - 'perm.action.file_upload': 'ファイルをアップロード', - 'perm.action.file_edit': 'ファイル情報を編集', - 'perm.action.file_delete': 'ファイルを削除', - 'perm.action.place_edit': '場所を追加/編集/削除', - 'perm.action.day_edit': '日・メモ・割り当てを編集', - 'perm.action.reservation_edit': '予約を管理', - 'perm.action.budget_edit': '予算を管理', - 'perm.action.packing_edit': '持ち物を管理', - 'perm.action.collab_edit': 'コラボ(メモ・投票・チャット)', - 'perm.action.share_manage': '共有リンクを管理', - 'perm.actionHint.trip_create': '新しい旅行を作成できる人', - 'perm.actionHint.trip_edit': '旅行名や日付などを変更できる人', - 'perm.actionHint.trip_delete': '旅行を完全に削除できる人', - 'perm.actionHint.trip_archive': '旅行をアーカイブできる人', - 'perm.actionHint.trip_cover_upload': 'カバー画像を変更できる人', - 'perm.actionHint.member_manage': 'メンバーを招待/削除できる人', - 'perm.actionHint.file_upload': 'ファイルをアップロードできる人', - 'perm.actionHint.file_edit': 'ファイル説明やリンクを編集できる人', - 'perm.actionHint.file_delete': 'ファイルをゴミ箱へ移動/完全削除できる人', - 'perm.actionHint.place_edit': '場所を追加・編集・削除できる人', - 'perm.actionHint.day_edit': '日やメモ、割り当てを編集できる人', - 'perm.actionHint.reservation_edit': '予約を作成・編集・削除できる人', - 'perm.actionHint.budget_edit': '予算項目を管理できる人', - 'perm.actionHint.packing_edit': '持ち物やバッグを管理できる人', - 'perm.actionHint.collab_edit': 'メモや投票、メッセージを作成できる人', - 'perm.actionHint.share_manage': '公開共有リンクを管理できる人', - - // Undo - 'undo.button': '元に戻す', - 'undo.tooltip': '元に戻す: {action}', - 'undo.assignPlace': '場所を日に割り当て', - 'undo.removeAssignment': '日の割り当てを解除', - 'undo.reorder': '場所を並び替え', - 'undo.optimize': 'ルートを最適化', - 'undo.deletePlace': '場所を削除', - 'undo.deletePlaces': '場所を削除', - 'undo.moveDay': '場所を別の日に移動', - 'undo.lock': '場所のロックを切り替え', - 'undo.importGpx': 'GPXをインポート', - 'undo.importKeyholeMarkup': 'KMZ/KMLをインポート', - 'undo.importGoogleList': 'Googleマップをインポート', - 'undo.importNaverList': 'Naverマップをインポート', - 'undo.addPlace': '場所を追加', - 'undo.done': '元に戻しました: {action}', - - // Notifications - 'notifications.title': '通知', - 'notifications.markAllRead': 'すべて既読', - 'notifications.deleteAll': 'すべて削除', - 'notifications.showAll': 'すべて表示', - 'notifications.empty': '通知はありません', - 'notifications.emptyDescription': 'すべて確認済みです!', - 'notifications.all': 'すべて', - 'notifications.unreadOnly': '未読', - 'notifications.markRead': '既読にする', - 'notifications.markUnread': '未読にする', - 'notifications.delete': '削除', - 'notifications.system': 'システム', - 'notifications.synologySessionCleared.title': 'Synology Photosが切断されました', - 'notifications.synologySessionCleared.text': 'サーバーまたはアカウントが変更されました。設定で接続を再テストしてください。', - - // Notification test keys (dev only) - 'notifications.versionAvailable.title': '更新があります', - 'notifications.versionAvailable.text': 'TREK {version} が利用可能です。', - 'notifications.versionAvailable.button': '詳細を見る', - 'notifications.test.title': '{actor} からのテスト通知', - 'notifications.test.text': 'これはテスト通知です。', - 'notifications.test.booleanTitle': '{actor} が承認を求めています', - 'notifications.test.booleanText': 'テスト用の承認通知です。', - 'notifications.test.accept': '承認', - 'notifications.test.decline': '却下', - 'notifications.test.navigateTitle': '確認してください', - 'notifications.test.navigateText': 'テスト用の遷移通知です。', - 'notifications.test.goThere': '移動', - 'notifications.test.adminTitle': '管理者通知', - 'notifications.test.adminText': '{actor} が管理者全員に通知を送りました。', - 'notifications.test.tripTitle': '{actor} が旅行に投稿しました', - 'notifications.test.tripText': '旅行「{trip}」のテスト通知です。', - - // Todo - 'todo.subtab.packing': '持ち物リスト', - 'todo.subtab.todo': 'ToDo', - 'todo.completed': '完了', - 'todo.filter.all': 'すべて', - 'todo.filter.open': '未完了', - 'todo.filter.done': '完了', - 'todo.uncategorized': '未分類', - 'todo.namePlaceholder': 'タスク名', - 'todo.descriptionPlaceholder': '説明(任意)', - 'todo.unassigned': '未割り当て', - 'todo.noCategory': 'カテゴリなし', - 'todo.hasDescription': '説明あり', - 'todo.addItem': '新しいタスクを追加...', - 'todo.sidebar.sortBy': '並び替え', - 'todo.priority': '優先度', - 'todo.newCategoryLabel': '新規', - 'budget.categoriesLabel': 'カテゴリ', - 'todo.newCategory': 'カテゴリ名', - 'todo.addCategory': 'カテゴリを追加', - 'todo.newItem': '新しいタスク', - 'todo.empty': 'タスクはまだありません。追加して始めましょう!', - 'todo.filter.my': '自分のタスク', - 'todo.filter.overdue': '期限切れ', - 'todo.sidebar.tasks': 'タスク', - 'todo.sidebar.categories': 'カテゴリ', - 'todo.detail.title': 'タスク', - 'todo.detail.description': '説明', - 'todo.detail.category': 'カテゴリ', - 'todo.detail.dueDate': '期限', - 'todo.detail.assignedTo': '担当者', - 'todo.detail.delete': '削除', - 'todo.detail.save': '変更を保存', - 'todo.sortByPrio': '優先度', - 'todo.detail.priority': '優先度', - 'todo.detail.noPriority': 'なし', - 'todo.detail.create': 'タスクを作成', - - // Notifications — dev test events - 'notif.test.title': '[テスト] 通知', - 'notif.test.simple.text': 'これはシンプルなテスト通知です。', - 'notif.test.boolean.text': 'このテスト通知を承認しますか?', - 'notif.test.navigate.text': '下をクリックしてダッシュボードに移動してください。', - - // Notifications - 'notif.trip_invite.title': '旅行への招待', - 'notif.trip_invite.text': '{actor}が「{trip}」に招待しました', - 'notif.booking_change.title': '予約が更新されました', - 'notif.booking_change.text': '{actor}が「{trip}」の予約を更新しました', - 'notif.trip_reminder.title': '旅行リマインド', - 'notif.trip_reminder.text': '旅行「{trip}」がまもなく始まります!', - 'notif.todo_due.title': 'ToDoの期限', - 'notif.todo_due.text': '「{trip}」の{todo}は{due}が期限です', - 'notif.vacay_invite.title': 'Vacay Fusionへの招待', - 'notif.vacay_invite.text': '{actor}から旅行プランの統合に招待されました', - 'notif.photos_shared.title': '写真が共有されました', - 'notif.photos_shared.text': '{actor}が「{trip}」で{count}枚の写真を共有しました', - 'notif.collab_message.title': '新しいメッセージ', - 'notif.collab_message.text': '{actor}が「{trip}」でメッセージを送りました', - 'notif.packing_tagged.title': '持ち物の割り当て', - 'notif.packing_tagged.text': '{actor}が「{trip}」の{category}をあなたに割り当てました', - 'notif.version_available.title': '新しいバージョンがあります', - 'notif.version_available.text': 'TREK {version}が利用可能です', - 'notif.action.view_trip': '旅行を見る', - 'notif.action.view_collab': 'メッセージを見る', - 'notif.action.view_packing': '持ち物を見る', - 'notif.action.view_photos': '写真を見る', - 'notif.action.view_vacay': 'Vacayを見る', - 'notif.action.view_admin': '管理画面へ', - 'notif.action.view': '表示', - 'notif.action.accept': '承認', - 'notif.action.decline': '拒否', - 'notif.generic.title': '通知', - 'notif.generic.text': '新しい通知があります', - 'notif.dev.unknown_event.title': '[DEV] 不明なイベント', - 'notif.dev.unknown_event.text': 'イベントタイプ「{event}」はEVENT_NOTIFICATION_CONFIGに登録されていません', - -// Journey addon - 'journey.search.placeholder': '日記を検索…', - 'journey.search.noResults': '「{query}」に一致する日記はありません', - 'journey.title': '日記', - 'journey.subtitle': '旅の記録をリアルタイムで残そう', - 'journey.new': '新しい日記', - 'journey.create': '作成', - 'journey.titlePlaceholder': 'どこへ行きますか?', - 'journey.empty': '日記はまだありません', - 'journey.emptyHint': '次の旅を記録してみましょう', - 'journey.deleted': '日記を削除しました', - 'journey.createError': '日記を作成できませんでした', - 'journey.deleteError': '日記を削除できませんでした', - 'journey.deleteConfirmTitle': '削除', - 'journey.deleteConfirmMessage': '「{title}」を削除しますか?元に戻せません。', - 'journey.deleteConfirmGeneric': '本当に削除しますか?', - 'journey.notFound': '日記が見つかりません', - 'journey.photos': '写真', - 'journey.timelineEmpty': 'まだ立ち寄りがありません', - 'journey.timelineEmptyHint': 'チェックインするか、日記を書いて始めましょう', - 'journey.status.draft': '下書き', - 'journey.status.active': '進行中', - 'journey.status.completed': '完了', - 'journey.status.upcoming': '予定', - 'journey.status.archived': 'アーカイブ', - 'journey.checkin.add': 'チェックイン', - 'journey.checkin.namePlaceholder': '場所名', - 'journey.checkin.notesPlaceholder': 'メモ(任意)', - 'journey.checkin.save': '保存', - 'journey.checkin.error': 'チェックインを保存できませんでした', - 'journey.entry.add': '日記', - 'journey.entry.edit': '編集', - 'journey.entry.titlePlaceholder': 'タイトル(任意)', - 'journey.entry.bodyPlaceholder': '今日は何がありましたか?', - 'journey.entry.save': '保存', - 'journey.entry.error': '日記を保存できませんでした', - 'journey.photo.add': '写真', - 'journey.photo.uploadError': 'アップロードに失敗しました', - 'journey.share.share': '共有', - 'journey.share.public': '公開', - 'journey.share.linkCopied': '公開リンクをコピーしました', - 'journey.share.disabled': '公開共有は無効です', - 'journey.editor.titlePlaceholder': 'この瞬間に名前をつけて…', - 'journey.editor.bodyPlaceholder': 'この日のストーリーを書いてみよう…', - 'journey.editor.placePlaceholder': '場所(任意)', - 'journey.editor.tagsPlaceholder': 'タグ:穴場、最高の食事、また行きたい…', - 'journey.visibility.private': '非公開', - 'journey.visibility.shared': '共有', - 'journey.visibility.public': '公開', - 'journey.emptyState.title': 'ここから物語が始まります', - 'journey.emptyState.subtitle': '場所にチェックインするか、最初の日記を書いてみましょう', - -// Journey Frontpage - 'journey.frontpage.subtitle': '旅を、忘れられない物語に', - 'journey.frontpage.createJourney': '日記を作成', - 'journey.frontpage.activeJourney': '進行中の日記', - 'journey.frontpage.allJourneys': 'すべての日記', - 'journey.frontpage.journeys': '日記', - 'journey.frontpage.createNew': '新しい日記を作成', - 'journey.frontpage.createNewSub': '旅を選んで、物語を書き、共有しよう', - 'journey.frontpage.live': 'ライブ', - 'journey.frontpage.synced': '同期済み', - 'journey.frontpage.continueWriting': '続けて書く', - 'journey.frontpage.updated': '{time}に更新', - 'journey.frontpage.suggestionLabel': '旅行が終了しました', - 'journey.frontpage.suggestionText': '{title}を日記にしよう', - 'journey.frontpage.dismiss': '閉じる', - 'journey.frontpage.journeyName': '日記名', - 'journey.frontpage.namePlaceholder': '例:東南アジア 2026', - 'journey.frontpage.selectTrips': '旅行を選択', - 'journey.frontpage.tripsSelected': '件選択', - 'journey.frontpage.trips': '旅行', - 'journey.frontpage.placesImported': '場所がインポートされます', - 'journey.frontpage.places': '場所', - - // Journey Detail - 'journey.detail.backToJourney': '日記に戻る', - 'journey.detail.syncedWithTrips': '旅行と同期済み', - 'journey.detail.addEntry': 'エントリーを追加', - 'journey.detail.newEntry': '新しいエントリー', - 'journey.detail.editEntry': 'エントリーを編集', - 'journey.detail.noEntries': 'エントリーはまだありません', - 'journey.detail.noEntriesHint': '旅行を追加して下書きエントリーを作成しましょう', - 'journey.detail.noPhotos': '写真はまだありません', - 'journey.detail.noPhotosHint': 'エントリーに写真を追加するか、Immich/Synologyライブラリを表示', - 'journey.detail.journeyTab': '日記', - 'journey.detail.journeyStats': '統計', - 'journey.detail.syncedTrips': '同期中の旅行', - 'journey.detail.noTripsLinked': 'リンクされた旅行はありません', - 'journey.detail.contributors': '参加者', - 'journey.detail.readMore': 'もっと見る', - 'journey.detail.prosCons': '良かった点・気になった点', - 'journey.detail.photos': '写真', - 'journey.detail.day': '{number}日目', - 'journey.detail.places': '場所', - -// Journey Detail — Stats - 'journey.stats.days': '日数', - 'journey.stats.cities': '都市', - 'journey.stats.entries': 'エントリー', - 'journey.stats.photos': '写真', - 'journey.stats.places': '場所', - 'journey.skeletons.show': '提案を表示', - 'journey.skeletons.hide': '提案を非表示', - -// Journey Detail — Verdict - 'journey.verdict.lovedIt': '最高だった', - 'journey.verdict.couldBeBetter': '改善の余地あり', - -// Journey Detail — Synced badge - 'journey.synced.places': '場所', - 'journey.synced.synced': '同期済み', - -// Journey Entry Editor - 'journey.editor.discardChangesConfirm': '未保存の変更があります。破棄しますか?', - 'journey.editor.uploadPhotos': '写真をアップロード', - 'journey.editor.uploading': 'アップロード中…', - 'journey.editor.fromGallery': 'ギャラリーから', - 'journey.editor.allPhotosAdded': 'すべての写真は追加済みです', - 'journey.editor.writeStory': 'ストーリーを書く…', - 'journey.editor.prosCons': '良かった点・気になった点', - 'journey.editor.pros': '良かった点', - 'journey.editor.cons': '気になった点', - 'journey.editor.proPlaceholder': '良かったこと…', - 'journey.editor.conPlaceholder': 'いまいちだったこと…', - 'journey.editor.addAnother': '追加', - 'journey.editor.date': '日付', - 'journey.editor.location': '場所', - 'journey.editor.searchLocation': '場所を検索…', - 'journey.editor.mood': '気分', - 'journey.editor.weather': '天気', - 'journey.editor.photoFirst': '1番目', - 'journey.editor.makeFirst': '1番目にする', - 'journey.editor.searching': '検索中…', - -// Journey Entry — Moods - 'journey.mood.amazing': '最高', - 'journey.mood.good': '良い', - 'journey.mood.neutral': '普通', - 'journey.mood.rough': '大変', - -// Journey Entry — Weather - 'journey.weather.sunny': '晴れ', - 'journey.weather.partly': '晴れ時々くもり', - 'journey.weather.cloudy': 'くもり', - 'journey.weather.rainy': '雨', - 'journey.weather.stormy': '嵐', - 'journey.weather.cold': '雪', - -// Journey — Trip Linking - 'journey.trips.linkTrip': '旅行をリンク', - 'journey.trips.searchTrip': '旅行を検索', - 'journey.trips.searchPlaceholder': '旅行名または目的地…', - 'journey.trips.noTripsAvailable': '利用できる旅行がありません', - 'journey.trips.link': 'リンク', - 'journey.trips.tripLinked': '旅行をリンクしました', - 'journey.trips.linkFailed': 'リンクに失敗しました', - 'journey.trips.addTrip': '旅行を追加', - 'journey.trips.unlinkTrip': 'リンク解除', - 'journey.trips.unlinkMessage': '「{title}」のリンクを解除しますか?この旅行から同期されたエントリーと写真はすべて完全に削除されます。元に戻せません。', - 'journey.trips.unlink': '解除', - 'journey.trips.tripUnlinked': 'リンクを解除しました', - 'journey.trips.unlinkFailed': '解除に失敗しました', - 'journey.trips.noTripsLinkedSettings': 'リンクされた旅行はありません', - -// Journey — Contributors - 'journey.contributors.invite': '参加者を招待', - 'journey.contributors.searchUser': 'ユーザーを検索', - 'journey.contributors.searchPlaceholder': 'ユーザー名またはメール…', - 'journey.contributors.noUsers': 'ユーザーが見つかりません', - 'journey.contributors.role': '役割', - 'journey.contributors.added': '参加者を追加しました', - 'journey.contributors.addFailed': '追加に失敗しました', - 'journey.contributors.remove': '参加者を削除', - 'journey.contributors.removeConfirm': '{username}をこの日記から削除しますか?', - 'journey.contributors.removed': '参加者を削除しました', - 'journey.contributors.removeFailed': '削除に失敗しました', - -// Journey — Share - 'journey.share.publicShare': '公開共有', - 'journey.share.createLink': '共有リンクを作成', - 'journey.share.linkCreated': '共有リンクを作成しました', - 'journey.share.createFailed': 'リンク作成に失敗しました', - 'journey.share.copy': 'コピー', - 'journey.share.copied': 'コピーしました!', - 'journey.share.timeline': 'タイムライン', - 'journey.share.gallery': 'ギャラリー', - 'journey.share.map': 'マップ', - 'journey.share.removeLink': '共有リンクを削除', - 'journey.share.linkDeleted': '共有リンクを削除しました', - 'journey.share.deleteFailed': '削除に失敗しました', - 'journey.share.updateFailed': '更新に失敗しました', - -// Journey — Invite - 'journey.invite.role': '役割', - 'journey.invite.viewer': '閲覧者', - 'journey.invite.editor': '編集者', - 'journey.invite.invite': '招待', - 'journey.invite.inviting': '招待中…', - -// Journey — Settings Dialog - 'journey.settings.title': '日記設定', - 'journey.settings.coverImage': 'カバー画像', - 'journey.settings.changeCover': 'カバーを変更', - 'journey.settings.addCover': 'カバー画像を追加', - 'journey.settings.name': '名前', - 'journey.settings.subtitle': 'サブタイトル', - 'journey.settings.subtitlePlaceholder': '例:タイ・ベトナム・カンボジア', - 'journey.settings.endJourney': '日記をアーカイブ', - 'journey.settings.reopenJourney': '日記を復元', - 'journey.settings.archived': '日記をアーカイブしました', - 'journey.settings.reopened': '日記を復元しました', - 'journey.settings.endDescription': 'Liveバッジを非表示にします。いつでも再開できます。', - 'journey.settings.delete': '削除', - 'journey.settings.deleteJourney': '日記を削除', - 'journey.settings.deleteMessage': '「{title}」を削除しますか?すべてのエントリーと写真が失われます。', - 'journey.settings.saved': '設定を保存しました', - 'journey.settings.saveFailed': '保存に失敗しました', - 'journey.settings.coverUpdated': 'カバーを更新しました', - 'journey.settings.coverFailed': 'アップロードに失敗しました', - 'journey.settings.failedToDelete': '削除に失敗しました', - 'journey.entries.deleteTitle': 'エントリーを削除', - 'journey.photosUploaded': '{count}枚の写真をアップロード', - 'journey.photosAdded': '{count}枚の写真を追加', - -// Journey — Public Page - 'journey.public.notFound': '見つかりません', - 'journey.public.notFoundMessage': 'この日記は存在しないか、リンクの有効期限が切れています。', - 'journey.public.readOnly': '閲覧のみ · 公開日記', - 'journey.public.tagline': '旅の記録&探索キット', - 'journey.public.sharedVia': '共有元', - 'journey.public.madeWith': '作成:', - -// Journey — PDF Export - 'journey.pdf.journeyBook': '日記ブック', - 'journey.pdf.madeWith': 'Made with TREK', - 'journey.pdf.day': '日目', - 'journey.pdf.theEnd': 'おわり', - 'journey.pdf.saveAsPdf': 'PDFとして保存', - 'journey.pdf.pages': 'ページ', - 'journey.picker.tripPeriod': '旅行期間', - 'journey.picker.dateRange': '日付範囲', - 'journey.picker.allPhotos': 'すべての写真', - 'journey.picker.albums': 'アルバム', - 'journey.picker.selected': '選択中', - 'journey.picker.addTo': '追加先', - 'journey.picker.newGallery': '新しいギャラリー', - 'journey.picker.selectAll': 'すべて選択', - 'journey.picker.deselectAll': '選択解除', - 'journey.picker.noAlbums': 'アルバムがありません', - 'journey.picker.selectDate': '日付を選択', - 'journey.picker.search': '検索', - -// Dashboard Mobile - 'dashboard.greeting.morning': 'おはようございます、', - 'dashboard.greeting.afternoon': 'こんにちは、', - 'dashboard.greeting.evening': 'こんばんは、', - 'dashboard.mobile.liveNow': 'ライブ中', - 'dashboard.mobile.tripProgress': '旅行の進行状況', - 'dashboard.mobile.daysLeft': '残り{count}日', - 'dashboard.mobile.places': '場所', - 'dashboard.mobile.buddies': '仲間', - 'dashboard.mobile.newTrip': '新しい旅行', - 'dashboard.mobile.currency': '通貨', - 'dashboard.mobile.timezone': 'タイムゾーン', - 'dashboard.mobile.upcomingTrips': '今後の旅行', - 'dashboard.mobile.yourTrips': 'あなたの旅行', - 'dashboard.mobile.trips': '旅行', - 'dashboard.mobile.starts': '開始', - 'dashboard.mobile.duration': '期間', - 'dashboard.mobile.day': '日', - 'dashboard.mobile.days': '日', - 'dashboard.mobile.ongoing': '進行中', - 'dashboard.mobile.startsToday': '今日開始', - 'dashboard.mobile.tomorrow': '明日', - 'dashboard.mobile.inDays': '{count}日後', - 'dashboard.mobile.inMonths': '{count}か月後', - 'dashboard.mobile.completed': '完了', - 'dashboard.mobile.currencyConverter': '通貨換算', - - // BottomNav & Profile - 'nav.profile': 'プロフィール', - 'nav.bottomSettings': '設定', - 'nav.bottomAdmin': '管理者設定', - 'nav.bottomLogout': 'ログアウト', - 'nav.bottomAdminBadge': '管理者', - -// DayPlan Mobile - 'dayplan.mobile.addPlace': '場所を追加', - 'dayplan.mobile.searchPlaces': '場所を検索…', - 'dayplan.mobile.allAssigned': 'すべて割り当て済み', - 'dayplan.mobile.noMatch': '一致なし', - 'dayplan.mobile.createNew': '新しい場所を作成', - -'admin.addons.catalog.journey.name': '日記', - 'admin.addons.catalog.journey.description': 'チェックイン、写真、日ごとのストーリーで旅を記録', - -// OAuth scope groups - 'oauth.scope.group.trips': '旅行', - 'oauth.scope.group.places': '場所', - 'oauth.scope.group.atlas': '地図', - 'oauth.scope.group.packing': '持ち物', - 'oauth.scope.group.todos': 'ToDo', - 'oauth.scope.group.budget': '予算', - 'oauth.scope.group.reservations': '予約', - 'oauth.scope.group.collab': 'コラボ', - 'oauth.scope.group.notifications': '通知', - 'oauth.scope.group.vacay': '休暇', - 'oauth.scope.group.geo': '地図', - 'oauth.scope.group.weather': '天気', - 'oauth.scope.group.journey': '日記', - -// OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': '旅行・旅程を表示', - 'oauth.scope.trips:read.description': '旅行、日程、メモ、メンバーを閲覧', - 'oauth.scope.trips:write.label': '旅行・旅程を編集', - 'oauth.scope.trips:write.description': '旅行や日程、メモの作成・更新、メンバー管理', - 'oauth.scope.trips:delete.label': '旅行を削除', - 'oauth.scope.trips:delete.description': '旅行全体を完全に削除(元に戻せません)', - 'oauth.scope.trips:share.label': '共有リンクを管理', - 'oauth.scope.trips:share.description': '旅行の公開共有リンクを作成・更新・無効化', - 'oauth.scope.places:read.label': '場所・地図データを表示', - 'oauth.scope.places:read.description': '場所、日への割り当て、タグ、カテゴリを閲覧', - 'oauth.scope.places:write.label': '場所を管理', - 'oauth.scope.places:write.description': '場所、割り当て、タグの作成・更新・削除', - 'oauth.scope.atlas:read.label': '地図を表示', - 'oauth.scope.atlas:read.description': '訪問した国・地域、バケットリストを閲覧', - 'oauth.scope.atlas:write.label': '地図を管理', - 'oauth.scope.atlas:write.description': '訪問済みの国・地域を管理、バケットリスト編集', - 'oauth.scope.packing:read.label': '持ち物リストを表示', - 'oauth.scope.packing:read.description': '持ち物、バッグ、担当者を閲覧', - 'oauth.scope.packing:write.label': '持ち物リストを管理', - 'oauth.scope.packing:write.description': '持ち物やバッグの追加・編集・削除・並び替え', - 'oauth.scope.todos:read.label': 'ToDoリストを表示', - 'oauth.scope.todos:read.description': '旅行のToDoと担当者を閲覧', - 'oauth.scope.todos:write.label': 'ToDoリストを管理', - 'oauth.scope.todos:write.description': 'ToDoの作成・編集・完了・削除・並び替え', - 'oauth.scope.budget:read.label': '予算を表示', - 'oauth.scope.budget:read.description': '予算項目や内訳を閲覧', - 'oauth.scope.budget:write.label': '予算を管理', - 'oauth.scope.budget:write.description': '予算項目の作成・編集・削除', - 'oauth.scope.reservations:read.label': '予約を表示', - 'oauth.scope.reservations:read.description': '予約や宿泊情報を閲覧', - 'oauth.scope.reservations:write.label': '予約を管理', - 'oauth.scope.reservations:write.description': '予約の作成・編集・削除・並び替え', - 'oauth.scope.collab:read.label': 'コラボを表示', - 'oauth.scope.collab:read.description': '共同メモ、投票、メッセージを閲覧', - 'oauth.scope.collab:write.label': 'コラボを管理', - 'oauth.scope.collab:write.description': '共同メモ、投票、メッセージを管理', - 'oauth.scope.notifications:read.label': '通知を表示', - 'oauth.scope.notifications:read.description': 'アプリ内通知と未読数を閲覧', - 'oauth.scope.notifications:write.label': '通知を管理', - 'oauth.scope.notifications:write.description': '通知を既読にする・対応する', - 'oauth.scope.vacay:read.label': '休暇プランを表示', - 'oauth.scope.vacay:read.description': '休暇プランのデータや統計を閲覧', - 'oauth.scope.vacay:write.label': '休暇プランを管理', - 'oauth.scope.vacay:write.description': '休暇エントリーや予定を管理', - 'oauth.scope.geo:read.label': '地図・ジオコーディング', - 'oauth.scope.geo:read.description': '場所検索、地図URL解析、逆ジオコーディング', - 'oauth.scope.weather:read.label': '天気予報', - 'oauth.scope.weather:read.description': '旅行先・日程の天気予報を取得', - 'oauth.scope.journey:read.label': '日記を表示', - 'oauth.scope.journey:read.description': '日記、エントリー、参加者を閲覧', - 'oauth.scope.journey:write.label': '日記を管理', - 'oauth.scope.journey:write.description': '日記やエントリーの作成・編集・削除', - 'oauth.scope.journey:share.label': '日記共有を管理', - 'oauth.scope.journey:share.description': '公開共有リンクの作成・更新・無効化', - -// System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': '写真の場所が3.0で変更されました', - 'system_notice.v3_photos.body': '旅行プランナー内の写真は削除されましたが、写真データは安全です。TREKがImmichやSynologyのライブラリを変更することはありません。\n\n写真は現在日記アドオンにあります。日記は任意機能です。未有効の場合は、管理画面 → アドオンで有効にしてください。', - 'system_notice.v3_journey.title': '日記登場 — 旅の日記', - 'system_notice.v3_journey.body': 'タイムライン、写真ギャラリー、インタラクティブな地図で旅を物語に。', - 'system_notice.v3_journey.cta_label': '日記を開く', - 'system_notice.v3_journey.highlight_timeline': '日ごとのタイムラインとギャラリー', - 'system_notice.v3_journey.highlight_photos': 'ImmichやSynologyからインポート', - 'system_notice.v3_journey.highlight_share': 'ログイン不要で公開共有', - 'system_notice.v3_journey.highlight_export': 'PDFフォトブックとして書き出し', - 'system_notice.v3_features.title': '3.0のその他の注目点', - 'system_notice.v3_features.body': '今回のリリースで知っておきたいポイント。', - 'system_notice.v3_features.highlight_dashboard': 'モバイル重視のダッシュボード刷新', - 'system_notice.v3_features.highlight_offline': 'PWAとして完全オフライン対応', - 'system_notice.v3_features.highlight_search': 'リアルタイム場所検索', - 'system_notice.v3_features.highlight_import': 'KMZ/KMLから場所をインポート', - -// System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1に更新', - 'system_notice.v3_mcp.body': 'MCP連携が全面的に刷新されました。OAuth 2.1が推奨認証方式です。従来の静的トークン(trek_…)は非推奨となり、将来削除されます。', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1推奨(mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24の詳細な権限スコープ', - 'system_notice.v3_mcp.highlight_deprecated': '静的trek_トークンは非推奨', - 'system_notice.v3_mcp.highlight_tools': 'ツールとプロンプトを拡張', - -// System notices — personal thank you - 'system_notice.v3_thankyou.title': '開発者より一言', - 'system_notice.v3_thankyou.body': '少しだけお時間をください。\n\nTREKは、自分の旅のために作った小さな個人プロジェクトでした。それが今では4,000人以上に使ってもらえるとは思ってもいませんでした。スターも、Issueも、機能要望も、すべて目を通しています。\n\nTREKはこれからもオープンソース、自分でホストでき、あなたのものです。トラッキングなし、サブスクなし。旅が好きな人が作ったツールです。\n\nhttps://github.com/jubnlにも感謝を。3.0の多くはあなたのおかげです。\n\nバグ報告、翻訳、共有、利用してくれたすべての方へ—本当にありがとうございます。\n\nこれからも一緒に旅を。\n\n— Maurice', - - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': '対応が必要:ユーザーアカウントの競合', - 'system_notice.v3014_whitespace_collision.body': '3.0.14 へのアップグレードにより、保存されているアカウントの先頭または末尾の空白が原因で、ユーザー名またはメールアドレスの競合が1件以上検出されました。影響を受けたアカウントは自動的にリネームされています。対象となるアカウントを特定するには、サーバーログで **[migration] WHITESPACE COLLISION** で始まる行を確認してください。', -// System notices — onboarding - - 'system_notice.welcome_v1.title': 'TREKへようこそ', - 'system_notice.welcome_v1.body': 'オールインワンの旅行プランナー。旅程作成、共有、整理をオンライン・オフラインで。', - 'system_notice.welcome_v1.cta_label': '旅行を計画', - 'system_notice.welcome_v1.hero_alt': 'TREKのUIが重なった風景写真', - 'system_notice.welcome_v1.highlight_plan': '日ごとの旅程作成', - 'system_notice.welcome_v1.highlight_share': '仲間と共同編集', - 'system_notice.welcome_v1.highlight_offline': 'モバイルでオフライン対応', - 'system_notice.dev_test_modal.title': '[Dev] テスト通知', - 'system_notice.dev_test_modal.body': 'これは開発用テスト通知です。', - 'system_notice.pager.prev': '前へ', - 'system_notice.pager.next': '次へ', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': '通知{n}へ', - 'system_notice.pager.position': '{total}件中{current}件目', - 'transport.addTransport': '移動手段を追加', - 'transport.modalTitle.create': '移動手段を追加', - 'transport.modalTitle.edit': '移動手段を編集', - 'transport.title': '移動手段', - 'transport.addManual': '手動で追加', - - // Added to match EN keys - 'journey.editor.uploadingProgress': 'アップロード中 {done}/{total}…', - 'journey.editor.uploadFailed': '写真のアップロードに失敗しました', - 'journey.editor.uploadPartialFailed': '{total}枚中{failed}枚の写真がアップロードに失敗しました — もう一度保存して再試行してください', - 'journey.photosUploadFailed': '一部の写真をアップロードできませんでした', - 'settings.oauth.modal.machineClient': 'マシンクライアント(ブラウザログインなし)', - 'settings.oauth.modal.machineClientHint': 'client_credentials グラントを使用します — リダイレクト URI は不要です。トークンは client_id + client_secret を介して直接発行され、選択したスコープ内であなたとして動作します。', - 'settings.oauth.modal.machineClientUsage': 'トークンを取得するには、grant_type=client_credentials、client_id、client_secret を指定して POST /oauth/token を呼び出します。ブラウザもリフレッシュトークンも不要です。', - 'settings.oauth.badge.machine': 'マシン', -} - -export default ja diff --git a/client/src/i18n/translations/ko.ts b/client/src/i18n/translations/ko.ts deleted file mode 100644 index c3a4849f..00000000 --- a/client/src/i18n/translations/ko.ts +++ /dev/null @@ -1,2428 +0,0 @@ -const ko: Record = { - // Common - 'common.save': '저장', - 'common.showMore': '더 보기', - 'common.showLess': '접기', - 'common.cancel': '취소', - 'common.clear': '지우기', - 'common.delete': '삭제', - 'common.edit': '편집', - 'common.add': '추가', - 'common.loading': '로딩 중...', - 'common.import': '가져오기', - 'common.select': '선택', - 'common.selectAll': '전체 선택', - 'common.deselectAll': '전체 해제', - 'common.error': '오류', - 'common.unknownError': '알 수 없는 오류', - 'common.tooManyAttempts': '시도 횟수가 너무 많습니다. 잠시 후 다시 시도해 주세요.', - 'common.back': '뒤로', - 'common.all': '전체', - 'common.close': '닫기', - 'common.open': '열기', - 'common.upload': '업로드', - 'common.search': '검색', - 'common.confirm': '확인', - 'common.ok': '확인', - 'common.yes': '예', - 'common.no': '아니오', - 'common.or': '또는', - 'common.none': '없음', - 'common.date': '날짜', - 'common.rename': '이름 변경', - 'common.discardChanges': '변경 사항 취소', - 'common.discard': '취소', - 'common.name': '이름', - 'common.email': '이메일', - 'common.password': '비밀번호', - 'common.saving': '저장 중...', - 'common.justNow': '방금 전', - 'common.hoursAgo': '{count}시간 전', - 'common.daysAgo': '{count}일 전', - 'common.saved': '저장됨', - 'trips.memberRemoved': '{username} 제거됨', - 'trips.memberRemoveError': '제거 실패', - 'trips.memberAdded': '{username} 추가됨', - 'trips.memberAddError': '추가 실패', - 'trips.reminder': '리마인더', - 'trips.reminderNone': '없음', - 'trips.reminderDay': '일', - 'trips.reminderDays': '일', - 'trips.reminderCustom': '직접 설정', - 'trips.reminderDaysBefore': '일 전 출발', - 'trips.reminderDisabledHint': '여행 리마인더가 비활성화되어 있습니다. 관리자 > 설정 > 알림에서 활성화하세요.', - 'common.update': '업데이트', - 'common.change': '변경', - 'common.uploading': '업로드 중…', - 'common.backToPlanning': '계획으로 돌아가기', - 'common.reset': '초기화', - 'common.expand': '펼치기', - 'common.collapse': '접기', - - // Navbar - 'nav.trip': '여행', - 'nav.share': '공유', - 'nav.settings': '설정', - 'nav.admin': '관리자', - 'nav.logout': '로그아웃', - 'nav.lightMode': '라이트 모드', - 'nav.darkMode': '다크 모드', - 'nav.autoMode': '자동 모드', - 'nav.administrator': '관리자', - - // Dashboard - 'dashboard.title': '내 여행', - 'dashboard.subtitle.loading': '여행 불러오는 중...', - 'dashboard.subtitle.trips': '{count}개 여행 ({archived}개 보관됨)', - 'dashboard.subtitle.empty': '첫 번째 여행을 시작하세요', - 'dashboard.subtitle.activeOne': '활성 여행 {count}개', - 'dashboard.subtitle.activeMany': '활성 여행 {count}개', - 'dashboard.subtitle.archivedSuffix': ' · {count}개 보관됨', - 'dashboard.newTrip': '새 여행', - 'dashboard.gridView': '격자 보기', - 'dashboard.listView': '목록 보기', - 'dashboard.currency': '통화', - 'dashboard.timezone': '시간대', - 'dashboard.localTime': '현지', - 'dashboard.timezoneCustomTitle': '사용자 지정 시간대', - 'dashboard.timezoneCustomLabelPlaceholder': '레이블 (선택)', - 'dashboard.timezoneCustomTzPlaceholder': '예: America/New_York', - 'dashboard.timezoneCustomAdd': '추가', - 'dashboard.timezoneCustomErrorEmpty': '시간대 식별자를 입력하세요', - 'dashboard.timezoneCustomErrorInvalid': '잘못된 시간대입니다. Europe/Berlin 같은 형식을 사용하세요', - 'dashboard.timezoneCustomErrorDuplicate': '이미 추가됨', - 'dashboard.emptyTitle': '아직 여행이 없습니다', - 'dashboard.emptyText': '첫 번째 여행을 만들고 계획을 시작하세요!', - 'dashboard.emptyButton': '첫 번째 여행 만들기', - 'dashboard.nextTrip': '다음 여행', - 'dashboard.shared': '공유됨', - 'dashboard.sharedBy': '{name}이(가) 공유', - 'dashboard.days': '일', - 'dashboard.places': '장소', - 'dashboard.members': '동행자', - 'dashboard.archive': '보관', - 'dashboard.copyTrip': '복사', - 'dashboard.copySuffix': '사본', - 'dashboard.restore': '복원', - 'dashboard.archived': '보관됨', - 'dashboard.status.ongoing': '진행 중', - 'dashboard.status.today': '오늘', - 'dashboard.status.tomorrow': '내일', - 'dashboard.status.past': '지난 여행', - 'dashboard.status.daysLeft': '{count}일 남음', - 'dashboard.toast.loadError': '여행 불러오기 실패', - 'dashboard.toast.created': '여행이 생성되었습니다!', - 'dashboard.toast.createError': '여행 생성 실패', - 'dashboard.toast.updated': '여행이 업데이트되었습니다!', - 'dashboard.toast.updateError': '여행 업데이트 실패', - 'dashboard.toast.deleted': '여행이 삭제되었습니다', - 'dashboard.toast.deleteError': '여행 삭제 실패', - 'dashboard.toast.archived': '여행이 보관되었습니다', - 'dashboard.toast.archiveError': '여행 보관 실패', - 'dashboard.toast.restored': '여행이 복원되었습니다', - 'dashboard.toast.restoreError': '여행 복원 실패', - 'dashboard.toast.copied': '여행이 복사되었습니다!', - 'dashboard.toast.copyError': '여행 복사 실패', - 'dashboard.confirm.delete': '여행 "{title}"을(를) 삭제할까요? 모든 장소와 계획이 영구 삭제됩니다.', - 'dashboard.confirm.copy.title': '이 여행을 복사할까요?', - 'dashboard.confirm.copy.willCopy': '복사될 항목', - 'dashboard.confirm.copy.will1': '일정, 장소 및 일별 배정', - 'dashboard.confirm.copy.will2': '숙박 및 예약', - 'dashboard.confirm.copy.will3': '예산 항목 및 카테고리 순서', - 'dashboard.confirm.copy.will4': '짐 목록 (체크 해제 상태)', - 'dashboard.confirm.copy.will5': '할 일 (미배정 및 미완료)', - 'dashboard.confirm.copy.will6': '일별 메모', - 'dashboard.confirm.copy.wontCopy': '복사되지 않는 항목', - 'dashboard.confirm.copy.wont1': '공동 작업자 및 멤버 배정', - 'dashboard.confirm.copy.wont2': '공동 작업 메모, 투표 및 메시지', - 'dashboard.confirm.copy.wont3': '파일 및 사진', - 'dashboard.confirm.copy.wont4': '공유 토큰', - 'dashboard.confirm.copy.confirm': '여행 복사', - 'dashboard.editTrip': '여행 편집', - 'dashboard.createTrip': '새 여행 만들기', - 'dashboard.tripTitle': '제목', - 'dashboard.tripTitlePlaceholder': '예: 일본에서의 여름', - 'dashboard.tripDescription': '설명', - 'dashboard.tripDescriptionPlaceholder': '이 여행에 대해 설명해 주세요', - 'dashboard.startDate': '시작일', - 'dashboard.endDate': '종료일', - 'dashboard.dayCount': '일수', - 'dashboard.dayCountHint': '여행 날짜가 설정되지 않은 경우 계획할 일수입니다.', - 'dashboard.noDateHint': '날짜 미설정 — 기본 7일이 생성됩니다. 언제든지 변경할 수 있습니다.', - 'dashboard.coverImage': '커버 이미지', - 'dashboard.addCoverImage': '커버 이미지 추가 (또는 끌어다 놓기)', - 'dashboard.addMembers': '동행자', - 'dashboard.addMember': '멤버 추가', - 'dashboard.coverSaved': '커버 이미지가 저장되었습니다', - 'dashboard.coverUploadError': '업로드 실패', - 'dashboard.coverRemoveError': '삭제 실패', - 'dashboard.titleRequired': '제목을 입력하세요', - 'dashboard.endDateError': '종료일은 시작일 이후여야 합니다', - - // Settings - 'settings.title': '설정', - 'settings.subtitle': '개인 설정을 구성하세요', - 'settings.tabs.display': '화면', - 'settings.tabs.map': '지도', - 'settings.tabs.notifications': '알림', - 'settings.tabs.integrations': '통합', - 'settings.tabs.account': '계정', - 'settings.tabs.offline': '오프라인', - 'settings.tabs.about': '정보', - 'settings.map': '지도', - 'settings.mapTemplate': '지도 템플릿', - 'settings.mapTemplatePlaceholder.select': '템플릿 선택...', - 'settings.mapDefaultHint': '비워두면 OpenStreetMap (기본값) 사용', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': '지도 타일 URL 템플릿', - 'settings.mapProvider': '지도 공급자', - 'settings.mapProviderHint': '여행 플래너 및 Journey 지도에 영향을 줍니다. Atlas는 항상 Leaflet을 사용합니다.', - 'settings.mapLeafletSubtitle': '클래식 2D, 모든 래스터 타일', - 'settings.mapMapboxSubtitle': '벡터 타일, 3D 건물 및 지형', - 'settings.mapExperimental': '실험적', - 'settings.mapMapboxToken': 'Mapbox 액세스 토큰', - 'settings.mapMapboxTokenHint': '공개 토큰 (pk.*) 출처', - 'settings.mapMapboxTokenLink': 'mapbox.com → 액세스 토큰', - 'settings.mapStyle': '지도 스타일', - 'settings.mapStylePlaceholder': 'Mapbox 스타일 선택', - 'settings.mapStyleHint': '프리셋 또는 mapbox://styles/USER/ID URL 직접 입력', - 'settings.map3dBuildings': '3D 건물 및 지형', - 'settings.map3dHint': '기울기 + 실제 3D 건물 돌출 — 위성 포함 모든 스타일에서 작동합니다.', - 'settings.mapHighQuality': '고품질 모드', - 'settings.mapHighQualityHint': '안티앨리어싱 + 구형 투영으로 선명한 경계와 현실적인 지구 뷰를 제공합니다.', - 'settings.mapHighQualityWarning': '저사양 기기에서 성능에 영향을 줄 수 있습니다.', - 'settings.mapTipLabel': '팁:', - 'settings.mapTip': '우클릭 후 드래그하여 지도를 회전/기울이세요. 가운데 클릭으로 장소를 추가할 수 있습니다 (우클릭은 회전 전용).', - 'settings.latitude': '위도', - 'settings.longitude': '경도', - 'settings.saveMap': '지도 저장', - 'settings.apiKeys': 'API 키', - 'settings.mapsKey': 'Google Maps API 키', - 'settings.mapsKeyHint': '장소 검색용. Places API (New) 필요. console.cloud.google.com에서 발급', - 'settings.weatherKey': 'OpenWeatherMap API 키', - 'settings.weatherKeyHint': '날씨 데이터용. openweathermap.org/api에서 무료 발급', - 'settings.keyPlaceholder': '키 입력...', - 'settings.configured': '설정됨', - 'settings.saveKeys': '키 저장', - 'settings.display': '화면', - 'settings.colorMode': '색상 모드', - 'settings.light': '라이트', - 'settings.dark': '다크', - 'settings.auto': '자동', - 'settings.language': '언어', - 'settings.temperature': '온도 단위', - 'settings.timeFormat': '시간 형식', - 'settings.bookingLabels': '예약 경로 레이블', - 'settings.bookingLabelsHint': '지도에 역 / 공항 이름을 표시합니다. 끄면 아이콘만 표시됩니다.', - 'settings.blurBookingCodes': '예약 코드 흐리게', - 'settings.notifications': '알림', - 'settings.notifyTripInvite': '여행 초대', - 'settings.notifyBookingChange': '예약 변경', - 'settings.notifyTripReminder': '여행 리마인더', - 'settings.notifyTodoDue': '할 일 마감 임박', - 'settings.notifyVacayInvite': 'Vacay 퓨전 초대', - 'settings.notifyPhotosShared': '공유된 사진 (Immich)', - 'settings.notifyCollabMessage': '채팅 메시지 (Collab)', - 'settings.notifyPackingTagged': '짐 목록: 배정', - 'settings.notifyWebhook': '웹훅 알림', - 'settings.notifyVersionAvailable': '새 버전 사용 가능', - 'settings.notificationPreferences.email': '이메일', - 'settings.notificationPreferences.webhook': '웹훅', - 'settings.notificationPreferences.inapp': '앱 내', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': '알림 채널이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 설정을 요청하세요.', - 'settings.webhookUrl.label': '웹훅 URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Discord, Slack 또는 사용자 지정 웹훅 URL을 입력하여 알림을 받으세요.', - 'settings.webhookUrl.saved': '웹훅 URL이 저장되었습니다', - 'settings.webhookUrl.test': '테스트', - 'settings.webhookUrl.testSuccess': '테스트 웹훅이 성공적으로 전송되었습니다', - 'settings.webhookUrl.testFailed': '테스트 웹훅 실패', - 'settings.ntfyUrl.topicLabel': 'Ntfy 토픽', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy 서버 URL (선택)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'ntfy 토픽을 입력하여 푸시 알림을 받으세요. 서버를 비워두면 관리자가 설정한 기본값을 사용합니다.', - 'settings.ntfyUrl.tokenLabel': '액세스 토큰 (선택)', - 'settings.ntfyUrl.tokenHint': '비밀번호로 보호된 토픽에 필요합니다.', - 'settings.ntfyUrl.saved': 'Ntfy 설정이 저장되었습니다', - 'settings.ntfyUrl.test': '테스트', - 'settings.ntfyUrl.testSuccess': '테스트 ntfy 알림이 성공적으로 전송되었습니다', - 'settings.ntfyUrl.testFailed': '테스트 ntfy 알림 실패', - 'settings.ntfyUrl.tokenCleared': '액세스 토큰이 삭제되었습니다', - 'admin.notifications.title': '알림', - 'admin.notifications.hint': '알림 채널을 하나 선택하세요. 한 번에 하나만 활성화할 수 있습니다.', - 'admin.notifications.none': '비활성화', - 'admin.notifications.email': '이메일 (SMTP)', - 'admin.notifications.webhook': '웹훅', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': '사용자가 자신의 ntfy 토픽을 설정하여 푸시 알림을 받을 수 있습니다. 아래에 기본 서버를 설정하면 사용자 설정에 미리 채워집니다.', - 'admin.notifications.save': '알림 설정 저장', - 'admin.notifications.saved': '알림 설정이 저장되었습니다', - 'admin.notifications.testWebhook': '테스트 웹훅 전송', - 'admin.notifications.testWebhookSuccess': '테스트 웹훅이 성공적으로 전송되었습니다', - 'admin.notifications.testWebhookFailed': '테스트 웹훅 실패', - 'admin.notifications.testNtfy': '테스트 ntfy 전송', - 'admin.notifications.testNtfySuccess': '테스트 ntfy가 성공적으로 전송되었습니다', - 'admin.notifications.testNtfyFailed': '테스트 ntfy 실패', - 'admin.notifications.emailPanel.title': '이메일 (SMTP)', - 'admin.notifications.webhookPanel.title': '웹훅', - 'admin.notifications.inappPanel.title': '앱 내', - 'admin.notifications.inappPanel.hint': '앱 내 알림은 항상 활성화되어 있으며 전역으로 비활성화할 수 없습니다.', - 'admin.notifications.adminWebhookPanel.title': '관리자 웹훅', - 'admin.notifications.adminWebhookPanel.hint': '이 웹훅은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 웹훅과 별개이며 설정 시 항상 실행됩니다.', - 'admin.notifications.adminWebhookPanel.saved': '관리자 웹훅 URL이 저장되었습니다', - 'admin.notifications.adminWebhookPanel.testSuccess': '테스트 웹훅이 성공적으로 전송되었습니다', - 'admin.notifications.adminWebhookPanel.testFailed': '테스트 웹훅 실패', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'URL이 설정되면 관리자 웹훅은 항상 실행됩니다', - 'admin.notifications.adminNtfyPanel.title': '관리자 Ntfy', - 'admin.notifications.adminNtfyPanel.hint': '이 ntfy 토픽은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 토픽과 별개이며 설정 시 항상 실행됩니다.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 서버 URL', - 'admin.notifications.adminNtfyPanel.serverHint': '사용자 ntfy 알림의 기본 서버로도 사용됩니다. 비워두면 ntfy.sh가 기본값입니다. 사용자는 자신의 설정에서 변경할 수 있습니다.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': '관리자 토픽', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': '액세스 토큰 (선택)', - 'admin.notifications.adminNtfyPanel.tokenCleared': '관리자 액세스 토큰이 삭제되었습니다', - 'admin.notifications.adminNtfyPanel.saved': '관리자 ntfy 설정이 저장되었습니다', - 'admin.notifications.adminNtfyPanel.test': '테스트 ntfy 전송', - 'admin.notifications.adminNtfyPanel.testSuccess': '테스트 ntfy가 성공적으로 전송되었습니다', - 'admin.notifications.adminNtfyPanel.testFailed': '테스트 ntfy 실패', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': '토픽이 설정되면 관리자 ntfy는 항상 실행됩니다', - 'admin.notifications.adminNotificationsHint': '관리자 전용 알림 (예: 버전 알림)을 전달할 채널을 설정하세요.', - 'admin.notifications.tripReminders.title': '여행 리마인더', - 'admin.notifications.tripReminders.hint': '여행 시작 전에 리마인더 알림을 전송합니다 (여행에 리마인더 일수가 설정되어 있어야 합니다).', - 'admin.notifications.tripReminders.enabled': '여행 리마인더 활성화됨', - 'admin.notifications.tripReminders.disabled': '여행 리마인더 비활성화됨', - 'admin.smtp.title': '이메일 및 알림', - 'admin.smtp.hint': '이메일 알림 전송을 위한 SMTP 설정입니다.', - 'admin.smtp.testButton': '테스트 이메일 전송', - 'admin.webhook.hint': '사용자가 알림용 웹훅 URL을 설정할 수 있습니다 (Discord, Slack 등).', - 'admin.smtp.testSuccess': '테스트 이메일이 성공적으로 전송되었습니다', - 'admin.smtp.testFailed': '테스트 이메일 실패', - 'settings.notificationsDisabled': '알림이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 활성화를 요청하세요.', - 'settings.notificationsActive': '활성 채널', - 'settings.notificationsManagedByAdmin': '알림 이벤트는 관리자가 설정합니다.', - 'dayplan.icsTooltip': '캘린더 내보내기 (ICS)', - 'share.linkTitle': '공개 링크', - 'share.linkHint': '로그인 없이 이 여행을 볼 수 있는 링크를 만드세요. 읽기 전용 — 편집 불가.', - 'share.createLink': '링크 만들기', - 'share.deleteLink': '링크 삭제', - 'share.createError': '링크 생성 실패', - 'common.copy': '복사', - 'common.copied': '복사됨', - 'share.permMap': '지도 및 계획', - 'share.permBookings': '예약', - 'share.permPacking': '짐 목록', - 'shared.expired': '링크가 만료되었거나 유효하지 않습니다', - 'shared.expiredHint': '이 공유 여행 링크는 더 이상 유효하지 않습니다.', - 'shared.readOnly': '읽기 전용 공유 보기', - 'shared.tabPlan': '계획', - 'shared.tabBookings': '예약', - 'shared.tabPacking': '짐 목록', - 'shared.tabBudget': '예산', - 'shared.tabChat': '채팅', - 'shared.days': '일', - 'shared.places': '장소', - 'shared.other': '기타', - 'shared.totalBudget': '총 예산', - 'shared.messages': '메시지', - 'shared.sharedVia': '공유 경로', - 'shared.confirmed': '확정됨', - 'shared.pending': '대기 중', - 'share.permBudget': '예산', - 'share.permCollab': '채팅', - 'settings.on': '켜기', - 'settings.off': '끄기', - 'settings.mcp.title': 'MCP 설정', - 'settings.mcp.endpoint': 'MCP 엔드포인트', - 'settings.mcp.clientConfig': '클라이언트 설정', - 'settings.mcp.clientConfigHint': '을 아래 목록의 API 토큰으로 교체하세요. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).', - 'settings.mcp.clientConfigHintOAuth': '을 위에서 생성한 OAuth 2.1 클라이언트 자격 증명으로 교체하세요. mcp-remote가 처음 연결 시 브라우저를 열어 인증을 완료합니다. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).', - 'settings.mcp.copy': '복사', - 'settings.mcp.copied': '복사됨!', - 'settings.mcp.apiTokens': 'API 토큰', - 'settings.mcp.createToken': '새 토큰 만들기', - 'settings.mcp.noTokens': '토큰이 없습니다. MCP 클라이언트 연결을 위해 토큰을 만드세요.', - 'settings.mcp.tokenCreatedAt': '생성일', - 'settings.mcp.tokenUsedAt': '사용일', - 'settings.mcp.deleteTokenTitle': '토큰 삭제', - 'settings.mcp.deleteTokenMessage': '이 토큰은 즉시 무효화됩니다. 이 토큰을 사용하는 MCP 클라이언트는 접근 권한을 잃게 됩니다.', - 'settings.mcp.modal.createTitle': 'API 토큰 만들기', - 'settings.mcp.modal.tokenName': '토큰 이름', - 'settings.mcp.modal.tokenNamePlaceholder': '예: Claude Desktop, 업무용 노트북', - 'settings.mcp.modal.creating': '생성 중…', - 'settings.mcp.modal.create': '토큰 만들기', - 'settings.mcp.modal.createdTitle': '토큰이 생성되었습니다', - 'settings.mcp.modal.createdWarning': '이 토큰은 한 번만 표시됩니다. 지금 복사하여 저장하세요 — 다시 복구할 수 없습니다.', - 'settings.mcp.modal.done': '완료', - 'settings.mcp.toast.created': '토큰이 생성되었습니다', - 'settings.mcp.toast.createError': '토큰 생성 실패', - 'settings.mcp.toast.deleted': '토큰이 삭제되었습니다', - 'settings.mcp.toast.deleteError': '토큰 삭제 실패', - 'settings.mcp.apiTokensDeprecated': 'API 토큰은 더 이상 사용되지 않으며 향후 버전에서 제거될 예정입니다. 대신 OAuth 2.1 클라이언트를 사용하세요.', - 'settings.oauth.clients': 'OAuth 2.1 클라이언트', - 'settings.oauth.clientsHint': 'OAuth 2.1 클라이언트를 등록하여 타사 MCP 앱 (Claude Web, Cursor 등)이 정적 토큰 없이 연결할 수 있도록 하세요.', - 'settings.oauth.createClient': '새 클라이언트', - 'settings.oauth.noClients': '등록된 OAuth 클라이언트가 없습니다.', - 'settings.oauth.clientId': '클라이언트 ID', - 'settings.oauth.clientSecret': '클라이언트 시크릿', - 'settings.oauth.deleteClient': '클라이언트 삭제', - 'settings.oauth.deleteClientMessage': '이 클라이언트와 모든 활성 세션이 영구 삭제됩니다. 이 클라이언트를 사용하는 앱은 즉시 접근 권한을 잃게 됩니다.', - 'settings.oauth.rotateSecret': '시크릿 교체', - 'settings.oauth.rotateSecretMessage': '새 클라이언트 시크릿이 생성되고 기존 세션은 즉시 무효화됩니다. 이 대화창을 닫기 전에 앱을 업데이트하세요.', - 'settings.oauth.rotateSecretConfirm': '교체', - 'settings.oauth.rotateSecretConfirming': '교체 중…', - 'settings.oauth.rotateSecretDoneTitle': '새 시크릿이 생성되었습니다', - 'settings.oauth.rotateSecretDoneWarning': '이 시크릿은 한 번만 표시됩니다. 지금 복사하여 앱을 업데이트하세요 — 이전 세션은 모두 무효화되었습니다.', - 'settings.oauth.activeSessions': '활성 OAuth 세션', - 'settings.oauth.sessionScopes': '권한 범위', - 'settings.oauth.sessionExpires': '만료', - 'settings.oauth.revoke': '취소', - 'settings.oauth.revokeSession': '세션 취소', - 'settings.oauth.revokeSessionMessage': '이 OAuth 세션의 접근 권한이 즉시 취소됩니다.', - 'settings.oauth.modal.createTitle': 'OAuth 클라이언트 등록', - 'settings.oauth.modal.presets': '빠른 프리셋', - 'settings.oauth.modal.clientName': '앱 이름', - 'settings.oauth.modal.clientNamePlaceholder': '예: Claude Web, My MCP App', - 'settings.oauth.modal.redirectUris': '리디렉션 URI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': '한 줄에 URI 하나. HTTPS 필수 (localhost 예외). 정확히 일치해야 합니다.', - 'settings.oauth.modal.scopes': '허용 권한 범위', - 'settings.oauth.modal.scopesHint': 'list_trips 및 get_trip_summary는 항상 사용 가능합니다 — 권한 범위 불필요. AI가 다른 도구를 사용하는 데 필요한 여행 ID를 찾을 수 있습니다.', - 'settings.oauth.modal.selectAll': '전체 선택', - 'settings.oauth.modal.deselectAll': '전체 해제', - 'settings.oauth.modal.creating': '등록 중…', - 'settings.oauth.modal.create': '클라이언트 등록', - 'settings.oauth.modal.createdTitle': '클라이언트가 등록되었습니다', - 'settings.oauth.modal.createdWarning': '클라이언트 시크릿은 한 번만 표시됩니다. 지금 복사하세요 — 다시 복구할 수 없습니다.', - 'settings.oauth.toast.createError': 'OAuth 클라이언트 등록 실패', - 'settings.oauth.toast.deleted': 'OAuth 클라이언트가 삭제되었습니다', - 'settings.oauth.toast.deleteError': 'OAuth 클라이언트 삭제 실패', - 'settings.oauth.toast.revoked': '세션이 취소되었습니다', - 'settings.oauth.toast.revokeError': '세션 취소 실패', - 'settings.oauth.toast.rotateError': '클라이언트 시크릿 교체 실패', - 'settings.account': '계정', - 'settings.about': '정보', - 'settings.about.reportBug': '버그 신고', - 'settings.about.reportBugHint': '문제를 발견하셨나요? 알려주세요', - 'settings.about.featureRequest': '기능 요청', - 'settings.about.featureRequestHint': '새로운 기능을 제안하세요', - 'settings.about.wikiHint': '문서 및 가이드', - 'settings.about.supporters.badge': '월간 후원자', - 'settings.about.supporters.title': 'TREK의 여행 동반자', - 'settings.about.supporters.subtitle': '다음 여정을 계획하는 동안, 이분들이 TREK의 미래를 함께 만들어가고 있습니다. 월간 후원금은 개발과 실제 작업 시간에 직접 사용되어 TREK이 오픈 소스로 유지될 수 있게 합니다.', - 'settings.about.supporters.since': '{date}부터 후원자', - 'settings.about.supporters.tierEmpty': '첫 번째 후원자가 되어보세요', - 'settings.about.supporter.tier.noReturnTicket': '편도 티켓', - 'settings.about.supporter.tier.lostLuggageVip': '분실 수하물 VIP', - 'settings.about.supporter.tier.businessClassDreamer': '비즈니스 클래스 꿈꾸기', - 'settings.about.supporter.tier.budgetTraveller': '알뜰 여행자', - 'settings.about.supporter.tier.hostelBunkmate': '호스텔 룸메이트', - 'settings.about.description': 'TREK은 첫 아이디어부터 마지막 추억까지 여행을 체계적으로 관리하는 자체 호스팅 여행 플래너입니다. 일별 계획, 예산, 짐 목록, 사진 등 모든 것이 하나의 서버에 담겨 있습니다.', - 'settings.about.madeWith': '으로 만들어졌습니다', - 'settings.about.madeBy': 'Maurice와 성장하는 오픈 소스 커뮤니티가 함께', - 'settings.username': '사용자 이름', - 'settings.email': '이메일', - 'settings.role': '역할', - 'settings.roleAdmin': '관리자', - 'settings.oidcLinked': '연결됨', - 'settings.changePassword': '비밀번호 변경', - 'settings.currentPassword': '현재 비밀번호', - 'settings.currentPasswordRequired': '현재 비밀번호를 입력하세요', - 'settings.newPassword': '새 비밀번호', - 'settings.confirmPassword': '새 비밀번호 확인', - 'settings.updatePassword': '비밀번호 업데이트', - 'settings.passwordRequired': '현재 비밀번호와 새 비밀번호를 입력하세요', - 'settings.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다', - 'settings.passwordMismatch': '비밀번호가 일치하지 않습니다', - 'settings.passwordWeak': '비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다', - 'settings.passwordChanged': '비밀번호가 성공적으로 변경되었습니다', - 'settings.mustChangePassword': '계속하기 전에 비밀번호를 변경해야 합니다. 아래에서 새 비밀번호를 설정하세요.', - 'settings.deleteAccount': '계정 삭제', - 'settings.deleteAccountTitle': '계정을 삭제할까요?', - 'settings.deleteAccountWarning': '계정과 모든 여행, 장소, 파일이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.', - 'settings.deleteAccountConfirm': '영구 삭제', - 'settings.deleteBlockedTitle': '삭제 불가', - 'settings.deleteBlockedMessage': '유일한 관리자입니다. 계정을 삭제하기 전에 다른 사용자를 관리자로 승격하세요.', - 'settings.roleUser': '사용자', - 'settings.saveProfile': '프로필 저장', - 'settings.toast.mapSaved': '지도 설정이 저장되었습니다', - 'settings.toast.keysSaved': 'API 키가 저장되었습니다', - 'settings.toast.displaySaved': '화면 설정이 저장되었습니다', - 'settings.toast.profileSaved': '프로필이 저장되었습니다', - 'settings.uploadAvatar': '프로필 사진 업로드', - 'settings.removeAvatar': '프로필 사진 삭제', - 'settings.avatarUploaded': '프로필 사진이 업데이트되었습니다', - 'settings.avatarRemoved': '프로필 사진이 삭제되었습니다', - 'settings.avatarError': '업로드 실패', - 'settings.mfa.title': '2단계 인증 (2FA)', - 'settings.mfa.description': '이메일 및 비밀번호로 로그인할 때 두 번째 단계를 추가합니다. 인증 앱 (Google Authenticator, Authy 등)을 사용하세요.', - 'settings.mfa.requiredByPolicy': '관리자가 2단계 인증을 요구합니다. 앱을 계속 사용하려면 아래에서 인증 앱을 설정하세요.', - 'settings.mfa.backupTitle': '백업 코드', - 'settings.mfa.backupDescription': '인증 앱에 접근할 수 없을 때 이 일회용 백업 코드를 사용하세요.', - 'settings.mfa.backupWarning': '지금 이 코드를 저장하세요. 각 코드는 한 번만 사용할 수 있습니다.', - 'settings.mfa.backupCopy': '코드 복사', - 'settings.mfa.backupDownload': 'TXT 다운로드', - 'settings.mfa.backupPrint': '인쇄 / PDF', - 'settings.mfa.backupCopied': '백업 코드가 복사되었습니다', - 'settings.mfa.enabled': '계정에 2FA가 활성화되어 있습니다.', - 'settings.mfa.disabled': '2FA가 활성화되지 않았습니다.', - 'settings.mfa.setup': '인증 앱 설정', - 'settings.mfa.scanQr': '앱으로 이 QR 코드를 스캔하거나 시크릿을 수동으로 입력하세요.', - 'settings.mfa.secretLabel': '시크릿 키 (수동 입력)', - 'settings.mfa.codePlaceholder': '6자리 코드', - 'settings.mfa.enable': '2FA 활성화', - 'settings.mfa.cancelSetup': '취소', - 'settings.mfa.disableTitle': '2FA 비활성화', - 'settings.mfa.disableHint': '계정 비밀번호와 인증 앱의 현재 코드를 입력하세요.', - 'settings.mfa.disable': '2FA 비활성화', - 'settings.mfa.toastEnabled': '2단계 인증이 활성화되었습니다', - 'settings.mfa.toastDisabled': '2단계 인증이 비활성화되었습니다', - 'settings.mfa.demoBlocked': '데모 모드에서는 사용할 수 없습니다', - - // Login - 'login.error': '로그인 실패. 자격 증명을 확인하세요.', - 'login.tagline': '나의 여행.\n나의 계획.', - 'login.description': '인터랙티브 지도, 예산, 실시간 동기화로 함께 여행을 계획하세요.', - 'login.features.maps': '인터랙티브 지도', - 'login.features.mapsDesc': 'Google Places, 경로 및 클러스터링', - 'login.features.realtime': '실시간 동기화', - 'login.features.realtimeDesc': 'WebSocket으로 함께 계획', - 'login.features.budget': '예산 추적', - 'login.features.budgetDesc': '카테고리, 차트 및 1인당 비용', - 'login.features.collab': '협업', - 'login.features.collabDesc': '공유 여행으로 다중 사용자 지원', - 'login.features.packing': '짐 목록', - 'login.features.packingDesc': '카테고리, 진행 상황 및 제안', - 'login.features.bookings': '예약', - 'login.features.bookingsDesc': '항공, 호텔, 레스토랑 등', - 'login.features.files': '문서', - 'login.features.filesDesc': '문서 업로드 및 관리', - 'login.features.routes': '스마트 경로', - 'login.features.routesDesc': '자동 최적화 및 Google Maps 내보내기', - 'login.selfHosted': '자체 호스팅 · 오픈 소스 · 내 데이터는 내 것', - 'login.title': '로그인', - 'login.subtitle': '다시 오신 것을 환영합니다', - 'login.signingIn': '로그인 중…', - 'login.signIn': '로그인', - 'login.createAdmin': '관리자 계정 만들기', - 'login.createAdminHint': 'TREK의 첫 번째 관리자 계정을 설정하세요.', - 'login.setNewPassword': '새 비밀번호 설정', - 'login.setNewPasswordHint': '계속하기 전에 비밀번호를 변경해야 합니다.', - 'login.createAccount': '계정 만들기', - 'login.createAccountHint': '새 계정을 등록하세요.', - 'login.creating': '생성 중…', - 'login.noAccount': '계정이 없으신가요?', - 'login.hasAccount': '이미 계정이 있으신가요?', - 'login.register': '회원가입', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': '사용자 이름', - 'login.oidc.registrationDisabled': '회원가입이 비활성화되어 있습니다. 관리자에게 문의하세요.', - 'login.oidc.noEmail': '공급자로부터 이메일을 받지 못했습니다.', - 'login.oidc.tokenFailed': '인증 실패.', - 'login.oidc.invalidState': '유효하지 않은 세션입니다. 다시 시도하세요.', - 'login.demoFailed': '데모 로그인 실패', - 'login.oidcSignIn': '{name}으로 로그인', - 'login.oidcOnly': '비밀번호 인증이 비활성화되었습니다. SSO 공급자로 로그인하세요.', - 'login.oidcLoggedOut': '로그아웃되었습니다. SSO 공급자로 다시 로그인하세요.', - 'login.demoHint': '데모 체험 — 회원가입 불필요', - 'login.mfaTitle': '2단계 인증', - 'login.mfaSubtitle': '인증 앱의 6자리 코드를 입력하세요.', - 'login.mfaCodeLabel': '인증 코드', - 'login.mfaCodeRequired': '인증 앱의 코드를 입력하세요.', - 'login.mfaHint': 'Google Authenticator, Authy 또는 다른 TOTP 앱을 열어주세요.', - 'login.mfaBack': '← 로그인으로 돌아가기', - 'login.mfaVerify': '인증', - 'login.invalidInviteLink': '유효하지 않거나 만료된 초대 링크입니다', - 'login.oidcFailed': 'OIDC 로그인 실패', - 'login.usernameRequired': '사용자 이름을 입력하세요', - 'login.passwordMinLength': '비밀번호는 최소 8자 이상이어야 합니다', - 'login.forgotPassword': '비밀번호를 잊으셨나요?', - 'login.forgotPasswordTitle': '비밀번호 재설정', - 'login.forgotPasswordBody': '가입 시 사용한 이메일 주소를 입력하세요. 계정이 존재하면 재설정 링크를 보내드립니다.', - 'login.forgotPasswordSubmit': '재설정 링크 전송', - 'login.forgotPasswordSentTitle': '이메일을 확인하세요', - 'login.forgotPasswordSentBody': '해당 이메일로 계정이 있다면 재설정 링크가 전송 중입니다. 링크는 60분 후 만료됩니다.', - 'login.forgotPasswordSmtpHintOff': '알림: 관리자가 SMTP를 설정하지 않아 재설정 링크가 이메일 대신 서버 콘솔에 기록됩니다.', - 'login.backToLogin': '로그인으로 돌아가기', - 'login.newPassword': '새 비밀번호', - 'login.confirmPassword': '새 비밀번호 확인', - 'login.passwordsDontMatch': '비밀번호가 일치하지 않습니다', - 'login.mfaCode': '2FA 코드', - 'login.resetPasswordTitle': '새 비밀번호 설정', - 'login.resetPasswordBody': '이전에 사용하지 않은 강력한 비밀번호를 선택하세요. 최소 8자.', - 'login.resetPasswordMfaBody': '재설정을 완료하려면 2FA 코드 또는 백업 코드를 입력하세요.', - 'login.resetPasswordSubmit': '비밀번호 재설정', - 'login.resetPasswordVerify': '인증 및 재설정', - 'login.resetPasswordSuccessTitle': '비밀번호가 업데이트되었습니다', - 'login.resetPasswordSuccessBody': '새 비밀번호로 로그인할 수 있습니다.', - 'login.resetPasswordInvalidLink': '유효하지 않은 재설정 링크', - 'login.resetPasswordInvalidLinkBody': '이 링크가 없거나 손상되었습니다. 새 링크를 요청하세요.', - 'login.resetPasswordFailed': '재설정 실패. 링크가 만료되었을 수 있습니다.', - - // Register - 'register.passwordMismatch': '비밀번호가 일치하지 않습니다', - 'register.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다', - 'register.failed': '회원가입 실패', - 'register.getStarted': '시작하기', - 'register.subtitle': '계정을 만들고 꿈의 여행을 계획하세요.', - 'register.feature1': '무제한 여행 계획', - 'register.feature2': '인터랙티브 지도 보기', - 'register.feature3': '장소 및 카테고리 관리', - 'register.feature4': '예약 추적', - 'register.feature5': '짐 목록 만들기', - 'register.feature6': '사진 및 파일 저장', - 'register.createAccount': '계정 만들기', - 'register.startPlanning': '여행 계획 시작', - 'register.minChars': '최소 6자', - 'register.confirmPassword': '비밀번호 확인', - 'register.repeatPassword': '비밀번호 재입력', - 'register.registering': '가입 중...', - 'register.register': '회원가입', - 'register.hasAccount': '이미 계정이 있으신가요?', - 'register.signIn': '로그인', - - // Admin - 'admin.title': '관리자', - 'admin.subtitle': '사용자 관리 및 시스템 설정', - 'admin.tabs.users': '사용자', - 'admin.tabs.categories': '카테고리', - 'admin.tabs.backup': '백업', - 'admin.tabs.notifications': '알림', - 'admin.tabs.audit': '감사', - 'admin.stats.users': '사용자', - 'admin.stats.trips': '여행', - 'admin.stats.places': '장소', - 'admin.stats.photos': '사진', - 'admin.stats.files': '파일', - 'admin.table.user': '사용자', - 'admin.table.email': '이메일', - 'admin.table.role': '역할', - 'admin.table.created': '생성일', - 'admin.table.lastLogin': '마지막 로그인', - 'admin.table.actions': '작업', - 'admin.you': '(나)', - 'admin.editUser': '사용자 편집', - 'admin.newPassword': '새 비밀번호', - 'admin.newPasswordHint': '비워두면 현재 비밀번호 유지', - 'admin.deleteUser': '사용자 "{name}"을(를) 삭제할까요? 모든 여행이 영구 삭제됩니다.', - 'admin.deleteUserTitle': '사용자 삭제', - 'admin.newPasswordPlaceholder': '새 비밀번호 입력…', - 'admin.toast.loadError': '관리자 데이터 불러오기 실패', - 'admin.toast.userUpdated': '사용자가 업데이트되었습니다', - 'admin.toast.updateError': '업데이트 실패', - 'admin.toast.userDeleted': '사용자가 삭제되었습니다', - 'admin.toast.deleteError': '삭제 실패', - 'admin.toast.cannotDeleteSelf': '자신의 계정은 삭제할 수 없습니다', - 'admin.toast.userCreated': '사용자가 생성되었습니다', - 'admin.toast.createError': '사용자 생성 실패', - 'admin.toast.fieldsRequired': '사용자 이름, 이메일, 비밀번호가 필요합니다', - 'admin.createUser': '사용자 만들기', - 'admin.invite.title': '초대 링크', - 'admin.invite.subtitle': '일회용 회원가입 링크 만들기', - 'admin.invite.create': '링크 만들기', - 'admin.invite.createAndCopy': '만들기 및 복사', - 'admin.invite.empty': '아직 초대 링크가 없습니다', - 'admin.invite.maxUses': '최대 사용 횟수', - 'admin.invite.expiry': '만료 기간', - 'admin.invite.uses': '사용됨', - 'admin.invite.expiresAt': '만료', - 'admin.invite.createdBy': '작성자', - 'admin.invite.active': '활성', - 'admin.invite.expired': '만료됨', - 'admin.invite.usedUp': '사용 완료', - 'admin.invite.copied': '초대 링크가 클립보드에 복사되었습니다', - 'admin.invite.copyLink': '링크 복사', - 'admin.invite.deleted': '초대 링크가 삭제되었습니다', - 'admin.invite.createError': '초대 링크 생성 실패', - 'admin.invite.deleteError': '초대 링크 삭제 실패', - 'admin.tabs.settings': '설정', - 'admin.allowRegistration': '회원가입 허용', - 'admin.allowRegistrationHint': '새 사용자가 직접 회원가입할 수 있습니다', - 'admin.authMethods': '인증 방법', - 'admin.passwordLogin': '비밀번호 로그인', - 'admin.passwordLoginHint': '사용자가 이메일과 비밀번호로 로그인할 수 있습니다', - 'admin.passwordRegistration': '비밀번호 회원가입', - 'admin.passwordRegistrationHint': '새 사용자가 이메일과 비밀번호로 가입할 수 있습니다', - 'admin.oidcLogin': 'SSO 로그인', - 'admin.oidcLoginHint': '사용자가 SSO로 로그인할 수 있습니다', - 'admin.oidcRegistration': 'SSO 자동 프로비저닝', - 'admin.oidcRegistrationHint': '새 SSO 사용자의 계정을 자동으로 생성합니다', - 'admin.envOverrideHint': '비밀번호 로그인 설정은 OIDC_ONLY 환경 변수로 제어되며 여기서 변경할 수 없습니다.', - 'admin.lockoutWarning': '최소 하나의 로그인 방법이 활성화되어 있어야 합니다', - 'admin.requireMfa': '2단계 인증 (2FA) 요구', - 'admin.requireMfaHint': '2FA가 없는 사용자는 앱을 사용하기 전에 설정에서 설정을 완료해야 합니다.', - 'admin.apiKeys': 'API 키', - 'admin.apiKeysHint': '선택 사항. 사진 및 날씨 등 확장된 장소 데이터를 활성화합니다.', - 'admin.mapsKey': 'Google Maps API 키', - 'admin.mapsKeyHint': '장소 검색에 필요합니다. console.cloud.google.com에서 발급', - 'admin.mapsKeyHintLong': 'API 키 없이는 장소 검색에 OpenStreetMap이 사용됩니다. Google API 키가 있으면 사진, 평점, 영업 시간도 불러올 수 있습니다. console.cloud.google.com에서 발급하세요.', - 'admin.recommended': '권장', - 'admin.weatherKey': 'OpenWeatherMap API 키', - 'admin.weatherKeyHint': '날씨 데이터용. openweathermap.org에서 무료 발급', - 'admin.validateKey': '테스트', - 'admin.keyValid': '연결됨', - 'admin.keyInvalid': '유효하지 않음', - 'admin.keySaved': 'API 키가 저장되었습니다', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Google, Apple, Authentik 또는 Keycloak 등 외부 공급자를 통한 로그인을 허용합니다.', - 'admin.oidcDisplayName': '표시 이름', - 'admin.oidcIssuer': '발급자 URL', - 'admin.oidcIssuerHint': '공급자의 OpenID Connect 발급자 URL. 예: https://accounts.google.com', - 'admin.oidcSaved': 'OIDC 설정이 저장되었습니다', - 'admin.oidcOnlyMode': '비밀번호 인증 비활성화', - 'admin.oidcOnlyModeHint': '활성화하면 SSO 로그인만 허용됩니다. 비밀번호 기반 로그인 및 회원가입이 차단됩니다.', - - // File Types - 'admin.fileTypes': '허용된 파일 형식', - 'admin.fileTypesHint': '사용자가 업로드할 수 있는 파일 형식을 설정합니다.', - 'admin.fileTypesFormat': '쉼표로 구분된 확장자 (예: jpg,png,pdf,doc). 모든 형식을 허용하려면 *를 사용하세요.', - 'admin.fileTypesSaved': '파일 형식 설정이 저장되었습니다', - - 'admin.placesPhotos.title': '장소 사진', - 'admin.placesPhotos.subtitle': 'Google Places API에서 사진을 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다. Wikimedia 사진은 영향을 받지 않습니다.', - 'admin.placesAutocomplete.title': '장소 자동완성', - 'admin.placesAutocomplete.subtitle': '검색 제안에 Google Places API를 사용합니다. API 할당량 절약을 위해 비활성화할 수 있습니다.', - 'admin.placesDetails.title': '장소 상세 정보', - 'admin.placesDetails.subtitle': 'Google Places API에서 상세 장소 정보 (영업 시간, 평점, 웹사이트)를 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다.', - // Packing Templates & Bag Tracking - 'admin.bagTracking.title': '가방 추적', - 'admin.bagTracking.subtitle': '짐 항목에 무게 및 가방 배정을 활성화합니다', - 'admin.collab.chat.title': '채팅', - 'admin.collab.chat.subtitle': '여행 협업을 위한 실시간 메시지', - 'admin.collab.notes.title': '메모', - 'admin.collab.notes.subtitle': '공유 메모 및 문서', - 'admin.collab.polls.title': '투표', - 'admin.collab.polls.subtitle': '그룹 투표', - 'admin.collab.whatsnext.title': '다음 할 일', - 'admin.collab.whatsnext.subtitle': '활동 제안 및 다음 단계', - 'admin.tabs.config': '개인 설정', - 'admin.tabs.defaults': '기본값', - 'admin.defaultSettings.title': '기본 사용자 설정', - 'admin.defaultSettings.description': '인스턴스 전체 기본값을 설정합니다. 설정을 변경하지 않은 사용자에게 이 값이 표시됩니다. 사용자의 변경 사항이 항상 우선합니다.', - 'admin.defaultSettings.saved': '기본값이 저장되었습니다', - 'admin.defaultSettings.reset': '기본 내장값으로 초기화', - 'admin.defaultSettings.resetToBuiltIn': '초기화', - 'admin.tabs.templates': '짐 목록 템플릿', - 'admin.packingTemplates.title': '짐 목록 템플릿', - 'admin.packingTemplates.subtitle': '여행을 위한 재사용 가능한 짐 목록을 만드세요', - 'admin.packingTemplates.create': '새 템플릿', - 'admin.packingTemplates.namePlaceholder': '템플릿 이름 (예: 해변 휴가)', - 'admin.packingTemplates.empty': '아직 템플릿이 없습니다', - 'admin.packingTemplates.items': '항목', - 'admin.packingTemplates.categories': '카테고리', - 'admin.packingTemplates.itemName': '항목 이름', - 'admin.packingTemplates.itemCategory': '카테고리', - 'admin.packingTemplates.categoryName': '카테고리 이름 (예: 의류)', - 'admin.packingTemplates.addCategory': '카테고리 추가', - 'admin.packingTemplates.created': '템플릿이 생성되었습니다', - 'admin.packingTemplates.deleted': '템플릿이 삭제되었습니다', - 'admin.packingTemplates.loadError': '템플릿 불러오기 실패', - 'admin.packingTemplates.createError': '템플릿 생성 실패', - 'admin.packingTemplates.deleteError': '템플릿 삭제 실패', - 'admin.packingTemplates.saveError': '저장 실패', - - // Addons - 'admin.tabs.addons': '애드온', - 'admin.addons.title': '애드온', - 'admin.addons.subtitle': '기능을 활성화 또는 비활성화하여 TREK 경험을 맞춤 설정하세요.', - 'admin.addons.catalog.packing.name': '목록', - 'admin.addons.catalog.packing.description': '여행을 위한 짐 목록 및 할 일 작업', - 'admin.addons.catalog.budget.name': '예산', - 'admin.addons.catalog.budget.description': '지출 추적 및 여행 예산 계획', - 'admin.addons.catalog.documents.name': '문서', - 'admin.addons.catalog.documents.description': '여행 서류 저장 및 관리', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': '캘린더 보기가 있는 개인 휴가 플래너', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': '방문한 나라와 여행 통계가 있는 세계 지도', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': '여행 계획을 위한 실시간 메모, 투표, 채팅', - 'admin.addons.catalog.memories.name': '사진 (Immich)', - 'admin.addons.catalog.memories.description': 'Immich 인스턴스를 통해 여행 사진 공유', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'AI 어시스턴트 통합을 위한 모델 컨텍스트 프로토콜', - 'admin.addons.subtitleBefore': '기능을 활성화 또는 비활성화하여 ', - 'admin.addons.subtitleAfter': ' 경험을 맞춤 설정하세요.', - 'admin.addons.enabled': '활성화됨', - 'admin.addons.disabled': '비활성화됨', - 'admin.addons.type.trip': '여행', - 'admin.addons.type.global': '전역', - 'admin.addons.type.integration': '통합', - 'admin.addons.tripHint': '각 여행 내 탭으로 사용 가능', - 'admin.addons.globalHint': '메인 내비게이션의 독립 섹션으로 사용 가능', - 'admin.addons.integrationHint': '전용 페이지가 없는 백엔드 서비스 및 API 통합', - 'admin.addons.toast.updated': '애드온이 업데이트되었습니다', - 'admin.addons.toast.error': '애드온 업데이트 실패', - 'admin.addons.noAddons': '사용 가능한 애드온이 없습니다', - // Weather info - 'admin.weather.title': '날씨 데이터', - 'admin.weather.badge': '2026년 3월 24일부터', - 'admin.weather.description': 'TREK은 날씨 데이터 소스로 Open-Meteo를 사용합니다. Open-Meteo는 무료 오픈 소스 날씨 서비스로 API 키가 필요 없습니다.', - 'admin.weather.forecast': '16일 예보', - 'admin.weather.forecastDesc': '이전: 5일 (OpenWeatherMap)', - 'admin.weather.climate': '과거 기후 데이터', - 'admin.weather.climateDesc': '16일 예보 이후 날짜의 85년 평균값', - 'admin.weather.requests': '하루 10,000 요청', - 'admin.weather.requestsDesc': '무료, API 키 불필요', - 'admin.weather.locationHint': '날씨는 각 날의 좌표가 있는 첫 번째 장소를 기준으로 합니다. 날에 배정된 장소가 없으면 장소 목록의 임의 장소가 참조로 사용됩니다.', - - // GitHub - 'admin.tabs.mcpTokens': 'MCP 접근', - 'admin.mcpTokens.title': 'MCP 접근', - 'admin.mcpTokens.subtitle': '모든 사용자의 OAuth 세션 및 API 토큰을 관리합니다', - 'admin.mcpTokens.sectionTitle': 'API 토큰', - 'admin.mcpTokens.owner': '소유자', - 'admin.mcpTokens.tokenName': '토큰 이름', - 'admin.mcpTokens.created': '생성일', - 'admin.mcpTokens.lastUsed': '마지막 사용', - 'admin.mcpTokens.never': '없음', - 'admin.mcpTokens.empty': '생성된 MCP 토큰이 없습니다', - 'admin.mcpTokens.deleteTitle': '토큰 삭제', - 'admin.mcpTokens.deleteMessage': '토큰이 즉시 취소됩니다. 사용자는 이 토큰을 통한 MCP 접근 권한을 잃게 됩니다.', - 'admin.mcpTokens.deleteSuccess': '토큰이 삭제되었습니다', - 'admin.mcpTokens.deleteError': '토큰 삭제 실패', - 'admin.mcpTokens.loadError': '토큰 불러오기 실패', - 'admin.oauthSessions.sectionTitle': 'OAuth 세션', - 'admin.oauthSessions.clientName': '클라이언트', - 'admin.oauthSessions.owner': '소유자', - 'admin.oauthSessions.scopes': '권한 범위', - 'admin.oauthSessions.created': '생성일', - 'admin.oauthSessions.empty': '활성 OAuth 세션이 없습니다', - 'admin.oauthSessions.revokeTitle': '세션 취소', - 'admin.oauthSessions.revokeMessage': 'OAuth 세션이 즉시 취소됩니다. 클라이언트는 MCP 접근 권한을 잃게 됩니다.', - 'admin.oauthSessions.revokeSuccess': '세션이 취소되었습니다', - 'admin.oauthSessions.revokeError': '세션 취소 실패', - 'admin.oauthSessions.loadError': 'OAuth 세션 불러오기 실패', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': '보안 및 관리 이벤트 (백업, 사용자, MFA, 설정).', - 'admin.audit.empty': '아직 감사 항목이 없습니다.', - 'admin.audit.refresh': '새로 고침', - 'admin.audit.loadMore': '더 불러오기', - 'admin.audit.showing': '{count}개 불러옴 · 총 {total}개', - 'admin.audit.col.time': '시간', - 'admin.audit.col.user': '사용자', - 'admin.audit.col.action': '작업', - 'admin.audit.col.resource': '리소스', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': '상세', - 'admin.github.title': '릴리스 히스토리', - 'admin.github.subtitle': '{repo}의 최신 업데이트', - 'admin.github.latest': '최신', - 'admin.github.prerelease': '사전 릴리스', - 'admin.github.showDetails': '상세 보기', - 'admin.github.hideDetails': '상세 숨기기', - 'admin.github.loadMore': '더 불러오기', - 'admin.github.loading': '불러오는 중...', - 'admin.github.error': '릴리스 불러오기 실패', - 'admin.github.by': '작성자', - 'admin.github.support': 'TREK 개발 지속에 도움이 됩니다', - - 'admin.update.available': '업데이트 사용 가능', - 'admin.update.text': 'TREK {version}이(가) 사용 가능합니다. 현재 {current}을(를) 실행 중입니다.', - 'admin.update.button': 'GitHub에서 보기', - 'admin.update.install': '업데이트 설치', - 'admin.update.confirmTitle': '업데이트를 설치할까요?', - 'admin.update.confirmText': 'TREK이 {current}에서 {version}으로 업데이트됩니다. 서버가 이후 자동으로 재시작됩니다.', - 'admin.update.dataInfo': '모든 데이터 (여행, 사용자, API 키, 업로드, Vacay, Atlas, 예산)가 보존됩니다.', - 'admin.update.warning': '재시작 중에 앱이 잠시 사용할 수 없게 됩니다.', - 'admin.update.confirm': '지금 업데이트', - 'admin.update.installing': '업데이트 중…', - 'admin.update.success': '업데이트 완료! 서버가 재시작 중입니다…', - 'admin.update.failed': '업데이트 실패', - 'admin.update.backupHint': '업데이트 전에 백업을 생성하는 것을 권장합니다.', - 'admin.update.backupLink': '백업으로 이동', - 'admin.update.howTo': '업데이트 방법', - 'admin.update.dockerText': 'TREK 인스턴스가 Docker에서 실행 중입니다. {version}으로 업데이트하려면 서버에서 다음 명령을 실행하세요:', - 'admin.update.reloadHint': '잠시 후 페이지를 새로 고침하세요.', - - // Vacay addon - 'vacay.subtitle': '휴가 일수를 계획하고 관리하세요', - 'vacay.settings': '설정', - 'vacay.year': '연도', - 'vacay.addYear': '다음 연도 추가', - 'vacay.addPrevYear': '이전 연도 추가', - 'vacay.removeYear': '연도 제거', - 'vacay.removeYearConfirm': '{year}을(를) 제거할까요?', - 'vacay.removeYearHint': '이 연도의 모든 휴가 항목 및 회사 공휴일이 영구 삭제됩니다.', - 'vacay.remove': '제거', - 'vacay.persons': '인원', - 'vacay.noPersons': '추가된 인원이 없습니다', - 'vacay.addPerson': '인원 추가', - 'vacay.editPerson': '인원 편집', - 'vacay.removePerson': '인원 제거', - 'vacay.removePersonConfirm': '{name}을(를) 제거할까요?', - 'vacay.removePersonHint': '이 인원의 모든 휴가 항목이 영구 삭제됩니다.', - 'vacay.personName': '이름', - 'vacay.personNamePlaceholder': '이름 입력', - 'vacay.color': '색상', - 'vacay.add': '추가', - 'vacay.legend': '범례', - 'vacay.publicHoliday': '공휴일', - 'vacay.companyHoliday': '회사 휴일', - 'vacay.weekend': '주말', - 'vacay.modeVacation': '휴가', - 'vacay.modeCompany': '회사 휴일', - 'vacay.entitlement': '휴가 일수', - 'vacay.entitlementDays': '일', - 'vacay.used': '사용', - 'vacay.remaining': '남음', - 'vacay.carriedOver': '{year}에서 이월', - 'vacay.blockWeekends': '주말 차단', - 'vacay.blockWeekendsHint': '주말에 휴가 항목 추가를 방지합니다', - 'vacay.weekendDays': '주말 요일', - 'vacay.mon': '월', - 'vacay.tue': '화', - 'vacay.wed': '수', - 'vacay.thu': '목', - 'vacay.fri': '금', - 'vacay.sat': '토', - 'vacay.sun': '일', - 'vacay.publicHolidays': '공휴일', - 'vacay.publicHolidaysHint': '캘린더에 공휴일을 표시합니다', - 'vacay.selectCountry': '국가 선택', - 'vacay.selectRegion': '지역 선택 (선택)', - 'vacay.addCalendar': '캘린더 추가', - 'vacay.calendarLabel': '레이블 (선택)', - 'vacay.calendarColor': '색상', - 'vacay.noCalendars': '아직 공휴일 캘린더가 없습니다', - 'vacay.companyHolidays': '회사 휴일', - 'vacay.companyHolidaysHint': '회사 전체 휴일 표시를 허용합니다', - 'vacay.companyHolidaysNoDeduct': '회사 휴일은 휴가 일수에서 차감되지 않습니다.', - 'vacay.weekStart': '주 시작 요일', - 'vacay.weekStartHint': '캘린더 주가 월요일 또는 일요일에 시작할지 선택하세요', - 'vacay.carryOver': '이월', - 'vacay.carryOverHint': '남은 휴가 일수를 다음 연도로 자동 이월합니다', - 'vacay.sharing': '공유', - 'vacay.sharingHint': '다른 TREK 사용자와 휴가 계획을 공유합니다', - 'vacay.owner': '소유자', - 'vacay.shareEmailPlaceholder': 'TREK 사용자 이메일', - 'vacay.shareSuccess': '계획이 성공적으로 공유되었습니다', - 'vacay.shareError': '계획 공유 실패', - 'vacay.dissolve': '퓨전 해제', - 'vacay.dissolveHint': '캘린더를 다시 분리합니다. 항목은 유지됩니다.', - 'vacay.dissolveAction': '해제', - 'vacay.dissolved': '캘린더가 분리되었습니다', - 'vacay.fusedWith': '퓨전됨', - 'vacay.you': '나', - 'vacay.noData': '데이터 없음', - 'vacay.changeColor': '색상 변경', - 'vacay.inviteUser': '사용자 초대', - 'vacay.inviteHint': '다른 TREK 사용자를 초대하여 통합 휴가 캘린더를 공유하세요.', - 'vacay.selectUser': '사용자 선택', - 'vacay.sendInvite': '초대 전송', - 'vacay.inviteSent': '초대가 전송되었습니다', - 'vacay.inviteError': '초대 전송 실패', - 'vacay.pending': '대기 중', - 'vacay.noUsersAvailable': '사용 가능한 사용자가 없습니다', - 'vacay.accept': '수락', - 'vacay.decline': '거절', - 'vacay.acceptFusion': '수락 및 퓨전', - 'vacay.inviteTitle': '퓨전 요청', - 'vacay.inviteWantsToFuse': '귀하와 휴가 캘린더를 공유하고 싶어 합니다.', - 'vacay.fuseInfo1': '두 사람 모두 하나의 공유 캘린더에서 모든 휴가 항목을 볼 수 있습니다.', - 'vacay.fuseInfo2': '양측 모두 서로의 항목을 만들고 편집할 수 있습니다.', - 'vacay.fuseInfo3': '양측 모두 항목을 삭제하고 휴가 일수를 변경할 수 있습니다.', - 'vacay.fuseInfo4': '공휴일 및 회사 휴일 등의 설정이 공유됩니다.', - 'vacay.fuseInfo5': '어느 쪽이든 언제든지 퓨전을 해제할 수 있습니다. 항목은 보존됩니다.', - 'nav.myTrips': '내 여행', - - // Atlas addon - 'atlas.subtitle': '전 세계의 나의 여행 발자취', - 'atlas.countries': '국가', - 'atlas.trips': '여행', - 'atlas.places': '장소', - 'atlas.unmark': '제거', - 'atlas.confirmMark': '이 나라를 방문한 곳으로 표시할까요?', - 'atlas.confirmUnmark': '방문 목록에서 이 나라를 제거할까요?', - 'atlas.confirmUnmarkRegion': '방문 목록에서 이 지역을 제거할까요?', - 'atlas.markVisited': '방문으로 표시', - 'atlas.markVisitedHint': '방문 목록에 이 나라를 추가합니다', - 'atlas.markRegionVisitedHint': '방문 목록에 이 지역을 추가합니다', - 'atlas.addToBucket': '버킷 리스트에 추가', - 'atlas.addPoi': '장소 추가', - 'atlas.searchCountry': '국가 검색...', - 'atlas.bucketNamePlaceholder': '이름 (국가, 도시, 장소...)', - 'atlas.month': '월', - 'atlas.year': '연도', - 'atlas.addToBucketHint': '방문하고 싶은 장소로 저장', - 'atlas.bucketWhen': '방문 예정 시기는 언제인가요?', - 'atlas.statsTab': '통계', - 'atlas.bucketTab': '버킷 리스트', - 'atlas.addBucket': '버킷 리스트에 추가', - 'atlas.bucketNotesPlaceholder': '메모 (선택)', - 'atlas.bucketEmpty': '버킷 리스트가 비어 있습니다', - 'atlas.bucketEmptyHint': '꿈꾸는 방문지를 추가하세요', - 'atlas.days': '일', - 'atlas.visitedCountries': '방문한 나라', - 'atlas.cities': '도시', - 'atlas.noData': '아직 여행 데이터가 없습니다', - 'atlas.noDataHint': '여행을 만들고 장소를 추가하여 세계 지도를 확인하세요', - 'atlas.lastTrip': '마지막 여행', - 'atlas.nextTrip': '다음 여행', - 'atlas.daysLeft': '일 남음', - 'atlas.streak': '연속', - 'atlas.years': '년', - 'atlas.yearInRow': '연 연속', - 'atlas.yearsInRow': '년 연속', - 'atlas.tripIn': '에서 여행', - 'atlas.tripsIn': '에서 여행', - 'atlas.since': '부터', - 'atlas.europe': '유럽', - 'atlas.asia': '아시아', - 'atlas.northAmerica': '북미', - 'atlas.southAmerica': '남미', - 'atlas.africa': '아프리카', - 'atlas.oceania': '오세아니아', - 'atlas.other': '기타', - 'atlas.firstVisit': '첫 번째 여행', - 'atlas.lastVisitLabel': '마지막 여행', - 'atlas.tripSingular': '여행', - 'atlas.tripPlural': '여행', - 'atlas.placeVisited': '방문한 장소', - 'atlas.placesVisited': '방문한 장소', - - // Trip Planner - 'trip.tabs.plan': '계획', - 'trip.tabs.transports': '교통', - 'trip.tabs.reservations': '예약', - 'trip.tabs.reservationsShort': '예약', - 'trip.tabs.packing': '짐 목록', - 'trip.tabs.packingShort': '짐', - 'trip.tabs.lists': '목록', - 'trip.tabs.listsShort': '목록', - 'trip.tabs.budget': '예산', - 'trip.tabs.files': '파일', - 'trip.loading': '여행 불러오는 중...', - 'trip.loadingPhotos': '장소 사진 불러오는 중...', - 'trip.mobilePlan': '계획', - 'trip.mobilePlaces': '장소', - 'trip.toast.placeUpdated': '장소가 업데이트되었습니다', - 'trip.toast.placeAdded': '장소가 추가되었습니다', - 'trip.toast.placeDeleted': '장소가 삭제되었습니다', - 'trip.toast.selectDay': '먼저 날을 선택하세요', - 'trip.toast.assignedToDay': '장소가 날에 배정되었습니다', - 'trip.toast.reorderError': '순서 변경 실패', - 'trip.toast.reservationUpdated': '예약이 업데이트되었습니다', - 'trip.toast.reservationAdded': '예약이 추가되었습니다', - 'trip.toast.deleted': '삭제됨', - 'trip.confirm.deletePlace': '이 장소를 삭제할까요?', - 'trip.confirm.deletePlaces': '{count}개 장소를 삭제할까요?', - 'trip.toast.placesDeleted': '{count}개 장소가 삭제되었습니다', - - // Day Plan Sidebar - 'dayplan.emptyDay': '이 날에 계획된 장소가 없습니다', - 'dayplan.cannotReorderTransport': '고정된 시간이 있는 예약은 순서를 변경할 수 없습니다', - 'dayplan.confirmRemoveTimeTitle': '시간을 제거할까요?', - 'dayplan.confirmRemoveTimeBody': '이 장소에 고정된 시간 ({time})이 있습니다. 이동하면 시간이 제거되고 자유 정렬이 허용됩니다.', - 'dayplan.confirmRemoveTimeAction': '시간 제거 및 이동', - 'dayplan.cannotDropOnTimed': '시간이 고정된 항목 사이에 배치할 수 없습니다', - 'dayplan.cannotBreakChronology': '이 작업은 시간 고정 항목과 예약의 시간 순서를 깨뜨립니다', - 'dayplan.addNote': '메모 추가', - 'dayplan.expandAll': '모든 날 펼치기', - 'dayplan.collapseAll': '모든 날 접기', - 'dayplan.editNote': '메모 편집', - 'dayplan.noteAdd': '메모 추가', - 'dayplan.noteEdit': '메모 편집', - 'dayplan.noteTitle': '메모', - 'dayplan.noteSubtitle': '일별 메모', - 'dayplan.totalCost': '총 비용', - 'dayplan.days': '일', - 'dayplan.dayN': '{n}일차', - 'dayplan.calculating': '계산 중...', - 'dayplan.route': '경로', - 'dayplan.optimize': '최적화', - 'dayplan.optimized': '경로가 최적화되었습니다', - 'dayplan.routeError': '경로 계산 실패', - 'dayplan.toast.needTwoPlaces': '경로 최적화에는 최소 두 개의 장소가 필요합니다', - 'dayplan.toast.routeOptimized': '경로가 최적화되었습니다', - 'dayplan.toast.noGeoPlaces': '경로 계산을 위한 좌표가 있는 장소가 없습니다', - 'dayplan.confirmed': '확정됨', - 'dayplan.pendingRes': '대기 중', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': '일별 계획을 PDF로 내보내기', - 'dayplan.pdfError': 'PDF 내보내기 실패', - - // Places Sidebar - 'places.addPlace': '장소/활동 추가', - 'places.importFile': '파일 가져오기', - 'places.sidebarDrop': '끌어다 가져오기', - 'places.importFileHint': 'Google My Maps, Google Earth 또는 GPS 추적기 등의 .gpx, .kml, .kmz 파일을 가져옵니다.', - 'places.importFileDropHere': '파일을 선택하거나 여기에 끌어다 놓으세요', - 'places.importFileDropActive': '파일을 놓으세요', - 'places.importFileUnsupported': '지원하지 않는 파일 형식입니다. .gpx, .kml 또는 .kmz를 사용하세요.', - 'places.importFileTooLarge': '파일이 너무 큽니다. 최대 업로드 크기는 {maxMb} MB입니다.', - 'places.importFileError': '가져오기 실패', - 'places.importAllSkipped': '모든 장소가 이미 여행에 포함되어 있습니다.', - 'places.gpxImported': 'GPX에서 {count}개 장소를 가져왔습니다', - 'places.gpxImportTypes': '무엇을 가져올까요?', - 'places.gpxImportWaypoints': '웨이포인트', - 'places.gpxImportRoutes': '경로', - 'places.gpxImportTracks': '트랙 (경로 형상 포함)', - 'places.gpxImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.', - 'places.kmlImportTypes': '무엇을 가져올까요?', - 'places.kmlImportPoints': '포인트 (Placemarks)', - 'places.kmlImportPaths': '경로 (LineStrings)', - 'places.kmlImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.', - 'places.selectionCount': '{count}개 선택됨', - 'places.deleteSelected': '선택 항목 삭제', - 'places.kmlKmzImported': 'KMZ/KML에서 {count}개 장소를 가져왔습니다', - 'places.urlResolved': 'URL에서 장소를 가져왔습니다', - 'places.importList': '목록 가져오기', - 'places.kmlKmzSummaryValues': '총 Placemarks: {total} · 가져옴: {created} · 건너뜀: {skipped}', - 'places.importGoogleList': 'Google 목록', - 'places.importNaverList': '네이버 목록', - 'places.googleListHint': '공유된 Google Maps 목록 링크를 붙여넣어 모든 장소를 가져옵니다.', - 'places.googleListImported': '"{list}"에서 {count}개 장소를 가져왔습니다', - 'places.googleListError': 'Google Maps 목록 가져오기 실패', - 'places.naverListHint': '공유된 네이버 지도 목록 링크를 붙여넣어 모든 장소를 가져옵니다.', - 'places.naverListImported': '"{list}"에서 {count}개 장소를 가져왔습니다', - 'places.naverListError': '네이버 지도 목록 가져오기 실패', - 'places.viewDetails': '상세 보기', - 'places.assignToDay': '어느 날에 추가할까요?', - 'places.all': '전체', - 'places.unplanned': '미계획', - 'places.filterTracks': '트랙', - 'places.search': '장소 검색...', - 'places.allCategories': '모든 카테고리', - 'places.categoriesSelected': '카테고리', - 'places.clearFilter': '필터 지우기', - 'places.count': '장소 {count}개', - 'places.countSingular': '장소 1개', - 'places.allPlanned': '모든 장소가 계획되었습니다', - 'places.noneFound': '장소를 찾을 수 없습니다', - 'places.editPlace': '장소 편집', - 'places.formName': '이름', - 'places.formNamePlaceholder': '예: 에펠탑', - 'places.formDescription': '설명', - 'places.formDescriptionPlaceholder': '간단한 설명...', - 'places.formAddress': '주소', - 'places.formAddressPlaceholder': '도로, 도시, 국가', - 'places.formLat': '위도 (예: 48.8566)', - 'places.formLng': '경도 (예: 2.3522)', - 'places.formCategory': '카테고리', - 'places.noCategory': '카테고리 없음', - 'places.categoryNamePlaceholder': '카테고리 이름', - 'places.formTime': '시간', - 'places.startTime': '시작', - 'places.endTime': '종료', - 'places.endTimeBeforeStart': '종료 시간이 시작 시간보다 앞입니다', - 'places.timeCollision': '시간 겹침:', - 'places.formWebsite': '웹사이트', - 'places.formNotes': '메모', - 'places.formNotesPlaceholder': '개인 메모...', - 'places.formReservation': '예약', - 'places.reservationNotesPlaceholder': '예약 메모, 확인 번호...', - 'places.mapsSearchPlaceholder': '장소 검색...', - 'places.mapsSearchError': '장소 검색 실패.', - 'places.loadingDetails': '장소 상세 정보 불러오는 중…', - 'places.osmHint': 'OpenStreetMap 검색 사용 중 (사진, 영업 시간, 평점 없음). 전체 정보를 위해 설정에서 Google API 키를 추가하세요.', - 'places.osmActive': 'OpenStreetMap으로 검색 중 (사진, 평점, 영업 시간 없음). 향상된 데이터를 위해 설정에서 Google API 키를 추가하세요.', - 'places.categoryCreateError': '카테고리 생성 실패', - 'places.nameRequired': '이름을 입력하세요', - 'places.saveError': '저장 실패', - // Place Inspector - 'inspector.opened': '영업 중', - 'inspector.closed': '영업 종료', - 'inspector.openingHours': '영업 시간', - 'inspector.showHours': '영업 시간 보기', - 'inspector.files': '파일', - 'inspector.filesCount': '{count}개 파일', - 'inspector.remove': '제거', - 'inspector.removeFromDay': '날에서 제거', - 'inspector.addToDay': '날에 추가', - 'inspector.confirmedRes': '확정된 예약', - 'inspector.pendingRes': '대기 중인 예약', - 'inspector.google': 'Google Maps에서 열기', - 'inspector.website': '웹사이트 열기', - 'inspector.addRes': '예약', - 'inspector.editRes': '예약 편집', - 'inspector.participants': '참가자', - 'inspector.trackStats': '트랙 통계', - - // Reservations - 'reservations.title': '예약', - 'reservations.empty': '아직 예약이 없습니다', - 'reservations.emptyHint': '항공, 호텔 등의 예약을 추가하세요', - 'reservations.add': '예약 추가', - 'reservations.addManual': '직접 예약', - 'reservations.placeHint': '팁: 예약은 일별 계획과 연결하기 위해 장소에서 직접 만드는 것이 가장 좋습니다.', - 'reservations.confirmed': '확정됨', - 'reservations.pending': '대기 중', - 'reservations.summary': '{confirmed}개 확정, {pending}개 대기 중', - 'reservations.fromPlan': '계획에서', - 'reservations.showFiles': '파일 보기', - 'reservations.editTitle': '예약 편집', - 'reservations.status': '상태', - 'reservations.datetime': '날짜 및 시간', - 'reservations.startTime': '시작 시간', - 'reservations.endTime': '종료 시간', - 'reservations.date': '날짜', - 'reservations.time': '시간', - 'reservations.timeAlt': '시간 (대안, 예: 19:30)', - 'reservations.notes': '메모', - 'reservations.notesPlaceholder': '추가 메모...', - 'reservations.meta.airline': '항공사', - 'reservations.meta.flightNumber': '항공편 번호', - 'reservations.meta.from': '출발', - 'reservations.meta.to': '도착', - 'reservations.needsReview': '검토 필요', - 'reservations.needsReviewHint': '공항이 자동으로 매칭되지 않았습니다 — 위치를 확인해 주세요.', - 'reservations.searchLocation': '역, 항구, 주소 검색…', - 'airport.searchPlaceholder': '공항 코드 또는 도시 (예: ICN)', - 'map.connections': '연결', - 'map.showConnections': '예약 경로 표시', - 'map.hideConnections': '예약 경로 숨기기', - 'reservations.meta.trainNumber': '열차 번호', - 'reservations.meta.platform': '플랫폼', - 'reservations.meta.seat': '좌석', - 'reservations.meta.checkIn': '체크인', - 'reservations.meta.checkInUntil': '체크인 마감', - 'reservations.meta.checkOut': '체크아웃', - 'reservations.meta.linkAccommodation': '숙박', - 'reservations.meta.pickAccommodation': '숙박 연결', - 'reservations.meta.noAccommodation': '없음', - 'reservations.meta.hotelPlace': '숙박', - 'reservations.meta.pickHotel': '숙박 선택', - 'reservations.meta.fromDay': '부터', - 'reservations.meta.toDay': '까지', - 'reservations.meta.selectDay': '날 선택', - 'reservations.type.flight': '항공', - 'reservations.type.hotel': '숙박', - 'reservations.type.restaurant': '레스토랑', - 'reservations.type.train': '기차', - 'reservations.type.car': '차량', - 'reservations.type.cruise': '크루즈', - 'reservations.type.event': '이벤트', - 'reservations.type.tour': '투어', - 'reservations.type.other': '기타', - 'reservations.confirm.delete': '예약 "{name}"을(를) 삭제할까요?', - 'reservations.confirm.deleteTitle': '예약을 삭제할까요?', - 'reservations.confirm.deleteBody': '"{name}"이(가) 영구 삭제됩니다.', - 'reservations.toast.updated': '예약이 업데이트되었습니다', - 'reservations.toast.removed': '예약이 삭제되었습니다', - 'reservations.toast.fileUploaded': '파일이 업로드되었습니다', - 'reservations.toast.uploadError': '업로드 실패', - 'reservations.newTitle': '새 예약', - 'reservations.bookingType': '예약 유형', - 'reservations.titleLabel': '제목', - 'reservations.titlePlaceholder': '예: 대한항공 KE123, 호텔 신라, ...', - 'reservations.locationAddress': '위치 / 주소', - 'reservations.locationPlaceholder': '주소, 공항, 호텔...', - 'reservations.confirmationCode': '예약 코드', - 'reservations.confirmationPlaceholder': '예: ABC12345', - 'reservations.day': '날', - 'reservations.noDay': '날 없음', - 'reservations.place': '장소', - 'reservations.noPlace': '장소 없음', - 'reservations.pendingSave': '저장될 예정…', - 'reservations.uploading': '업로드 중...', - 'reservations.attachFile': '파일 첨부', - 'reservations.linkExisting': '기존 파일 연결', - 'reservations.toast.saveError': '저장 실패', - 'reservations.toast.updateError': '업데이트 실패', - 'reservations.toast.deleteError': '삭제 실패', - 'reservations.confirm.remove': '"{name}"의 예약을 제거할까요?', - 'reservations.linkAssignment': '날 배정에 연결', - 'reservations.pickAssignment': '계획에서 배정을 선택하세요...', - 'reservations.noAssignment': '연결 없음 (독립)', - 'reservations.price': '가격', - 'reservations.budgetCategory': '예산 카테고리', - 'reservations.budgetCategoryPlaceholder': '예: 교통, 숙박', - 'reservations.budgetCategoryAuto': '자동 (예약 유형에서)', - 'reservations.budgetHint': '저장 시 예산 항목이 자동으로 생성됩니다.', - 'reservations.departureDate': '출발', - 'reservations.arrivalDate': '도착', - 'reservations.departureTime': '출발 시간', - 'reservations.arrivalTime': '도착 시간', - 'reservations.pickupDate': '픽업', - 'reservations.returnDate': '반납', - 'reservations.pickupTime': '픽업 시간', - 'reservations.returnTime': '반납 시간', - 'reservations.endDate': '종료 날짜', - 'reservations.meta.departureTimezone': '출발 시간대', - 'reservations.meta.arrivalTimezone': '도착 시간대', - 'reservations.span.departure': '출발', - 'reservations.span.arrival': '도착', - 'reservations.span.inTransit': '이동 중', - 'reservations.span.pickup': '픽업', - 'reservations.span.return': '반납', - 'reservations.span.active': '활성', - 'reservations.span.start': '시작', - 'reservations.span.end': '종료', - 'reservations.span.ongoing': '진행 중', - 'reservations.validation.endBeforeStart': '종료 날짜/시간은 시작 날짜/시간 이후여야 합니다', - 'reservations.addBooking': '예약 추가', - - // Budget - 'budget.title': '예산', - 'budget.exportCsv': 'CSV 내보내기', - 'budget.emptyTitle': '아직 예산이 없습니다', - 'budget.emptyText': '카테고리와 항목을 만들어 여행 예산을 계획하세요', - 'budget.emptyPlaceholder': '카테고리 이름을 입력하세요...', - 'budget.createCategory': '카테고리 만들기', - 'budget.category': '카테고리', - 'budget.categoryName': '카테고리 이름', - 'budget.table.name': '이름', - 'budget.table.total': '합계', - 'budget.table.persons': '인원', - 'budget.table.days': '일수', - 'budget.table.perPerson': '1인당', - 'budget.table.perDay': '일당', - 'budget.table.perPersonDay': '1인/일', - 'budget.table.note': '메모', - 'budget.table.date': '날짜', - 'budget.newEntry': '새 항목', - 'budget.defaultEntry': '새 항목', - 'budget.defaultCategory': '새 카테고리', - 'budget.total': '합계', - 'budget.totalBudget': '총 예산', - 'budget.byCategory': '카테고리별', - 'budget.editTooltip': '클릭하여 편집', - 'budget.linkedToReservation': '예약에 연결됨 — 이름은 예약에서 편집하세요', - 'budget.confirm.deleteCategory': '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?', - 'budget.deleteCategory': '카테고리 삭제', - 'budget.perPerson': '1인당', - 'budget.paid': '지불됨', - 'budget.open': '미결', - 'budget.noMembers': '배정된 멤버가 없습니다', - 'budget.settlement': '정산', - 'budget.settlementInfo': '예산 항목의 멤버 아바타를 클릭하면 녹색으로 표시됩니다 — 해당 멤버가 지불했음을 의미합니다. 그러면 정산에서 누가 누구에게 얼마를 지불해야 하는지 보여줍니다.', - 'budget.netBalances': '순 잔액', - - // Files - 'files.title': '파일', - 'files.pageTitle': '파일 및 서류', - 'files.subtitle': '{trip}의 파일 {count}개', - 'files.download': '다운로드', - 'files.openError': '파일을 열 수 없습니다', - 'files.downloadPdf': 'PDF 다운로드', - 'files.count': '파일 {count}개', - 'files.countSingular': '파일 1개', - 'files.uploaded': '{count}개 업로드됨', - 'files.uploadError': '업로드 실패', - 'files.dropzone': '여기에 파일을 놓으세요', - 'files.dropzoneHint': '또는 클릭하여 탐색', - 'files.allowedTypes': '이미지, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · 최대 50 MB', - 'files.uploading': '업로드 중...', - 'files.filterAll': '전체', - 'files.filterPdf': 'PDF', - 'files.filterImages': '이미지', - 'files.filterDocs': '문서', - 'files.filterCollab': 'Collab 메모', - 'files.sourceCollab': 'Collab 메모에서', - 'files.empty': '아직 파일이 없습니다', - 'files.emptyHint': '파일을 업로드하여 여행에 첨부하세요', - 'files.openTab': '새 탭에서 열기', - 'files.confirm.delete': '이 파일을 삭제할까요?', - 'files.toast.deleted': '파일이 삭제되었습니다', - 'files.toast.deleteError': '파일 삭제 실패', - 'files.sourcePlan': '일별 계획', - 'files.sourceBooking': '예약', - 'files.sourceTransport': '교통', - 'files.attach': '첨부', - 'files.pasteHint': '클립보드에서 이미지를 붙여넣을 수도 있습니다 (Ctrl+V)', - 'files.trash': '휴지통', - 'files.trashEmpty': '휴지통이 비어 있습니다', - 'files.emptyTrash': '휴지통 비우기', - 'files.restore': '복원', - 'files.star': '즐겨찾기', - 'files.unstar': '즐겨찾기 해제', - 'files.assign': '배정', - 'files.assignTitle': '파일 배정', - 'files.assignPlace': '장소', - 'files.assignBooking': '예약', - 'files.assignTransport': '교통', - 'files.unassigned': '미배정', - 'files.unlink': '연결 해제', - 'files.toast.trashed': '휴지통으로 이동됨', - 'files.toast.restored': '파일이 복원되었습니다', - 'files.toast.trashEmptied': '휴지통이 비워졌습니다', - 'files.toast.assigned': '파일이 배정되었습니다', - 'files.toast.assignError': '배정 실패', - 'files.toast.restoreError': '복원 실패', - 'files.confirm.permanentDelete': '이 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.', - 'files.confirm.emptyTrash': '휴지통의 모든 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.', - 'files.noteLabel': '메모', - 'files.notePlaceholder': '메모 추가...', - - // Packing - 'packing.title': '짐 목록', - 'packing.empty': '짐 목록이 비어 있습니다', - 'packing.import': '가져오기', - 'packing.importTitle': '짐 목록 가져오기', - 'packing.importHint': '한 줄에 하나의 항목. 형식: 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택)', - 'packing.importPlaceholder': '위생, 칫솔\n의류, 티셔츠, 200\n서류, 여권, , 기내 수하물\n전자기기, 충전기, 50, 캐리어, checked', - 'packing.importCsv': 'CSV/TXT 불러오기', - 'packing.importAction': '{count}개 가져오기', - 'packing.importSuccess': '{count}개 항목을 가져왔습니다', - 'packing.importError': '가져오기 실패', - 'packing.importEmpty': '가져올 항목이 없습니다', - 'packing.progress': '{total}개 중 {packed}개 완료 ({percent}%)', - 'packing.clearChecked': '체크된 {count}개 제거', - 'packing.clearCheckedShort': '{count}개 제거', - 'packing.suggestions': '제안', - 'packing.suggestionsTitle': '제안 추가', - 'packing.allSuggested': '모든 제안이 추가되었습니다', - 'packing.allPacked': '모두 완료!', - 'packing.addPlaceholder': '새 항목 추가...', - 'packing.categoryPlaceholder': '카테고리...', - 'packing.filterAll': '전체', - 'packing.filterOpen': '미완료', - 'packing.filterDone': '완료', - 'packing.emptyTitle': '짐 목록이 비어 있습니다', - 'packing.emptyHint': '항목을 추가하거나 제안을 사용하세요', - 'packing.emptyFiltered': '이 필터와 일치하는 항목이 없습니다', - 'packing.menuRename': '이름 변경', - 'packing.menuCheckAll': '전체 체크', - 'packing.menuUncheckAll': '전체 체크 해제', - 'packing.menuDeleteCat': '카테고리 삭제', - 'packing.noMembers': '여행 멤버가 없습니다', - 'packing.addItem': '항목 추가', - 'packing.addItemPlaceholder': '항목 이름...', - 'packing.addCategory': '카테고리 추가', - 'packing.newCategoryPlaceholder': '카테고리 이름 (예: 의류)', - 'packing.applyTemplate': '템플릿 적용', - 'packing.template': '템플릿', - 'packing.templateApplied': '템플릿에서 {count}개 항목이 추가되었습니다', - 'packing.templateError': '템플릿 적용 실패', - 'packing.saveAsTemplate': '템플릿으로 저장', - 'packing.templateName': '템플릿 이름', - 'packing.templateSaved': '짐 목록이 템플릿으로 저장되었습니다', - 'packing.bags': '가방', - 'packing.noBag': '미배정', - 'packing.totalWeight': '총 무게', - 'packing.bagName': '가방 이름...', - 'packing.addBag': '가방 추가', - 'packing.changeCategory': '카테고리 변경', - 'packing.confirm.clearChecked': '체크된 {count}개 항목을 제거할까요?', - 'packing.confirm.deleteCat': '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?', - 'packing.defaultCategory': '기타', - 'packing.toast.saveError': '저장 실패', - 'packing.toast.deleteError': '삭제 실패', - 'packing.toast.renameError': '이름 변경 실패', - 'packing.toast.addError': '추가 실패', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: '여권', category: '서류' }, - { name: '신분증', category: '서류' }, - { name: '여행자 보험', category: '서류' }, - { name: '항공권', category: '서류' }, - { name: '신용카드', category: '금융' }, - { name: '현금', category: '금융' }, - { name: '비자', category: '서류' }, - { name: '티셔츠', category: '의류' }, - { name: '바지', category: '의류' }, - { name: '속옷', category: '의류' }, - { name: '양말', category: '의류' }, - { name: '재킷', category: '의류' }, - { name: '잠옷', category: '의류' }, - { name: '수영복', category: '의류' }, - { name: '우비', category: '의류' }, - { name: '편한 신발', category: '의류' }, - { name: '칫솔', category: '세면도구' }, - { name: '치약', category: '세면도구' }, - { name: '샴푸', category: '세면도구' }, - { name: '데오도란트', category: '세면도구' }, - { name: '자외선 차단제', category: '세면도구' }, - { name: '면도기', category: '세면도구' }, - { name: '충전기', category: '전자기기' }, - { name: '보조 배터리', category: '전자기기' }, - { name: '헤드폰', category: '전자기기' }, - { name: '여행용 어댑터', category: '전자기기' }, - { name: '카메라', category: '전자기기' }, - { name: '진통제', category: '건강' }, - { name: '반창고', category: '건강' }, - { name: '소독제', category: '건강' }, - ], - - // Members / Sharing - 'members.shareTrip': '여행 공유', - 'members.inviteUser': '사용자 초대', - 'members.selectUser': '사용자 선택…', - 'members.invite': '초대', - 'members.allHaveAccess': '모든 사용자가 이미 접근 권한을 가지고 있습니다.', - 'members.access': '접근', - 'members.person': '명', - 'members.persons': '명', - 'members.you': '나', - 'members.owner': '소유자', - 'members.leaveTrip': '여행 떠나기', - 'members.removeAccess': '접근 권한 제거', - 'members.confirmLeave': '여행을 떠날까요? 접근 권한을 잃게 됩니다.', - 'members.confirmRemove': '이 사용자의 접근 권한을 제거할까요?', - 'members.loadError': '멤버 불러오기 실패', - 'members.added': '추가됨', - 'members.addError': '추가 실패', - 'members.removed': '멤버가 제거되었습니다', - 'members.removeError': '제거 실패', - - // Categories (Admin) - 'categories.title': '카테고리', - 'categories.subtitle': '장소 카테고리 관리', - 'categories.new': '새 카테고리', - 'categories.empty': '아직 카테고리가 없습니다', - 'categories.namePlaceholder': '카테고리 이름', - 'categories.icon': '아이콘', - 'categories.color': '색상', - 'categories.customColor': '사용자 지정 색상 선택', - 'categories.preview': '미리보기', - 'categories.defaultName': '카테고리', - 'categories.update': '업데이트', - 'categories.create': '생성', - 'categories.confirm.delete': '카테고리를 삭제할까요? 이 카테고리의 장소는 삭제되지 않습니다.', - 'categories.toast.loadError': '카테고리 불러오기 실패', - 'categories.toast.nameRequired': '이름을 입력하세요', - 'categories.toast.updated': '카테고리가 업데이트되었습니다', - 'categories.toast.created': '카테고리가 생성되었습니다', - 'categories.toast.saveError': '저장 실패', - 'categories.toast.deleted': '카테고리가 삭제되었습니다', - 'categories.toast.deleteError': '삭제 실패', - - // Backup (Admin) - 'backup.title': '데이터 백업', - 'backup.subtitle': '데이터베이스 및 모든 업로드된 파일', - 'backup.refresh': '새로 고침', - 'backup.upload': '백업 업로드', - 'backup.uploading': '업로드 중…', - 'backup.create': '백업 만들기', - 'backup.creating': '생성 중…', - 'backup.empty': '아직 백업이 없습니다', - 'backup.createFirst': '첫 번째 백업 만들기', - 'backup.download': '다운로드', - 'backup.restore': '복원', - 'backup.confirm.restore': '백업 "{name}"을(를) 복원할까요?\n\n현재 모든 데이터가 백업으로 교체됩니다.', - 'backup.confirm.uploadRestore': '백업 파일 "{name}"을(를) 업로드하고 복원할까요?\n\n현재 모든 데이터가 덮어쓰여집니다.', - 'backup.confirm.delete': '백업 "{name}"을(를) 삭제할까요?', - 'backup.toast.loadError': '백업 불러오기 실패', - 'backup.toast.created': '백업이 성공적으로 생성되었습니다', - 'backup.toast.createError': '백업 생성 실패', - 'backup.toast.restored': '백업이 복원되었습니다. 페이지가 새로 고침됩니다…', - 'backup.toast.restoreError': '복원 실패', - 'backup.toast.uploadError': '업로드 실패', - 'backup.toast.deleted': '백업이 삭제되었습니다', - 'backup.toast.deleteError': '삭제 실패', - 'backup.toast.downloadError': '다운로드 실패', - 'backup.toast.settingsSaved': '자동 백업 설정이 저장되었습니다', - 'backup.toast.settingsError': '설정 저장 실패', - 'backup.auto.title': '자동 백업', - 'backup.auto.subtitle': '일정에 따른 자동 백업', - 'backup.auto.enable': '자동 백업 활성화', - 'backup.auto.enableHint': '선택한 일정에 따라 자동으로 백업이 생성됩니다', - 'backup.auto.interval': '간격', - 'backup.auto.hour': '실행 시간', - 'backup.auto.hourHint': '서버 현지 시간 ({format} 형식)', - 'backup.auto.dayOfWeek': '요일', - 'backup.auto.dayOfMonth': '매월 몇 일', - 'backup.auto.dayOfMonthHint': '모든 달과의 호환성을 위해 1-28로 제한', - 'backup.auto.scheduleSummary': '일정', - 'backup.auto.summaryDaily': '매일 {hour}:00', - 'backup.auto.summaryWeekly': '매주 {day} {hour}:00', - 'backup.auto.summaryMonthly': '매월 {day}일 {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': '자동 백업이 Docker 환경 변수로 설정되어 있습니다. 설정을 변경하려면 docker-compose.yml을 업데이트하고 컨테이너를 재시작하세요.', - 'backup.auto.copyEnv': 'Docker 환경 변수 복사', - 'backup.auto.envCopied': 'Docker 환경 변수가 클립보드에 복사되었습니다', - 'backup.auto.keepLabel': '이후 오래된 백업 삭제', - 'backup.dow.sunday': '일', - 'backup.dow.monday': '월', - 'backup.dow.tuesday': '화', - 'backup.dow.wednesday': '수', - 'backup.dow.thursday': '목', - 'backup.dow.friday': '금', - 'backup.dow.saturday': '토', - 'backup.interval.hourly': '매시간', - 'backup.interval.daily': '매일', - 'backup.interval.weekly': '매주', - 'backup.interval.monthly': '매월', - 'backup.keep.1day': '1일', - 'backup.keep.3days': '3일', - 'backup.keep.7days': '7일', - 'backup.keep.14days': '14일', - 'backup.keep.30days': '30일', - 'backup.keep.forever': '영구 보관', - - // Photos - 'photos.title': '사진', - 'photos.subtitle': '{trip}의 사진 {count}장', - 'photos.dropHere': '여기에 사진을 놓으세요...', - 'photos.dropHereActive': '여기에 사진을 놓으세요', - 'photos.captionForAll': '캡션 (전체)', - 'photos.captionPlaceholder': '선택적 캡션...', - 'photos.addCaption': '캡션 추가...', - 'photos.allDays': '모든 날', - 'photos.noPhotos': '아직 사진이 없습니다', - 'photos.uploadHint': '여행 사진을 업로드하세요', - 'photos.clickToSelect': '또는 클릭하여 선택', - 'photos.linkPlace': '장소 연결', - 'photos.noPlace': '장소 없음', - 'photos.uploadN': '{n}장 사진 업로드', - 'photos.linkDay': '날 연결', - 'photos.noDay': '날 없음', - 'photos.dayLabel': '{number}일차', - 'photos.photoSelected': '사진 선택됨', - 'photos.photosSelected': '사진 선택됨', - 'photos.fileTypeHint': 'JPG, PNG, WebP · 최대 10 MB · 최대 30장', - - // Backup restore modal - 'backup.restoreConfirmTitle': '백업을 복원할까요?', - 'backup.restoreWarning': '현재 모든 데이터 (여행, 장소, 사용자, 업로드)가 백업으로 영구 교체됩니다. 이 작업은 취소할 수 없습니다.', - 'backup.restoreTip': '팁: 복원 전에 현재 상태의 백업을 만드세요.', - 'backup.restoreConfirm': '예, 복원', - - // PDF - 'pdf.travelPlan': '여행 계획', - 'pdf.planned': '계획됨', - 'pdf.costLabel': '비용 (원)', - 'pdf.preview': 'PDF 미리보기', - 'pdf.saveAsPdf': 'PDF로 저장', - - // Planner - 'planner.places': '장소', - 'planner.bookings': '예약', - 'planner.packingList': '짐 목록', - 'planner.documents': '문서', - 'planner.dayPlan': '일별 계획', - 'planner.reservations': '예약', - 'planner.minTwoPlaces': '경로 계산에 좌표가 있는 장소가 최소 2개 필요합니다', - 'planner.noGeoPlaces': '좌표가 있는 장소가 없습니다', - 'planner.routeCalculated': '경로가 계산되었습니다', - 'planner.routeCalcFailed': '경로를 계산할 수 없습니다', - 'planner.routeError': '경로 계산 중 오류', - 'planner.icsExportFailed': 'ICS 내보내기 실패', - 'planner.routeOptimized': '경로가 최적화되었습니다', - 'planner.reservationUpdated': '예약이 업데이트되었습니다', - 'planner.reservationAdded': '예약이 추가되었습니다', - 'planner.confirmDeleteReservation': '예약을 삭제할까요?', - 'planner.reservationDeleted': '예약이 삭제되었습니다', - 'planner.days': '일', - 'planner.allPlaces': '모든 장소', - 'planner.totalPlaces': '총 {n}개 장소', - 'planner.noDaysPlanned': '아직 계획된 날이 없습니다', - 'planner.editTrip': '여행 편집 →', - 'planner.placeOne': '장소 1개', - 'planner.placeN': '장소 {n}개', - 'planner.addNote': '메모 추가', - 'planner.noEntries': '이 날에 항목이 없습니다', - 'planner.addPlace': '장소/활동 추가', - 'planner.addPlaceShort': '+ 장소/활동 추가', - 'planner.resPending': '예약 대기 중 · ', - 'planner.resConfirmed': '예약 확정 · ', - 'planner.notePlaceholder': '메모…', - 'planner.noteTimePlaceholder': '시간 (선택)', - 'planner.noteExamplePlaceholder': '예: 14:30 중앙역에서 S3, 7번 부두에서 페리, 점심 휴식…', - 'planner.totalCost': '총 비용', - 'planner.searchPlaces': '장소 검색…', - 'planner.allCategories': '모든 카테고리', - 'planner.noPlacesFound': '장소를 찾을 수 없습니다', - 'planner.addFirstPlace': '첫 번째 장소 추가', - 'planner.noReservations': '예약 없음', - 'planner.addFirstReservation': '첫 번째 예약 추가', - 'planner.new': '새로 만들기', - 'planner.addToDay': '+ 날에 추가', - 'planner.calculating': '계산 중…', - 'planner.route': '경로', - 'planner.optimize': '최적화', - 'planner.openGoogleMaps': 'Google Maps에서 열기', - 'planner.selectDayHint': '왼쪽 목록에서 날을 선택하여 일별 계획을 보세요', - 'planner.noPlacesForDay': '이 날에 아직 장소가 없습니다', - 'planner.addPlacesLink': '장소 추가 →', - 'planner.minTotal': '분 합계', - 'planner.noReservation': '예약 없음', - 'planner.removeFromDay': '날에서 제거', - 'planner.addToThisDay': '날에 추가', - 'planner.overview': '개요', - 'planner.noDays': '아직 날이 없습니다', - 'planner.editTripToAddDays': '여행을 편집하여 날을 추가하세요', - 'planner.dayCount': '{n}일', - 'planner.clickToUnlock': '클릭하여 잠금 해제', - 'planner.keepPosition': '경로 최적화 중 위치 유지', - 'planner.dayDetails': '일별 상세', - 'planner.dayN': '{n}일차', - - // Dashboard Stats - 'stats.countries': '국가', - 'stats.cities': '도시', - 'stats.trips': '여행', - 'stats.places': '장소', - 'stats.worldProgress': '세계 진행도', - 'stats.visited': '방문함', - 'stats.remaining': '남음', - 'stats.visitedCountries': '방문한 나라', - - // Day Detail Panel - 'day.precipProb': '강수 확률', - 'day.precipitation': '강수량', - 'day.wind': '바람', - 'day.sunrise': '일출', - 'day.sunset': '일몰', - 'day.hourlyForecast': '시간별 예보', - 'day.climateHint': '역사적 평균값 — 이 날짜로부터 16일 이내의 실제 예보를 사용할 수 있습니다.', - 'day.noWeather': '날씨 데이터가 없습니다. 좌표가 있는 장소를 추가하세요.', - 'day.overview': '일별 개요', - 'day.accommodation': '숙박', - 'day.addAccommodation': '숙박 추가', - 'day.hotelDayRange': '적용할 날', - 'day.noPlacesForHotel': '먼저 여행에 장소를 추가하세요', - 'day.allDays': '전체', - 'day.checkIn': '체크인', - 'day.checkInUntil': '까지', - 'day.checkOut': '체크아웃', - 'day.confirmation': '확인', - 'day.editAccommodation': '숙박 편집', - 'day.reservations': '예약', - - // Photos / Immich - 'memories.title': '사진', - 'memories.notConnected': '{provider_name}이(가) 연결되지 않았습니다', - 'memories.notConnectedHint': '이 여행에 사진을 추가하려면 설정에서 {provider_name} 인스턴스를 연결하세요.', - 'memories.notConnectedMultipleHint': '이 여행에 사진을 추가하려면 설정에서 다음 사진 공급자 중 하나를 연결하세요: {provider_names}', - 'memories.noDates': '사진을 불러오려면 여행에 날짜를 추가하세요.', - 'memories.noPhotos': '사진을 찾을 수 없습니다', - 'memories.noPhotosHint': '{provider_name}에서 이 여행의 날짜 범위에 해당하는 사진을 찾을 수 없습니다.', - 'memories.photosFound': '장', - 'memories.fromOthers': '다른 사람으로부터', - 'memories.sharePhotos': '사진 공유', - 'memories.sharing': '공유', - 'memories.reviewTitle': '사진 검토', - 'memories.reviewHint': '사진을 클릭하여 공유에서 제외하세요.', - 'memories.shareCount': '사진 {count}장 공유', - 'memories.providerUrl': '서버 URL', - 'memories.providerApiKey': 'API 키', - 'memories.providerUsername': '사용자 이름', - 'memories.providerPassword': '비밀번호', - 'memories.providerOTP': 'MFA 코드 (활성화된 경우)', - 'memories.skipSSLVerification': 'SSL 인증서 확인 건너뛰기', - 'memories.immichAutoUpload': '업로드 시 Journey 사진을 Immich에 미러링', - 'memories.providerUrlHintSynology': 'URL에 Photos 앱 경로를 포함하세요. 예: https://nas:5001/photo', - 'memories.testConnection': '연결 테스트', - 'memories.testShort': '테스트', - 'memories.testFirst': '먼저 연결을 테스트하세요', - 'memories.connected': '연결됨', - 'memories.disconnected': '연결되지 않음', - 'memories.connectionSuccess': '{provider_name}에 연결되었습니다', - 'memories.connectionError': '{provider_name}에 연결할 수 없습니다', - 'memories.saved': '{provider_name} 설정이 저장되었습니다', - 'memories.providerDisconnectedBanner': '{provider_name} 연결이 끊어졌습니다. 사진을 보려면 설정에서 다시 연결하세요.', - 'memories.saveError': '{provider_name} 설정을 저장할 수 없습니다', - 'memories.addPhotos': '사진 추가', - 'memories.linkAlbum': '앨범 연결', - 'memories.selectAlbum': '{provider_name} 앨범 선택', - 'memories.selectAlbumMultiple': '앨범 선택', - 'memories.noAlbums': '앨범을 찾을 수 없습니다', - 'memories.syncAlbum': '앨범 동기화', - 'memories.unlinkAlbum': '앨범 연결 해제', - 'memories.photos': '장', - 'memories.selectPhotos': '{provider_name}에서 사진 선택', - 'memories.selectPhotosMultiple': '사진 선택', - 'memories.selectHint': '사진을 탭하여 선택하세요.', - 'memories.selected': '선택됨', - 'memories.addSelected': '{count}장 추가', - 'memories.alreadyAdded': '추가됨', - 'memories.private': '비공개', - 'memories.stopSharing': '공유 중지', - 'memories.oldest': '오래된 것 먼저', - 'memories.newest': '최신 것 먼저', - 'memories.allLocations': '모든 위치', - 'memories.tripDates': '여행 날짜', - 'memories.allPhotos': '모든 사진', - 'memories.confirmShareTitle': '여행 멤버와 공유할까요?', - 'memories.confirmShareHint': '{count}장의 사진이 이 여행의 모든 멤버에게 표시됩니다. 나중에 개별 사진을 비공개로 만들 수 있습니다.', - 'memories.confirmShareButton': '사진 공유', - 'memories.error.loadAlbums': '앨범 불러오기 실패', - 'memories.error.linkAlbum': '앨범 연결 실패', - 'memories.error.unlinkAlbum': '앨범 연결 해제 실패', - 'memories.error.syncAlbum': '앨범 동기화 실패', - 'memories.error.loadPhotos': '사진 불러오기 실패', - 'memories.error.addPhotos': '사진 추가 실패', - 'memories.error.removePhoto': '사진 제거 실패', - 'memories.error.toggleSharing': '공유 업데이트 실패', - 'memories.saveRouteNotConfigured': '이 공급자에 대해 저장 경로가 설정되지 않았습니다', - 'memories.testRouteNotConfigured': '이 공급자에 대해 테스트 경로가 설정되지 않았습니다', - 'memories.fillRequiredFields': '모든 필수 항목을 입력하세요', - - // Collab Addon - 'collab.tabs.chat': '채팅', - 'collab.tabs.notes': '메모', - 'collab.tabs.polls': '투표', - 'collab.whatsNext.title': '다음 할 일', - 'collab.whatsNext.today': '오늘', - 'collab.whatsNext.tomorrow': '내일', - 'collab.whatsNext.empty': '예정된 활동이 없습니다', - 'collab.whatsNext.until': '까지', - 'collab.whatsNext.emptyHint': '시간이 있는 활동이 여기에 표시됩니다', - 'collab.chat.send': '전송', - 'collab.chat.placeholder': '메시지 입력...', - 'collab.chat.empty': '대화를 시작하세요', - 'collab.chat.emptyHint': '메시지는 모든 여행 멤버와 공유됩니다', - 'collab.chat.emptyDesc': '여행 그룹과 아이디어, 계획, 업데이트를 공유하세요', - 'collab.chat.today': '오늘', - 'collab.chat.yesterday': '어제', - 'collab.chat.deletedMessage': '메시지를 삭제했습니다', - 'collab.chat.reply': '답장', - 'collab.chat.loadMore': '이전 메시지 불러오기', - 'collab.chat.justNow': '방금 전', - 'collab.chat.minutesAgo': '{n}분 전', - 'collab.chat.hoursAgo': '{n}시간 전', - 'collab.notes.title': '메모', - 'collab.notes.new': '새 메모', - 'collab.notes.empty': '아직 메모가 없습니다', - 'collab.notes.emptyHint': '아이디어와 계획을 기록하세요', - 'collab.notes.all': '전체', - 'collab.notes.titlePlaceholder': '메모 제목', - 'collab.notes.contentPlaceholder': '내용을 입력하세요...', - 'collab.notes.categoryPlaceholder': '카테고리', - 'collab.notes.newCategory': '새 카테고리...', - 'collab.notes.category': '카테고리', - 'collab.notes.noCategory': '카테고리 없음', - 'collab.notes.color': '색상', - 'collab.notes.save': '저장', - 'collab.notes.cancel': '취소', - 'collab.notes.edit': '편집', - 'collab.notes.delete': '삭제', - 'collab.notes.pin': '고정', - 'collab.notes.unpin': '고정 해제', - 'collab.notes.daysAgo': '{n}일 전', - 'collab.notes.categorySettings': '카테고리 관리', - 'collab.notes.create': '생성', - 'collab.notes.website': '웹사이트', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': '파일 첨부', - 'collab.notes.noCategoriesYet': '아직 카테고리가 없습니다', - 'collab.notes.emptyDesc': '메모를 만들어 시작하세요', - 'collab.polls.title': '투표', - 'collab.polls.new': '새 투표', - 'collab.polls.empty': '아직 투표가 없습니다', - 'collab.polls.emptyHint': '그룹에 질문하고 함께 투표하세요', - 'collab.polls.question': '질문', - 'collab.polls.questionPlaceholder': '무엇을 할까요?', - 'collab.polls.addOption': '+ 옵션 추가', - 'collab.polls.optionPlaceholder': '옵션 {n}', - 'collab.polls.create': '투표 만들기', - 'collab.polls.close': '닫기', - 'collab.polls.closed': '종료됨', - 'collab.polls.votes': '{n}표', - 'collab.polls.vote': '{n}표', - 'collab.polls.multipleChoice': '복수 선택', - 'collab.polls.multiChoice': '복수 선택', - 'collab.polls.deadline': '마감일', - 'collab.polls.option': '옵션', - 'collab.polls.options': '옵션', - 'collab.polls.delete': '삭제', - 'collab.polls.closedSection': '종료됨', - - // Permissions - 'admin.tabs.permissions': '권한', - 'perm.title': '권한 설정', - 'perm.subtitle': '앱 전체에서 누가 작업을 수행할 수 있는지 제어합니다', - 'perm.saved': '권한 설정이 저장되었습니다', - 'perm.resetDefaults': '기본값으로 초기화', - 'perm.customized': '맞춤 설정됨', - 'perm.level.admin': '관리자만', - 'perm.level.tripOwner': '여행 소유자', - 'perm.level.tripMember': '여행 멤버', - 'perm.level.everybody': '모든 사람', - 'perm.cat.trip': '여행 관리', - 'perm.cat.members': '멤버 관리', - 'perm.cat.files': '파일', - 'perm.cat.content': '콘텐츠 및 일정', - 'perm.cat.extras': '예산, 짐 목록 및 협업', - 'perm.action.trip_create': '여행 만들기', - 'perm.action.trip_edit': '여행 상세 편집', - 'perm.action.trip_delete': '여행 삭제', - 'perm.action.trip_archive': '여행 보관/복원', - 'perm.action.trip_cover_upload': '커버 이미지 업로드', - 'perm.action.member_manage': '멤버 추가/제거', - 'perm.action.file_upload': '파일 업로드', - 'perm.action.file_edit': '파일 메타데이터 편집', - 'perm.action.file_delete': '파일 삭제', - 'perm.action.place_edit': '장소 추가/편집/삭제', - 'perm.action.day_edit': '날, 메모 및 배정 편집', - 'perm.action.reservation_edit': '예약 관리', - 'perm.action.budget_edit': '예산 관리', - 'perm.action.packing_edit': '짐 목록 관리', - 'perm.action.collab_edit': '협업 (메모, 투표, 채팅)', - 'perm.action.share_manage': '공유 링크 관리', - 'perm.actionHint.trip_create': '누가 새 여행을 만들 수 있는지', - 'perm.actionHint.trip_edit': '누가 여행 이름, 날짜, 설명, 통화를 변경할 수 있는지', - 'perm.actionHint.trip_delete': '누가 여행을 영구 삭제할 수 있는지', - 'perm.actionHint.trip_archive': '누가 여행을 보관하거나 복원할 수 있는지', - 'perm.actionHint.trip_cover_upload': '누가 커버 이미지를 업로드하거나 변경할 수 있는지', - 'perm.actionHint.member_manage': '누가 여행 멤버를 초대하거나 제거할 수 있는지', - 'perm.actionHint.file_upload': '누가 여행에 파일을 업로드할 수 있는지', - 'perm.actionHint.file_edit': '누가 파일 설명 및 링크를 편집할 수 있는지', - 'perm.actionHint.file_delete': '누가 파일을 휴지통으로 이동하거나 영구 삭제할 수 있는지', - 'perm.actionHint.place_edit': '누가 장소를 추가, 편집, 삭제할 수 있는지', - 'perm.actionHint.day_edit': '누가 날, 일별 메모, 장소 배정을 편집할 수 있는지', - 'perm.actionHint.reservation_edit': '누가 예약을 만들고, 편집하고, 삭제할 수 있는지', - 'perm.actionHint.budget_edit': '누가 예산 항목을 만들고, 편집하고, 삭제할 수 있는지', - 'perm.actionHint.packing_edit': '누가 짐 항목과 가방을 관리할 수 있는지', - 'perm.actionHint.collab_edit': '누가 메모, 투표를 만들고 메시지를 보낼 수 있는지', - 'perm.actionHint.share_manage': '누가 공개 공유 링크를 만들거나 삭제할 수 있는지', - - // Undo - 'undo.button': '실행 취소', - 'undo.tooltip': '실행 취소: {action}', - 'undo.assignPlace': '장소가 날에 배정되었습니다', - 'undo.removeAssignment': '장소가 날에서 제거되었습니다', - 'undo.reorder': '장소 순서가 변경되었습니다', - 'undo.optimize': '경로가 최적화되었습니다', - 'undo.deletePlace': '장소가 삭제되었습니다', - 'undo.deletePlaces': '장소들이 삭제되었습니다', - 'undo.moveDay': '장소가 다른 날로 이동되었습니다', - 'undo.lock': '장소 잠금이 변경되었습니다', - 'undo.importGpx': 'GPX 가져오기', - 'undo.importKeyholeMarkup': 'KMZ/KML 가져오기', - 'undo.importGoogleList': 'Google Maps 가져오기', - 'undo.importNaverList': '네이버 지도 가져오기', - 'undo.addPlace': '장소가 추가되었습니다', - 'undo.done': '실행 취소됨: {action}', - - // Notifications - 'notifications.title': '알림', - 'notifications.markAllRead': '모두 읽음으로 표시', - 'notifications.deleteAll': '모두 삭제', - 'notifications.showAll': '모든 알림 보기', - 'notifications.empty': '알림 없음', - 'notifications.emptyDescription': '모두 확인했습니다!', - 'notifications.all': '전체', - 'notifications.unreadOnly': '읽지 않음', - 'notifications.markRead': '읽음으로 표시', - 'notifications.markUnread': '읽지 않음으로 표시', - 'notifications.delete': '삭제', - 'notifications.system': '시스템', - 'notifications.synologySessionCleared.title': 'Synology Photos 연결 해제됨', - 'notifications.synologySessionCleared.text': '서버 또는 계정이 변경되었습니다 — 설정에서 연결을 다시 테스트하세요.', - - // Notification test keys (dev only) - 'notifications.versionAvailable.title': '업데이트 사용 가능', - 'notifications.versionAvailable.text': 'TREK {version}이(가) 사용 가능합니다.', - 'notifications.versionAvailable.button': '상세 보기', - 'notifications.test.title': '{actor}의 테스트 알림', - 'notifications.test.text': '간단한 테스트 알림입니다.', - 'notifications.test.booleanTitle': '{actor}이(가) 승인을 요청합니다', - 'notifications.test.booleanText': '테스트 boolean 알림입니다. 아래에서 작업을 선택하세요.', - 'notifications.test.accept': '승인', - 'notifications.test.decline': '거절', - 'notifications.test.navigateTitle': '확인할 항목이 있습니다', - 'notifications.test.navigateText': '테스트 navigate 알림입니다.', - 'notifications.test.goThere': '이동', - 'notifications.test.adminTitle': '관리자 방송', - 'notifications.test.adminText': '{actor}이(가) 모든 관리자에게 테스트 알림을 보냈습니다.', - 'notifications.test.tripTitle': '{actor}이(가) 여행에 게시했습니다', - 'notifications.test.tripText': '여행 "{trip}"의 테스트 알림입니다.', - - // Todo - 'todo.subtab.packing': '짐 목록', - 'todo.subtab.todo': '할 일', - 'todo.completed': '완료됨', - 'todo.filter.all': '전체', - 'todo.filter.open': '미완료', - 'todo.filter.done': '완료', - 'todo.uncategorized': '미분류', - 'todo.namePlaceholder': '작업 이름', - 'todo.descriptionPlaceholder': '설명 (선택)', - 'todo.unassigned': '미배정', - 'todo.noCategory': '카테고리 없음', - 'todo.hasDescription': '설명 있음', - 'todo.addItem': '새 작업 추가', - 'todo.sidebar.sortBy': '정렬 기준', - 'todo.priority': '우선순위', - 'todo.newCategoryLabel': '새로 만들기', - 'budget.categoriesLabel': '카테고리', - 'todo.newCategory': '카테고리 이름', - 'todo.addCategory': '카테고리 추가', - 'todo.newItem': '새 작업', - 'todo.empty': '아직 작업이 없습니다. 작업을 추가하여 시작하세요!', - 'todo.filter.my': '내 작업', - 'todo.filter.overdue': '기한 초과', - 'todo.sidebar.tasks': '작업', - 'todo.sidebar.categories': '카테고리', - 'todo.detail.title': '작업', - 'todo.detail.description': '설명', - 'todo.detail.category': '카테고리', - 'todo.detail.dueDate': '마감일', - 'todo.detail.assignedTo': '배정 대상', - 'todo.detail.delete': '삭제', - 'todo.detail.save': '변경 사항 저장', - 'todo.sortByPrio': '우선순위', - 'todo.detail.priority': '우선순위', - 'todo.detail.noPriority': '없음', - 'todo.detail.create': '작업 만들기', - - // Notifications — dev test events - 'notif.test.title': '[테스트] 알림', - 'notif.test.simple.text': '간단한 테스트 알림입니다.', - 'notif.test.boolean.text': '이 테스트 알림을 수락하시겠습니까?', - 'notif.test.navigate.text': '아래를 클릭하여 대시보드로 이동하세요.', - - // Notifications - 'notif.trip_invite.title': '여행 초대', - 'notif.trip_invite.text': '{actor}이(가) {trip}에 초대했습니다', - 'notif.booking_change.title': '예약 업데이트됨', - 'notif.booking_change.text': '{actor}이(가) {trip}의 예약을 업데이트했습니다', - 'notif.trip_reminder.title': '여행 리마인더', - 'notif.trip_reminder.text': '여행 {trip}이(가) 곧 시작됩니다!', - 'notif.todo_due.title': '할 일 마감', - 'notif.todo_due.text': '{trip}의 {todo}이(가) {due}에 마감됩니다', - 'notif.vacay_invite.title': 'Vacay 퓨전 초대', - 'notif.vacay_invite.text': '{actor}이(가) 휴가 계획 공유에 초대했습니다', - 'notif.photos_shared.title': '사진 공유됨', - 'notif.photos_shared.text': '{actor}이(가) {trip}에서 {count}장의 사진을 공유했습니다', - 'notif.collab_message.title': '새 메시지', - 'notif.collab_message.text': '{actor}이(가) {trip}에서 메시지를 보냈습니다', - 'notif.packing_tagged.title': '짐 목록 배정', - 'notif.packing_tagged.text': '{actor}이(가) {trip}의 {category}에 배정했습니다', - 'notif.version_available.title': '새 버전 사용 가능', - 'notif.version_available.text': 'TREK {version}이(가) 사용 가능합니다', - 'notif.action.view_trip': '여행 보기', - 'notif.action.view_collab': '메시지 보기', - 'notif.action.view_packing': '짐 목록 보기', - 'notif.action.view_photos': '사진 보기', - 'notif.action.view_vacay': 'Vacay 보기', - 'notif.action.view_admin': '관리자로 이동', - 'notif.action.view': '보기', - 'notif.action.accept': '수락', - 'notif.action.decline': '거절', - 'notif.generic.title': '알림', - 'notif.generic.text': '새 알림이 있습니다', - 'notif.dev.unknown_event.title': '[DEV] 알 수 없는 이벤트', - 'notif.dev.unknown_event.text': '이벤트 유형 "{event}"이(가) EVENT_NOTIFICATION_CONFIG에 등록되지 않았습니다', - - // Journey addon - 'journey.search.placeholder': 'Journey 검색…', - 'journey.search.noResults': '"{query}"와(과) 일치하는 Journey가 없습니다', - 'journey.title': 'Journey', - 'journey.subtitle': '여행을 실시간으로 기록하세요', - 'journey.new': '새 Journey', - 'journey.create': '만들기', - 'journey.titlePlaceholder': '어디로 가시나요?', - 'journey.empty': '아직 Journey가 없습니다', - 'journey.emptyHint': '다음 여행을 기록하기 시작하세요', - 'journey.deleted': 'Journey가 삭제되었습니다', - 'journey.createError': 'Journey를 만들 수 없습니다', - 'journey.deleteError': 'Journey를 삭제할 수 없습니다', - 'journey.deleteConfirmTitle': '삭제', - 'journey.deleteConfirmMessage': '"{title}"을(를) 삭제할까요? 이 작업은 취소할 수 없습니다.', - 'journey.deleteConfirmGeneric': '정말로 삭제할까요?', - 'journey.notFound': 'Journey를 찾을 수 없습니다', - 'journey.photos': '사진', - 'journey.timelineEmpty': '아직 정류장이 없습니다', - 'journey.timelineEmptyHint': '체크인을 추가하거나 일기를 작성하여 시작하세요', - 'journey.status.draft': '초안', - 'journey.status.active': '활성', - 'journey.status.completed': '완료됨', - 'journey.status.upcoming': '예정됨', - 'journey.status.archived': '보관됨', - 'journey.checkin.add': '체크인', - 'journey.checkin.namePlaceholder': '위치 이름', - 'journey.checkin.notesPlaceholder': '메모 (선택)', - 'journey.checkin.save': '저장', - 'journey.checkin.error': '체크인을 저장할 수 없습니다', - 'journey.entry.add': '일기', - 'journey.entry.edit': '항목 편집', - 'journey.entry.titlePlaceholder': '제목 (선택)', - 'journey.entry.bodyPlaceholder': '오늘 무슨 일이 있었나요?', - 'journey.entry.save': '저장', - 'journey.entry.error': '항목을 저장할 수 없습니다', - 'journey.photo.add': '사진', - 'journey.photo.uploadError': '업로드 실패', - 'journey.share.share': '공유', - 'journey.share.public': '공개', - 'journey.share.linkCopied': '공개 링크가 복사되었습니다', - 'journey.share.disabled': '공개 공유 비활성화됨', - 'journey.editor.titlePlaceholder': '이 순간에 이름을 붙여주세요...', - 'journey.editor.bodyPlaceholder': '오늘의 이야기를 들려주세요...', - 'journey.editor.placePlaceholder': '위치 (선택)', - 'journey.editor.tagsPlaceholder': '태그: 숨겨진 명소, 최고의 식사, 다시 방문...', - 'journey.visibility.private': '비공개', - 'journey.visibility.shared': '공유됨', - 'journey.visibility.public': '공개', - 'journey.emptyState.title': '여기서 이야기가 시작됩니다', - 'journey.emptyState.subtitle': '장소에 체크인하거나 첫 번째 일기 항목을 작성하세요', - - // Journey Frontpage - 'journey.frontpage.subtitle': '여행을 영원히 잊지 못할 이야기로 만드세요', - 'journey.frontpage.createJourney': 'Journey 만들기', - 'journey.frontpage.activeJourney': '활성 Journey', - 'journey.frontpage.allJourneys': '모든 Journey', - 'journey.frontpage.journeys': '개 Journey', - 'journey.frontpage.createNew': '새 Journey 만들기', - 'journey.frontpage.createNewSub': '여행을 선택하고, 이야기를 쓰고, 모험을 공유하세요', - 'journey.frontpage.live': '라이브', - 'journey.frontpage.synced': '동기화됨', - 'journey.frontpage.continueWriting': '계속 쓰기', - 'journey.frontpage.updated': '{time}에 업데이트됨', - 'journey.frontpage.suggestionLabel': '여행이 방금 종료됨', - 'journey.frontpage.suggestionText': '{title}을(를) Journey로 만들어보세요', - 'journey.frontpage.dismiss': '닫기', - 'journey.frontpage.journeyName': 'Journey 이름', - 'journey.frontpage.namePlaceholder': '예: 동남아시아 2026', - 'journey.frontpage.selectTrips': '여행 선택', - 'journey.frontpage.tripsSelected': '개 여행 선택됨', - 'journey.frontpage.trips': '개 여행', - 'journey.frontpage.placesImported': '개 장소가 가져와집니다', - 'journey.frontpage.places': '개 장소', - - // Journey Detail - 'journey.detail.backToJourney': 'Journey로 돌아가기', - 'journey.detail.syncedWithTrips': '여행과 동기화됨', - 'journey.detail.addEntry': '항목 추가', - 'journey.detail.newEntry': '새 항목', - 'journey.detail.editEntry': '항목 편집', - 'journey.detail.noEntries': '아직 항목이 없습니다', - 'journey.detail.noEntriesHint': '여행을 추가하여 스켈레톤 항목으로 시작하세요', - 'journey.detail.noPhotos': '아직 사진이 없습니다', - 'journey.detail.noPhotosHint': '항목에 사진을 업로드하거나 Immich/Synology 라이브러리를 탐색하세요', - 'journey.detail.journeyTab': 'Journey', - 'journey.detail.journeyStats': 'Journey 통계', - 'journey.detail.syncedTrips': '동기화된 여행', - 'journey.detail.noTripsLinked': '아직 연결된 여행이 없습니다', - 'journey.detail.contributors': '기여자', - 'journey.detail.readMore': '더 읽기', - 'journey.detail.prosCons': '장단점', - 'journey.detail.photos': '장', - 'journey.detail.day': '{number}일차', - 'journey.detail.places': '개 장소', - - // Journey Detail — Stats - 'journey.stats.days': '일', - 'journey.stats.cities': '도시', - 'journey.stats.entries': '항목', - 'journey.stats.photos': '사진', - 'journey.stats.places': '장소', - 'journey.skeletons.show': '제안 보기', - 'journey.skeletons.hide': '제안 숨기기', - - // Journey Detail — Verdict - 'journey.verdict.lovedIt': '정말 좋았어요', - 'journey.verdict.couldBeBetter': '더 좋을 수 있었어요', - - // Journey Detail — Synced badge - 'journey.synced.places': '개 장소', - 'journey.synced.synced': '동기화됨', - - // Journey Entry Editor - 'journey.editor.discardChangesConfirm': '저장되지 않은 변경 사항이 있습니다. 취소할까요?', - 'journey.editor.uploadPhotos': '사진 업로드', - 'journey.editor.uploading': '업로드 중...', - 'journey.editor.fromGallery': '갤러리에서', - 'journey.editor.allPhotosAdded': '모든 사진이 이미 추가되었습니다', - 'journey.editor.writeStory': '이야기를 써주세요...', - 'journey.editor.prosCons': '장단점', - 'journey.editor.pros': '장점', - 'journey.editor.cons': '단점', - 'journey.editor.proPlaceholder': '좋은 점...', - 'journey.editor.conPlaceholder': '아쉬운 점...', - 'journey.editor.addAnother': '하나 더 추가', - 'journey.editor.date': '날짜', - 'journey.editor.location': '위치', - 'journey.editor.searchLocation': '위치 검색...', - 'journey.editor.mood': '기분', - 'journey.editor.weather': '날씨', - 'journey.editor.photoFirst': '1번째', - 'journey.editor.makeFirst': '1번째로 설정', - 'journey.editor.searching': '검색 중...', - - // Journey Entry — Moods - 'journey.mood.amazing': '최고!', - 'journey.mood.good': '좋음', - 'journey.mood.neutral': '보통', - 'journey.mood.rough': '힘들었음', - - // Journey Entry — Weather - 'journey.weather.sunny': '맑음', - 'journey.weather.partly': '구름 조금', - 'journey.weather.cloudy': '흐림', - 'journey.weather.rainy': '비', - 'journey.weather.stormy': '폭풍', - 'journey.weather.cold': '눈', - - // Journey — Trip Linking - 'journey.trips.linkTrip': '여행 연결', - 'journey.trips.searchTrip': '여행 검색', - 'journey.trips.searchPlaceholder': '여행 이름 또는 목적지...', - 'journey.trips.noTripsAvailable': '사용 가능한 여행이 없습니다', - 'journey.trips.link': '연결', - 'journey.trips.tripLinked': '여행이 연결되었습니다', - 'journey.trips.linkFailed': '여행 연결 실패', - 'journey.trips.addTrip': '여행 추가', - 'journey.trips.unlinkTrip': '여행 연결 해제', - 'journey.trips.unlinkMessage': '"{title}"을(를) 연결 해제할까요? 이 여행의 동기화된 모든 항목과 사진이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.', - 'journey.trips.unlink': '연결 해제', - 'journey.trips.tripUnlinked': '여행 연결이 해제되었습니다', - 'journey.trips.unlinkFailed': '여행 연결 해제 실패', - 'journey.trips.noTripsLinkedSettings': '연결된 여행이 없습니다', - - // Journey — Contributors - 'journey.contributors.invite': '기여자 초대', - 'journey.contributors.searchUser': '사용자 검색', - 'journey.contributors.searchPlaceholder': '사용자 이름 또는 이메일...', - 'journey.contributors.noUsers': '사용자를 찾을 수 없습니다', - 'journey.contributors.role': '역할', - 'journey.contributors.added': '기여자가 추가되었습니다', - 'journey.contributors.addFailed': '기여자 추가 실패', - 'journey.contributors.remove': '기여자 제거', - 'journey.contributors.removeConfirm': '{username}을(를) 이 Journey에서 제거할까요?', - 'journey.contributors.removed': '기여자가 제거되었습니다', - 'journey.contributors.removeFailed': '기여자 제거 실패', - - // Journey — Share - 'journey.share.publicShare': '공개 공유', - 'journey.share.createLink': '공유 링크 만들기', - 'journey.share.linkCreated': '공유 링크가 생성되었습니다', - 'journey.share.createFailed': '링크 생성 실패', - 'journey.share.copy': '복사', - 'journey.share.copied': '복사됨!', - 'journey.share.timeline': '타임라인', - 'journey.share.gallery': '갤러리', - 'journey.share.map': '지도', - 'journey.share.removeLink': '공유 링크 제거', - 'journey.share.linkDeleted': '공유 링크가 삭제되었습니다', - 'journey.share.deleteFailed': '삭제 실패', - 'journey.share.updateFailed': '업데이트 실패', - - // Journey — Invite - 'journey.invite.role': '역할', - 'journey.invite.viewer': '뷰어', - 'journey.invite.editor': '편집자', - 'journey.invite.invite': '초대', - 'journey.invite.inviting': '초대 중...', - - // Journey — Settings Dialog - 'journey.settings.title': 'Journey 설정', - 'journey.settings.coverImage': '커버 이미지', - 'journey.settings.changeCover': '커버 변경', - 'journey.settings.addCover': '커버 이미지 추가', - 'journey.settings.name': '이름', - 'journey.settings.subtitle': '부제목', - 'journey.settings.subtitlePlaceholder': '예: 태국, 베트남 & 캄보디아', - 'journey.settings.endJourney': 'Journey 보관', - 'journey.settings.reopenJourney': 'Journey 복원', - 'journey.settings.archived': 'Journey가 보관되었습니다', - 'journey.settings.reopened': 'Journey가 복원되었습니다', - 'journey.settings.endDescription': '라이브 배지를 숨깁니다. 언제든지 다시 열 수 있습니다.', - 'journey.settings.delete': '삭제', - 'journey.settings.deleteJourney': 'Journey 삭제', - 'journey.settings.deleteMessage': '"{title}"을(를) 삭제할까요? 모든 항목과 사진이 삭제됩니다.', - 'journey.settings.saved': '설정이 저장되었습니다', - 'journey.settings.saveFailed': '저장 실패', - 'journey.settings.coverUpdated': '커버가 업데이트되었습니다', - 'journey.settings.coverFailed': '업로드 실패', - 'journey.settings.failedToDelete': '삭제 실패', - 'journey.entries.deleteTitle': '항목 삭제', - 'journey.photosUploaded': '{count}장 사진이 업로드되었습니다', - 'journey.photosAdded': '{count}장 사진이 추가되었습니다', - - // Journey — Public Page - 'journey.public.notFound': '찾을 수 없습니다', - 'journey.public.notFoundMessage': '이 Journey가 존재하지 않거나 링크가 만료되었습니다.', - 'journey.public.readOnly': '읽기 전용 · 공개 Journey', - 'journey.public.tagline': '여행 기록 및 탐험 키트', - 'journey.public.sharedVia': '공유 경로', - 'journey.public.madeWith': '으로 만들어짐', - - // Journey — PDF Export - 'journey.pdf.journeyBook': 'Journey 책', - 'journey.pdf.madeWith': 'TREK으로 만들어짐', - 'journey.pdf.day': '일차', - 'journey.pdf.theEnd': '끝', - 'journey.pdf.saveAsPdf': 'PDF로 저장', - 'journey.pdf.pages': '페이지', - 'journey.picker.tripPeriod': '여행 기간', - 'journey.picker.dateRange': '날짜 범위', - 'journey.picker.allPhotos': '모든 사진', - 'journey.picker.albums': '앨범', - 'journey.picker.selected': '선택됨', - 'journey.picker.addTo': '추가', - 'journey.picker.newGallery': '새 갤러리', - 'journey.picker.selectAll': '전체 선택', - 'journey.picker.deselectAll': '전체 해제', - 'journey.picker.noAlbums': '앨범을 찾을 수 없습니다', - 'journey.picker.selectDate': '날짜 선택', - 'journey.picker.search': '검색', - - // Dashboard Mobile - 'dashboard.greeting.morning': '좋은 아침이에요,', - 'dashboard.greeting.afternoon': '안녕하세요,', - 'dashboard.greeting.evening': '좋은 저녁이에요,', - 'dashboard.mobile.liveNow': '지금 라이브', - 'dashboard.mobile.tripProgress': '여행 진행도', - 'dashboard.mobile.daysLeft': '{count}일 남음', - 'dashboard.mobile.places': '장소', - 'dashboard.mobile.buddies': '동행자', - 'dashboard.mobile.newTrip': '새 여행', - 'dashboard.mobile.currency': '통화', - 'dashboard.mobile.timezone': '시간대', - 'dashboard.mobile.upcomingTrips': '예정된 여행', - 'dashboard.mobile.yourTrips': '내 여행', - 'dashboard.mobile.trips': '개 여행', - 'dashboard.mobile.starts': '시작', - 'dashboard.mobile.duration': '기간', - 'dashboard.mobile.day': '일', - 'dashboard.mobile.days': '일', - 'dashboard.mobile.ongoing': '진행 중', - 'dashboard.mobile.startsToday': '오늘 시작', - 'dashboard.mobile.tomorrow': '내일', - 'dashboard.mobile.inDays': '{count}일 후', - 'dashboard.mobile.inMonths': '{count}개월 후', - 'dashboard.mobile.completed': '완료됨', - 'dashboard.mobile.currencyConverter': '환율 계산기', - - // BottomNav & Profile - 'nav.profile': '프로필', - 'nav.bottomSettings': '설정', - 'nav.bottomAdmin': '관리자 설정', - 'nav.bottomLogout': '로그아웃', - 'nav.bottomAdminBadge': '관리자', - - // DayPlan Mobile - 'dayplan.mobile.addPlace': '장소 추가', - 'dayplan.mobile.searchPlaces': '장소 검색...', - 'dayplan.mobile.allAssigned': '모든 장소가 배정되었습니다', - 'dayplan.mobile.noMatch': '일치 없음', - 'dayplan.mobile.createNew': '새 장소 만들기', - - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': '체크인, 사진, 일별 이야기가 있는 여행 기록 및 여행 일지', - - // OAuth scope groups - 'oauth.scope.group.trips': '여행', - 'oauth.scope.group.places': '장소', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': '짐 목록', - 'oauth.scope.group.todos': '할 일', - 'oauth.scope.group.budget': '예산', - 'oauth.scope.group.reservations': '예약', - 'oauth.scope.group.collab': '협업', - 'oauth.scope.group.notifications': '알림', - 'oauth.scope.group.vacay': '휴가', - 'oauth.scope.group.geo': '지리', - 'oauth.scope.group.weather': '날씨', - 'oauth.scope.group.journey': 'Journey', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': '여행 및 일정 보기', - 'oauth.scope.trips:read.description': '여행, 날, 일별 메모, 멤버 읽기', - 'oauth.scope.trips:write.label': '여행 및 일정 편집', - 'oauth.scope.trips:write.description': '여행, 날, 메모 만들기 및 업데이트, 멤버 관리', - 'oauth.scope.trips:delete.label': '여행 삭제', - 'oauth.scope.trips:delete.description': '전체 여행 영구 삭제 — 이 작업은 되돌릴 수 없습니다', - 'oauth.scope.trips:share.label': '공유 링크 관리', - 'oauth.scope.trips:share.description': '여행의 공개 공유 링크 만들기, 업데이트, 취소', - 'oauth.scope.places:read.label': '장소 및 지도 데이터 보기', - 'oauth.scope.places:read.description': '장소, 날 배정, 태그, 카테고리 읽기', - 'oauth.scope.places:write.label': '장소 관리', - 'oauth.scope.places:write.description': '장소, 배정, 태그 만들기, 업데이트, 삭제', - 'oauth.scope.atlas:read.label': 'Atlas 보기', - 'oauth.scope.atlas:read.description': '방문한 나라, 지역, 버킷 리스트 읽기', - 'oauth.scope.atlas:write.label': 'Atlas 관리', - 'oauth.scope.atlas:write.description': '방문한 나라 및 지역 표시, 버킷 리스트 관리', - 'oauth.scope.packing:read.label': '짐 목록 보기', - 'oauth.scope.packing:read.description': '짐 항목, 가방, 카테고리 배정 읽기', - 'oauth.scope.packing:write.label': '짐 목록 관리', - 'oauth.scope.packing:write.description': '짐 항목 및 가방 추가, 업데이트, 삭제, 체크, 순서 변경', - 'oauth.scope.todos:read.label': '할 일 목록 보기', - 'oauth.scope.todos:read.description': '여행 할 일 항목 및 카테고리 배정 읽기', - 'oauth.scope.todos:write.label': '할 일 목록 관리', - 'oauth.scope.todos:write.description': '할 일 항목 만들기, 업데이트, 체크, 삭제, 순서 변경', - 'oauth.scope.budget:read.label': '예산 보기', - 'oauth.scope.budget:read.description': '예산 항목 및 지출 내역 읽기', - 'oauth.scope.budget:write.label': '예산 관리', - 'oauth.scope.budget:write.description': '예산 항목 만들기, 업데이트, 삭제', - 'oauth.scope.reservations:read.label': '예약 보기', - 'oauth.scope.reservations:read.description': '예약 및 숙박 상세 정보 읽기', - 'oauth.scope.reservations:write.label': '예약 관리', - 'oauth.scope.reservations:write.description': '예약 만들기, 업데이트, 삭제, 순서 변경', - 'oauth.scope.collab:read.label': '협업 보기', - 'oauth.scope.collab:read.description': '협업 메모, 투표, 메시지 읽기', - 'oauth.scope.collab:write.label': '협업 관리', - 'oauth.scope.collab:write.description': '협업 메모, 투표, 메시지 만들기, 업데이트, 삭제', - 'oauth.scope.notifications:read.label': '알림 보기', - 'oauth.scope.notifications:read.description': '앱 내 알림 및 읽지 않은 수 읽기', - 'oauth.scope.notifications:write.label': '알림 관리', - 'oauth.scope.notifications:write.description': '알림 읽음 표시 및 응답', - 'oauth.scope.vacay:read.label': '휴가 계획 보기', - 'oauth.scope.vacay:read.description': '휴가 계획 데이터, 항목, 통계 읽기', - 'oauth.scope.vacay:write.label': '휴가 계획 관리', - 'oauth.scope.vacay:write.description': '휴가 항목, 공휴일, 팀 계획 만들기 및 관리', - 'oauth.scope.geo:read.label': '지도 및 지오코딩', - 'oauth.scope.geo:read.description': '위치 검색, 지도 URL 확인, 좌표 역지오코딩', - 'oauth.scope.weather:read.label': '날씨 예보', - 'oauth.scope.weather:read.description': '여행 위치 및 날짜의 날씨 예보 가져오기', - 'oauth.scope.journey:read.label': 'Journey 보기', - 'oauth.scope.journey:read.description': 'Journey, 항목, 기여자 목록 읽기', - 'oauth.scope.journey:write.label': 'Journey 관리', - 'oauth.scope.journey:write.description': 'Journey 및 항목 만들기, 업데이트, 삭제', - 'oauth.scope.journey:share.label': 'Journey 링크 관리', - 'oauth.scope.journey:share.description': 'Journey의 공개 공유 링크 만들기, 업데이트, 취소', - - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': '3.0에서 사진이 이동했습니다', - 'system_notice.v3_photos.body': '여행 플래너의 **사진** 기능이 제거되었습니다. 사진은 안전합니다 — TREK은 Immich 또는 Synology 라이브러리를 수정하지 않았습니다.\n\n사진은 이제 **Journey** 애드온에 있습니다. Journey는 선택 사항입니다 — 아직 사용할 수 없다면 관리자에게 관리자 → 애드온에서 활성화를 요청하세요.', - 'system_notice.v3_journey.title': 'Journey를 만나보세요 — 여행 일지', - 'system_notice.v3_journey.body': '타임라인, 사진 갤러리, 인터랙티브 지도가 있는 풍부한 여행 이야기로 여행을 기록하세요.', - 'system_notice.v3_journey.cta_label': 'Journey 열기', - 'system_notice.v3_journey.highlight_timeline': '일별 타임라인 및 갤러리', - 'system_notice.v3_journey.highlight_photos': 'Immich 또는 Synology에서 가져오기', - 'system_notice.v3_journey.highlight_share': '공개 공유 — 로그인 불필요', - 'system_notice.v3_journey.highlight_export': 'PDF 사진 책으로 내보내기', - 'system_notice.v3_features.title': '3.0의 더 많은 하이라이트', - 'system_notice.v3_features.body': '이번 릴리스에서 알아두면 좋은 몇 가지 더.', - 'system_notice.v3_features.highlight_dashboard': '모바일 우선 대시보드 재설계', - 'system_notice.v3_features.highlight_offline': 'PWA로 완전한 오프라인 모드', - 'system_notice.v3_features.highlight_search': '실시간 장소 검색 자동완성', - 'system_notice.v3_features.highlight_import': 'KMZ/KML 파일에서 장소 가져오기', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 업그레이드', - 'system_notice.v3_mcp.body': 'MCP 통합이 완전히 개선되었습니다. OAuth 2.1이 이제 권장 인증 방법입니다. 기존 정적 토큰 (trek_…)은 더 이상 사용되지 않으며 향후 릴리스에서 제거될 예정입니다.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 권장 (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24개 세분화된 권한 범위', - 'system_notice.v3_mcp.highlight_deprecated': '정적 trek_ 토큰 더 이상 사용 안 됨', - 'system_notice.v3_mcp.highlight_tools': '확장된 도구 모음 및 프롬프트', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': '개인적인 감사 인사', - 'system_notice.v3_thankyou.body': '떠나시기 전에 잠깐 시간을 내주세요.\n\nTREK은 제 자신의 여행을 위해 만든 사이드 프로젝트로 시작했습니다. 4,000명이 넘는 분들이 모험을 계획하는 데 신뢰해 주실 줄은 상상도 못 했습니다. 모든 별, 모든 이슈, 모든 기능 요청 — 저는 다 읽고, 그것들이 풀타임 직장과 대학 사이의 늦은 밤을 버티게 해줍니다.\n\n알아주셨으면 합니다: TREK은 항상 오픈 소스이고, 항상 자체 호스팅이며, 항상 여러분의 것입니다. 추적 없음, 구독 없음, 조건 없음. 그저 여러분만큼 여행을 사랑하는 누군가가 만든 도구입니다.\n\n[jubnl](https://github.com/jubnl)에게 특별한 감사를. 당신은 훌륭한 협력자가 되었습니다. 3.0을 훌륭하게 만든 많은 부분에 당신의 손길이 담겨 있습니다. 거칠던 초기에 이 프로젝트를 믿어줘서 고맙습니다.\n\n그리고 버그를 제출하고, 문자열을 번역하고, TREK을 친구에게 공유하거나, 단순히 여행 계획에 사용해 주신 모든 분들께 — **감사합니다**. 여러분이 바로 이것이 존재하는 이유입니다.\n\n함께하는 더 많은 모험을 위해.\n\n— Maurice\n\n---\n\n[Discord 커뮤니티에 참여하세요](https://discord.gg/7Q6M6jDwzf)\n\nTREK이 여행을 더 즐겁게 만들어 준다면, [커피 한 잔](https://ko-fi.com/mauriceboe)으로 불을 켜두는 데 도움이 됩니다.', - - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': '조치 필요: 사용자 계정 충돌', - 'system_notice.v3014_whitespace_collision.body': '3.0.14 업그레이드 중 저장된 계정의 앞뒤 공백으로 인한 사용자 이름 또는 이메일 충돌이 감지되었습니다. 영향받은 계정은 자동으로 이름이 변경되었습니다. 검토가 필요한 계정을 확인하려면 **[migration] WHITESPACE COLLISION**으로 시작하는 줄의 서버 로그를 확인하세요.', - - // System notices — onboarding - 'system_notice.welcome_v1.title': 'TREK에 오신 것을 환영합니다', - 'system_notice.welcome_v1.body': '올인원 여행 플래너. 일정을 만들고, 친구들과 여행을 공유하고, 온라인 또는 오프라인으로 체계적으로 유지하세요.', - 'system_notice.welcome_v1.cta_label': '여행 계획', - 'system_notice.welcome_v1.hero_alt': 'TREK 계획 UI 오버레이가 있는 아름다운 여행지', - 'system_notice.welcome_v1.highlight_plan': '모든 여행을 위한 일별 일정', - 'system_notice.welcome_v1.highlight_share': '여행 파트너와 협업', - 'system_notice.welcome_v1.highlight_offline': '모바일에서 오프라인으로 작동', - 'system_notice.dev_test_modal.title': '[Dev] 테스트 공지', - 'system_notice.dev_test_modal.body': '개발 전용 테스트 공지입니다.', - 'system_notice.pager.prev': '이전 공지', - 'system_notice.pager.next': '다음 공지', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': '{n}번 공지로 이동', - 'system_notice.pager.position': '공지 {current}/{total}', - 'transport.addTransport': '교통 추가', - 'transport.modalTitle.create': '교통 추가', - 'transport.modalTitle.edit': '교통 편집', - 'transport.title': '교통', - 'transport.addManual': '직접 교통 입력', - - // Added to match EN keys - 'journey.editor.uploadingProgress': '업로드 중 {done}/{total}…', - 'journey.editor.uploadFailed': '사진 업로드 실패', - 'journey.editor.uploadPartialFailed': '{total}개 중 {failed}개의 사진을 업로드하지 못했습니다 — 다시 저장하여 재시도하세요', - 'journey.photosUploadFailed': '일부 사진을 업로드하지 못했습니다', - 'settings.oauth.modal.machineClient': '머신 클라이언트(브라우저 로그인 없음)', - 'settings.oauth.modal.machineClientHint': 'client_credentials 권한 부여를 사용합니다 — 리디렉션 URI가 필요하지 않습니다. 토큰은 client_id + client_secret을 통해 직접 발급되며 선택한 범위 내에서 사용자로 작동합니다.', - 'settings.oauth.modal.machineClientUsage': '토큰 받기: grant_type=client_credentials, client_id, client_secret으로 POST /oauth/token을 호출하세요. 브라우저도 새로 고침 토큰도 필요 없습니다.', - 'settings.oauth.badge.machine': '머신', -} - -export default ko diff --git a/client/src/i18n/translations/nl.ts b/client/src/i18n/translations/nl.ts deleted file mode 100644 index 57af57ae..00000000 --- a/client/src/i18n/translations/nl.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const nl: Record = { - // Common - 'common.save': 'Opslaan', - 'common.showMore': 'Meer tonen', - 'common.showLess': 'Minder tonen', - 'common.cancel': 'Annuleren', - 'common.clear': 'Wissen', - 'common.delete': 'Verwijderen', - 'common.edit': 'Bewerken', - 'common.add': 'Toevoegen', - 'common.loading': 'Laden...', - 'common.import': 'Importeren', - 'common.select': 'Selecteren', - 'common.selectAll': 'Alles selecteren', - 'common.deselectAll': 'Alles deselecteren', - 'common.error': 'Fout', - 'common.unknownError': 'Onbekende fout', - 'common.tooManyAttempts': 'Te veel pogingen. Probeer het later opnieuw.', - 'common.back': 'Terug', - 'common.all': 'Alles', - 'common.close': 'Sluiten', - 'common.open': 'Openen', - 'common.upload': 'Uploaden', - 'common.search': 'Zoeken', - 'common.confirm': 'Bevestigen', - 'common.ok': 'OK', - 'common.yes': 'Ja', - 'common.no': 'Nee', - 'common.or': 'of', - 'common.none': 'Geen', - 'common.date': 'Datum', - 'common.rename': 'Hernoemen', - 'common.discardChanges': 'Wijzigingen verwerpen', - 'common.discard': 'Verwerpen', - 'common.name': 'Naam', - 'common.email': 'E-mail', - 'common.password': 'Wachtwoord', - 'common.saving': 'Opslaan...', - 'common.saved': 'Opgeslagen', - 'trips.memberRemoved': '{username} verwijderd', - 'trips.memberRemoveError': 'Verwijderen mislukt', - 'trips.memberAdded': '{username} toegevoegd', - 'trips.memberAddError': 'Toevoegen mislukt', - 'common.expand': 'Uitvouwen', - 'common.collapse': 'Inklappen', - 'trips.reminder': 'Herinnering', - 'trips.reminderNone': 'Geen', - 'trips.reminderDay': 'dag', - 'trips.reminderDays': 'dagen', - 'trips.reminderCustom': 'Aangepast', - 'trips.reminderDaysBefore': 'dagen voor vertrek', - 'trips.reminderDisabledHint': 'Reisherinneringen zijn uitgeschakeld. Schakel ze in via Admin > Instellingen > Meldingen.', - 'common.update': 'Bijwerken', - 'common.change': 'Wijzigen', - 'common.uploading': 'Uploaden…', - 'common.backToPlanning': 'Terug naar planning', - 'common.reset': 'Resetten', - - // Navbar - 'nav.trip': 'Reis', - 'nav.share': 'Delen', - 'nav.settings': 'Instellingen', - 'nav.admin': 'Admin', - 'nav.logout': 'Uitloggen', - 'nav.lightMode': 'Lichte modus', - 'nav.darkMode': 'Donkere modus', - 'nav.autoMode': 'Automatisch', - 'nav.administrator': 'Beheerder', - - // Dashboard - 'dashboard.title': 'Mijn reizen', - 'dashboard.subtitle.loading': 'Reizen laden...', - 'dashboard.subtitle.trips': '{count} reizen ({archived} gearchiveerd)', - 'dashboard.subtitle.empty': 'Begin je eerste reis', - 'dashboard.subtitle.activeOne': '{count} actieve reis', - 'dashboard.subtitle.activeMany': '{count} actieve reizen', - 'dashboard.subtitle.archivedSuffix': ' · {count} gearchiveerd', - 'dashboard.newTrip': 'Nieuwe reis', - 'dashboard.gridView': 'Rasterweergave', - 'dashboard.listView': 'Lijstweergave', - 'dashboard.currency': 'Valuta', - 'dashboard.timezone': 'Tijdzones', - 'dashboard.localTime': 'Lokaal', - 'dashboard.timezoneCustomTitle': 'Aangepaste tijdzone', - 'dashboard.timezoneCustomLabelPlaceholder': 'Label (optioneel)', - 'dashboard.timezoneCustomTzPlaceholder': 'bijv. America/New_York', - 'dashboard.timezoneCustomAdd': 'Toevoegen', - 'dashboard.timezoneCustomErrorEmpty': 'Voer een tijdzone-identificatie in', - 'dashboard.timezoneCustomErrorInvalid': 'Ongeldige tijdzone. Gebruik een formaat zoals Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Al toegevoegd', - 'dashboard.emptyTitle': 'Nog geen reizen', - 'dashboard.emptyText': 'Maak je eerste reis aan en begin met plannen!', - 'dashboard.emptyButton': 'Eerste reis aanmaken', - 'dashboard.nextTrip': 'Volgende reis', - 'dashboard.shared': 'Gedeeld', - 'dashboard.sharedBy': 'Gedeeld door {name}', - 'dashboard.days': 'Dagen', - 'dashboard.places': 'Plaatsen', - 'dashboard.members': 'Reisgenoten', - 'dashboard.archive': 'Archiveren', - 'dashboard.copyTrip': 'Kopiëren', - 'dashboard.copySuffix': 'kopie', - 'dashboard.restore': 'Herstellen', - 'dashboard.archived': 'Gearchiveerd', - 'dashboard.status.ongoing': 'Lopend', - 'dashboard.status.today': 'Vandaag', - 'dashboard.status.tomorrow': 'Morgen', - 'dashboard.status.past': 'Afgelopen', - 'dashboard.status.daysLeft': 'nog {count} dagen', - 'dashboard.toast.loadError': 'Reizen laden mislukt', - 'dashboard.toast.created': 'Reis aangemaakt!', - 'dashboard.toast.createError': 'Reis aanmaken mislukt', - 'dashboard.toast.updated': 'Reis bijgewerkt!', - 'dashboard.toast.updateError': 'Reis bijwerken mislukt', - 'dashboard.toast.deleted': 'Reis verwijderd', - 'dashboard.toast.deleteError': 'Reis verwijderen mislukt', - 'dashboard.toast.archived': 'Reis gearchiveerd', - 'dashboard.toast.archiveError': 'Reis archiveren mislukt', - 'dashboard.toast.restored': 'Reis hersteld', - 'dashboard.toast.restoreError': 'Reis herstellen mislukt', - 'dashboard.toast.copied': 'Reis gekopieerd!', - 'dashboard.toast.copyError': 'Reis kopiëren mislukt', - 'dashboard.confirm.delete': 'Reis "{title}" verwijderen? Alle plaatsen en plannen worden permanent verwijderd.', - 'dashboard.editTrip': 'Reis bewerken', - 'dashboard.createTrip': 'Nieuwe reis aanmaken', - 'dashboard.tripTitle': 'Titel', - 'dashboard.tripTitlePlaceholder': 'bijv. Zomer in Japan', - 'dashboard.tripDescription': 'Beschrijving', - 'dashboard.tripDescriptionPlaceholder': 'Waar gaat deze reis over?', - 'dashboard.startDate': 'Startdatum', - 'dashboard.endDate': 'Einddatum', - 'dashboard.dayCount': 'Aantal dagen', - 'dashboard.dayCountHint': 'Hoeveel dagen te plannen wanneer er geen reisdata zijn ingesteld.', - 'dashboard.noDateHint': 'Geen datum ingesteld — er worden standaard 7 dagen aangemaakt. Je kunt dit altijd wijzigen.', - 'dashboard.coverImage': 'Omslagafbeelding', - 'dashboard.addCoverImage': 'Omslagafbeelding toevoegen', - 'dashboard.addMembers': 'Reisgenoten', - 'dashboard.addMember': 'Lid toevoegen', - 'dashboard.coverSaved': 'Omslagafbeelding opgeslagen', - 'dashboard.coverUploadError': 'Uploaden mislukt', - 'dashboard.coverRemoveError': 'Verwijderen mislukt', - 'dashboard.titleRequired': 'Titel is verplicht', - 'dashboard.endDateError': 'Einddatum moet na de startdatum liggen', - - // Settings - 'settings.title': 'Instellingen', - 'settings.subtitle': 'Configureer je persoonlijke instellingen', - 'settings.tabs.display': 'Weergave', - 'settings.tabs.map': 'Kaart', - 'settings.tabs.notifications': 'Meldingen', - 'settings.tabs.integrations': 'Integraties', - 'settings.tabs.account': 'Account', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'Over', - 'settings.map': 'Kaart', - 'settings.mapTemplate': 'Kaartsjabloon', - 'settings.mapTemplatePlaceholder.select': 'Selecteer sjabloon...', - 'settings.mapDefaultHint': 'Laat leeg voor OpenStreetMap (standaard)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL-sjabloon voor kaarttegels', - 'settings.mapProvider': 'Kaartprovider', - 'settings.mapProviderHint': 'Geldt voor Trip Planner en Journey kaarten. Atlas gebruikt altijd Leaflet.', - 'settings.mapLeafletSubtitle': 'Klassiek 2D, elke raster-tile', - 'settings.mapMapboxSubtitle': 'Vector tiles, 3D-gebouwen & terrein', - 'settings.mapExperimental': 'Experimenteel', - 'settings.mapMapboxToken': 'Mapbox Access Token', - 'settings.mapMapboxTokenHint': 'Openbaar token (pk.*) van', - 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', - 'settings.mapStyle': 'Kaartstijl', - 'settings.mapStylePlaceholder': 'Kies een Mapbox-stijl', - 'settings.mapStyleHint': 'Preset of eigen mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D-gebouwen & terrein', - 'settings.map3dHint': 'Kanteling + echte 3D-gebouwenextrusies — werkt op elke stijl, inclusief satelliet.', - 'settings.mapHighQuality': 'Hoge kwaliteit modus', - 'settings.mapHighQualityHint': 'Antialiasing + globeprojectie voor scherpere randen en een realistische wereldweergave.', - 'settings.mapHighQualityWarning': 'Kan de prestaties op minder krachtige apparaten beïnvloeden.', - 'settings.mapTipLabel': 'Tip:', - 'settings.mapTip': 'Rechts-klik en sleep om de kaart te roteren/kantelen. Middenklik om een locatie toe te voegen (rechts-klik is voor rotatie).', - 'settings.latitude': 'Breedtegraad', - 'settings.longitude': 'Lengtegraad', - 'settings.saveMap': 'Kaart opslaan', - 'settings.apiKeys': 'API-sleutels', - 'settings.mapsKey': 'Google Maps API-sleutel', - 'settings.mapsKeyHint': 'Voor plaatsen zoeken. Vereist Places API (New). Verkrijgbaar op console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API-sleutel', - 'settings.weatherKeyHint': 'Voor weergegevens. Gratis op openweathermap.org/api', - 'settings.keyPlaceholder': 'Sleutel invoeren...', - 'settings.configured': 'Geconfigureerd', - 'settings.saveKeys': 'Sleutels opslaan', - 'settings.display': 'Weergave', - 'settings.colorMode': 'Kleurmodus', - 'settings.light': 'Licht', - 'settings.dark': 'Donker', - 'settings.auto': 'Automatisch', - 'settings.language': 'Taal', - 'settings.temperature': 'Temperatuureenheid', - 'settings.timeFormat': 'Tijdnotatie', - 'settings.blurBookingCodes': 'Boekingscodes vervagen', - 'settings.notifications': 'Meldingen', - 'settings.notifyTripInvite': 'Reisuitnodigingen', - 'settings.notifyBookingChange': 'Boekingswijzigingen', - 'settings.notifyTripReminder': 'Reisherinneringen', - 'settings.notifyTodoDue': 'Taak verloopt', - 'settings.notifyVacayInvite': 'Vacay-fusieuitnodigingen', - 'settings.notifyPhotosShared': 'Gedeelde foto\'s (Immich)', - 'settings.notifyCollabMessage': 'Chatberichten (Collab)', - 'settings.notifyPackingTagged': 'Paklijst: toewijzingen', - 'settings.notifyWebhook': 'Webhook-meldingen', - 'settings.notificationsDisabled': 'Meldingen zijn niet geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te schakelen.', - 'settings.notificationsActive': 'Actief kanaal', - 'settings.notificationsManagedByAdmin': 'Meldingsgebeurtenissen worden geconfigureerd door je beheerder.', - 'admin.notifications.title': 'Meldingen', - 'admin.notifications.hint': 'Kies een meldingskanaal. Er kan er slechts één tegelijk actief zijn.', - 'admin.notifications.none': 'Uitgeschakeld', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Meldingsinstellingen opslaan', - 'admin.notifications.saved': 'Meldingsinstellingen opgeslagen', - 'admin.notifications.testWebhook': 'Testwebhook verzenden', - 'admin.notifications.testWebhookSuccess': 'Testwebhook succesvol verzonden', - 'admin.notifications.testWebhookFailed': 'Testwebhook mislukt', - 'admin.smtp.title': 'E-mail en meldingen', - 'admin.smtp.hint': 'SMTP-configuratie voor het verzenden van e-mailmeldingen.', - 'admin.smtp.testButton': 'Test-e-mail verzenden', - 'admin.webhook.hint': 'Meldingen verzenden naar een externe webhook (Discord, Slack, enz.).', - 'admin.smtp.testSuccess': 'Test-e-mail succesvol verzonden', - 'admin.smtp.testFailed': 'Test-e-mail mislukt', - 'dayplan.icsTooltip': 'Kalender exporteren (ICS)', - 'share.linkTitle': 'Openbare link', - 'share.linkHint': 'Maak een link die iedereen kan gebruiken om deze reis te bekijken zonder in te loggen. Alleen-lezen — bewerken niet mogelijk.', - 'share.createLink': 'Link aanmaken', - 'share.deleteLink': 'Link verwijderen', - 'share.createError': 'Kon link niet aanmaken', - 'common.copy': 'Kopiëren', - 'common.copied': 'Gekopieerd', - 'share.permMap': 'Kaart en plan', - 'share.permBookings': 'Boekingen', - 'share.permPacking': 'Paklijst', - 'shared.expired': 'Link verlopen of ongeldig', - 'shared.expiredHint': 'Deze gedeelde reislink is niet meer actief.', - 'shared.readOnly': 'Alleen-lezen weergave', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Boekingen', - 'shared.tabPacking': 'Paklijst', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'dagen', - 'shared.places': 'plaatsen', - 'shared.other': 'Overig', - 'shared.totalBudget': 'Totaal budget', - 'shared.messages': 'berichten', - 'shared.sharedVia': 'Gedeeld via', - 'shared.confirmed': 'Bevestigd', - 'shared.pending': 'In afwachting', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - 'settings.on': 'Aan', - 'settings.off': 'Uit', - 'settings.mcp.title': 'MCP-configuratie', - 'settings.mcp.endpoint': 'MCP-eindpunt', - 'settings.mcp.clientConfig': 'Clientconfiguratie', - 'settings.mcp.clientConfigHint': 'Vervang door een API-token uit de onderstaande lijst. Het pad naar npx moet mogelijk worden aangepast voor jouw systeem (bijv. C:\\PROGRA~1\\nodejs\\npx.cmd op Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).', - 'settings.mcp.copy': 'Kopiëren', - 'settings.mcp.copied': 'Gekopieerd!', - 'settings.mcp.apiTokens': 'API-tokens', - 'settings.mcp.createToken': 'Nieuw token aanmaken', - 'settings.mcp.noTokens': 'Nog geen tokens. Maak er een aan om MCP-clients te verbinden.', - 'settings.mcp.tokenCreatedAt': 'Aangemaakt', - 'settings.mcp.tokenUsedAt': 'Gebruikt', - 'settings.mcp.deleteTokenTitle': 'Token verwijderen', - 'settings.mcp.deleteTokenMessage': 'Dit token werkt onmiddellijk niet meer. Elke MCP-client die het gebruikt verliest de toegang.', - 'settings.mcp.modal.createTitle': 'API-token aanmaken', - 'settings.mcp.modal.tokenName': 'Tokennaam', - 'settings.mcp.modal.tokenNamePlaceholder': 'bijv. Claude Desktop, Werklaptop', - 'settings.mcp.modal.creating': 'Aanmaken…', - 'settings.mcp.modal.create': 'Token aanmaken', - 'settings.mcp.modal.createdTitle': 'Token aangemaakt', - 'settings.mcp.modal.createdWarning': 'Dit token wordt slechts één keer getoond. Kopieer en bewaar het nu — het kan niet worden hersteld.', - 'settings.mcp.modal.done': 'Klaar', - 'settings.mcp.toast.created': 'Token aangemaakt', - 'settings.mcp.toast.createError': 'Token aanmaken mislukt', - 'settings.mcp.toast.deleted': 'Token verwijderd', - 'settings.mcp.toast.deleteError': 'Token verwijderen mislukt', - 'settings.mcp.apiTokensDeprecated': 'API-tokens zijn verouderd en worden in een toekomstige versie verwijderd. Gebruik OAuth 2.1-clients in plaats daarvan.', - 'settings.oauth.clients': 'OAuth 2.1-clients', - 'settings.oauth.clientsHint': 'Registreer OAuth 2.1-clients zodat externe MCP-toepassingen (Claude Web, Cursor, enz.) verbinding kunnen maken zonder statische tokens.', - 'settings.oauth.createClient': 'Nieuwe client', - 'settings.oauth.noClients': 'Geen OAuth-clients geregistreerd.', - 'settings.oauth.clientId': 'Client-ID', - 'settings.oauth.clientSecret': 'Clientgeheim', - 'settings.oauth.deleteClient': 'Client verwijderen', - 'settings.oauth.deleteClientMessage': 'Deze client en alle actieve sessies worden permanent verwijderd. Elke toepassing die deze client gebruikt, verliest onmiddellijk de toegang.', - 'settings.oauth.rotateSecret': 'Geheim vernieuwen', - 'settings.oauth.rotateSecretMessage': 'Er wordt een nieuw clientgeheim gegenereerd en alle bestaande sessies worden direct ongeldig. Werk uw toepassing bij voordat u dit venster sluit.', - 'settings.oauth.rotateSecretConfirm': 'Vernieuwen', - 'settings.oauth.rotateSecretConfirming': 'Vernieuwen…', - 'settings.oauth.rotateSecretDoneTitle': 'Nieuw geheim gegenereerd', - 'settings.oauth.rotateSecretDoneWarning': 'Dit geheim wordt slechts eenmalig getoond. Kopieer het nu en werk uw toepassing bij — alle vorige sessies zijn ongeldig gemaakt.', - 'settings.oauth.activeSessions': 'Actieve OAuth-sessies', - 'settings.oauth.sessionScopes': 'Rechten', - 'settings.oauth.sessionExpires': 'Verloopt', - 'settings.oauth.revoke': 'Intrekken', - 'settings.oauth.revokeSession': 'Sessie intrekken', - 'settings.oauth.revokeSessionMessage': 'Dit trekt onmiddellijk de toegang voor deze OAuth-sessie in.', - 'settings.oauth.modal.createTitle': 'OAuth-client registreren', - 'settings.oauth.modal.presets': 'Snelle instellingen', - 'settings.oauth.modal.clientName': 'Toepassingsnaam', - 'settings.oauth.modal.clientNamePlaceholder': 'bijv. Claude Web, Mijn MCP-app', - 'settings.oauth.modal.redirectUris': 'Redirect-URI\'s', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Eén URI per regel. HTTPS vereist (localhost uitgezonderd). Exacte overeenkomst vereist.', - 'settings.oauth.modal.scopes': 'Toegestane rechten', - 'settings.oauth.modal.scopesHint': 'list_trips en get_trip_summary zijn altijd beschikbaar — geen recht vereist. Ze helpen de AI trip-ID\'s te ontdekken.', - 'settings.oauth.modal.selectAll': 'Alles selecteren', - 'settings.oauth.modal.deselectAll': 'Alles deselecteren', - 'settings.oauth.modal.creating': 'Registreren…', - 'settings.oauth.modal.create': 'Client registreren', - 'settings.oauth.modal.createdTitle': 'Client geregistreerd', - 'settings.oauth.modal.createdWarning': 'Het clientgeheim wordt slechts eenmalig getoond. Kopieer het nu — het kan niet worden hersteld.', - 'settings.oauth.toast.createError': 'OAuth-client kon niet worden geregistreerd', - 'settings.oauth.toast.deleted': 'OAuth-client verwijderd', - 'settings.oauth.toast.deleteError': 'OAuth-client kon niet worden verwijderd', - 'settings.oauth.toast.revoked': 'Sessie ingetrokken', - 'settings.oauth.toast.revokeError': 'Sessie kon niet worden ingetrokken', - 'settings.oauth.toast.rotateError': 'Clientgeheim kon niet worden vernieuwd', - 'settings.oauth.modal.machineClient': 'Machineclient (zonder browserinlog)', - 'settings.oauth.modal.machineClientHint': "Gebruikt de client_credentials grant — geen redirect-URI's nodig. Het token wordt direct verstrekt via client_id + client_secret en handelt namens jou binnen de geselecteerde scopes.", - 'settings.oauth.modal.machineClientUsage': 'Token ophalen: POST /oauth/token met grant_type=client_credentials, client_id en client_secret. Geen browser, geen vernieuwingstoken.', - 'settings.oauth.badge.machine': 'machine', - 'settings.account': 'Account', - 'settings.about': 'Over', - 'settings.about.reportBug': 'Bug melden', - 'settings.about.reportBugHint': 'Probleem gevonden? Laat het ons weten', - 'settings.about.featureRequest': 'Feature aanvragen', - 'settings.about.featureRequestHint': 'Stel een nieuwe functie voor', - 'settings.about.wikiHint': 'Documentatie en handleidingen', - 'settings.about.supporters.badge': 'Maandelijkse Steuners', - 'settings.about.supporters.title': 'Reisgezelschap voor TREK', - 'settings.about.supporters.subtitle': 'Terwijl jij je volgende route plant, plannen deze mensen mee aan de toekomst van TREK. Hun maandelijkse bijdrage gaat rechtstreeks naar ontwikkeling en echte uren — zodat TREK Open Source blijft.', - 'settings.about.supporters.since': 'steuner sinds {date}', - 'settings.about.supporters.tierEmpty': 'Wees de eerste', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK is een zelf-gehoste reisplanner die je helpt je reizen te organiseren van het eerste idee tot de laatste herinnering. Dagplanning, budget, paklijsten, foto\'s en nog veel meer — alles op één plek, op je eigen server.', - 'settings.about.madeWith': 'Gemaakt met', - 'settings.about.madeBy': 'door Maurice en een groeiende open-source community.', - 'settings.username': 'Gebruikersnaam', - 'settings.email': 'E-mail', - 'settings.role': 'Rol', - 'settings.roleAdmin': 'Beheerder', - 'settings.oidcLinked': 'Gekoppeld met', - 'settings.changePassword': 'Wachtwoord wijzigen', - 'settings.mustChangePassword': 'U moet uw wachtwoord wijzigen voordat u kunt doorgaan. Stel hieronder een nieuw wachtwoord in.', - 'settings.currentPassword': 'Huidig wachtwoord', - 'settings.currentPasswordRequired': 'Huidig wachtwoord is verplicht', - 'settings.newPassword': 'Nieuw wachtwoord', - 'settings.confirmPassword': 'Bevestig nieuw wachtwoord', - 'settings.updatePassword': 'Wachtwoord bijwerken', - 'settings.passwordRequired': 'Voer het huidige en nieuwe wachtwoord in', - 'settings.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten', - 'settings.passwordMismatch': 'Wachtwoorden komen niet overeen', - 'settings.passwordWeak': 'Wachtwoord moet hoofdletters, kleine letters, een cijfer en een speciaal teken bevatten', - 'settings.passwordChanged': 'Wachtwoord succesvol gewijzigd', - 'settings.deleteAccount': 'Account verwijderen', - 'settings.deleteAccountTitle': 'Account verwijderen?', - 'settings.deleteAccountWarning': 'Je account en al je reizen, plaatsen en bestanden worden permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.', - 'settings.deleteAccountConfirm': 'Permanent verwijderen', - 'settings.deleteBlockedTitle': 'Verwijderen niet mogelijk', - 'settings.deleteBlockedMessage': 'Je bent de enige beheerder. Maak eerst een andere gebruiker beheerder voordat je je account verwijdert.', - 'settings.roleUser': 'Gebruiker', - 'settings.saveProfile': 'Profiel opslaan', - 'settings.mfa.title': 'Tweefactorauthenticatie (2FA)', - 'settings.mfa.description': 'Voegt een tweede stap toe bij het inloggen. Gebruik een authenticator-app (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'Je beheerder vereist tweestapsverificatie. Stel hieronder een authenticator-app in voordat je verdergaat.', - 'settings.mfa.backupTitle': 'Back-upcodes', - 'settings.mfa.backupDescription': 'Gebruik deze eenmalige codes als je geen toegang meer hebt tot je authenticator-app.', - 'settings.mfa.backupWarning': 'Sla deze codes nu op. Elke code kan maar een keer worden gebruikt.', - 'settings.mfa.backupCopy': 'Codes kopiëren', - 'settings.mfa.backupDownload': 'TXT downloaden', - 'settings.mfa.backupPrint': 'Afdrukken / PDF', - 'settings.mfa.backupCopied': 'Back-upcodes gekopieerd', - 'settings.mfa.enabled': '2FA is ingeschakeld op je account.', - 'settings.mfa.disabled': '2FA is niet ingeschakeld.', - 'settings.mfa.setup': 'Authenticator instellen', - 'settings.mfa.scanQr': 'Scan deze QR-code met je app of voer de sleutel handmatig in.', - 'settings.mfa.secretLabel': 'Geheime sleutel (handmatige invoer)', - 'settings.mfa.codePlaceholder': '6-cijferige code', - 'settings.mfa.enable': '2FA inschakelen', - 'settings.mfa.cancelSetup': 'Annuleren', - 'settings.mfa.disableTitle': '2FA uitschakelen', - 'settings.mfa.disableHint': 'Voer je wachtwoord en een huidige code van je authenticator in.', - 'settings.mfa.disable': '2FA uitschakelen', - 'settings.mfa.toastEnabled': 'Tweefactorauthenticatie ingeschakeld', - 'settings.mfa.toastDisabled': 'Tweefactorauthenticatie uitgeschakeld', - 'settings.mfa.demoBlocked': 'Niet beschikbaar in demomodus', - 'settings.toast.mapSaved': 'Kaartinstellingen opgeslagen', - 'settings.toast.keysSaved': 'API-sleutels opgeslagen', - 'settings.toast.displaySaved': 'Weergave-instellingen opgeslagen', - 'settings.toast.profileSaved': 'Profiel opgeslagen', - 'settings.uploadAvatar': 'Profielfoto uploaden', - 'settings.removeAvatar': 'Profielfoto verwijderen', - 'settings.avatarUploaded': 'Profielfoto bijgewerkt', - 'settings.avatarRemoved': 'Profielfoto verwijderd', - 'settings.avatarError': 'Uploaden mislukt', - - // Login - 'login.error': 'Inloggen mislukt. Controleer je inloggegevens.', - 'login.tagline': 'Jouw reizen.\nJouw plan.', - 'login.description': 'Plan reizen samen met interactieve kaarten, budgetten en realtime synchronisatie.', - 'login.features.maps': 'Interactieve kaarten', - 'login.features.mapsDesc': 'Google Places, routes en clustering', - 'login.features.realtime': 'Realtime synchronisatie', - 'login.features.realtimeDesc': 'Plan samen via WebSocket', - 'login.features.budget': 'Budgetbeheer', - 'login.features.budgetDesc': 'Categorieën, grafieken en kosten per persoon', - 'login.features.collab': 'Samenwerking', - 'login.features.collabDesc': 'Meerdere gebruikers met gedeelde reizen', - 'login.features.packing': 'Paklijsten', - 'login.features.packingDesc': 'Categorieën, voortgang en suggesties', - 'login.features.bookings': 'Reserveringen', - 'login.features.bookingsDesc': 'Vluchten, hotels, restaurants en meer', - 'login.features.files': 'Documenten', - 'login.features.filesDesc': 'Upload en beheer documenten', - 'login.features.routes': 'Slimme routes', - 'login.features.routesDesc': 'Automatisch optimaliseren en exporteren naar Google Maps', - 'login.selfHosted': 'Zelf gehost · Open Source · Jouw gegevens blijven van jou', - 'login.title': 'Inloggen', - 'login.subtitle': 'Welkom terug', - 'login.signingIn': 'Inloggen…', - 'login.signIn': 'Inloggen', - 'login.createAdmin': 'Beheerdersaccount aanmaken', - 'login.createAdminHint': 'Stel het eerste beheerdersaccount in voor TREK.', - 'login.setNewPassword': 'Nieuw wachtwoord instellen', - 'login.setNewPasswordHint': 'U moet uw wachtwoord wijzigen voordat u verder kunt gaan.', - 'login.createAccount': 'Account aanmaken', - 'login.createAccountHint': 'Registreer een nieuw account.', - 'login.creating': 'Aanmaken…', - 'login.noAccount': 'Nog geen account?', - 'login.hasAccount': 'Heb je al een account?', - 'login.register': 'Registreren', - 'login.emailPlaceholder': 'jouw@email.com', - 'login.username': 'Gebruikersnaam', - 'login.oidc.registrationDisabled': 'Registratie is uitgeschakeld. Neem contact op met je beheerder.', - 'login.oidc.noEmail': 'Geen e-mailadres ontvangen van de provider.', - 'login.mfaTitle': 'Tweefactorauthenticatie', - 'login.mfaSubtitle': 'Voer de 6-cijferige code van je authenticator-app in.', - 'login.mfaCodeLabel': 'Verificatiecode', - 'login.mfaCodeRequired': 'Voer de code van je authenticator-app in.', - 'login.mfaHint': 'Open Google Authenticator, Authy of een andere TOTP-app.', - 'login.mfaBack': '← Terug naar inloggen', - 'login.mfaVerify': 'Verifiëren', - 'login.invalidInviteLink': 'Ongeldige of verlopen uitnodigingslink', - 'login.oidcFailed': 'OIDC-aanmelding mislukt', - 'login.usernameRequired': 'Gebruikersnaam is vereist', - 'login.passwordMinLength': 'Wachtwoord moet minimaal 8 tekens bevatten', - 'login.forgotPassword': 'Wachtwoord vergeten?', - 'login.forgotPasswordTitle': 'Wachtwoord resetten', - 'login.forgotPasswordBody': 'Voer het e-mailadres van je account in. Als er een account bestaat, sturen we een resetlink.', - 'login.forgotPasswordSubmit': 'Resetlink verzenden', - 'login.forgotPasswordSentTitle': 'Controleer je e-mail', - 'login.forgotPasswordSentBody': 'Als er een account bestaat met dit adres, is de resetlink onderweg. Hij verloopt over 60 minuten.', - 'login.forgotPasswordSmtpHintOff': 'Let op: de beheerder heeft SMTP niet ingesteld. De resetlink wordt naar de serverconsole geschreven in plaats van via e-mail verzonden.', - 'login.backToLogin': 'Terug naar inloggen', - 'login.newPassword': 'Nieuw wachtwoord', - 'login.confirmPassword': 'Nieuw wachtwoord bevestigen', - 'login.passwordsDontMatch': 'Wachtwoorden komen niet overeen', - 'login.mfaCode': '2FA-code', - 'login.resetPasswordTitle': 'Nieuw wachtwoord instellen', - 'login.resetPasswordBody': 'Kies een sterk wachtwoord dat je hier nog niet hebt gebruikt. Minimaal 8 tekens.', - 'login.resetPasswordMfaBody': 'Voer je 2FA-code of een back-upcode in om de reset te voltooien.', - 'login.resetPasswordSubmit': 'Wachtwoord resetten', - 'login.resetPasswordVerify': 'Verifiëren en resetten', - 'login.resetPasswordSuccessTitle': 'Wachtwoord bijgewerkt', - 'login.resetPasswordSuccessBody': 'Je kunt nu inloggen met je nieuwe wachtwoord.', - 'login.resetPasswordInvalidLink': 'Ongeldige resetlink', - 'login.resetPasswordInvalidLinkBody': 'Deze link ontbreekt of is ongeldig. Vraag een nieuwe aan om door te gaan.', - 'login.resetPasswordFailed': 'Resetten mislukt. De link is mogelijk verlopen.', - 'login.oidc.tokenFailed': 'Authenticatie mislukt.', - 'login.oidc.invalidState': 'Ongeldige sessie. Probeer het opnieuw.', - 'login.demoFailed': 'Demo-login mislukt', - 'login.oidcSignIn': 'Inloggen met {name}', - 'login.oidcOnly': 'Wachtwoordauthenticatie is uitgeschakeld. Log in via je SSO-provider.', - 'login.oidcLoggedOut': 'Je bent uitgelogd. Log opnieuw in via je SSO-provider.', - 'login.demoHint': 'Probeer de demo — geen registratie nodig', - - // Register - 'register.passwordMismatch': 'Wachtwoorden komen niet overeen', - 'register.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten', - 'register.failed': 'Registratie mislukt', - 'register.getStarted': 'Aan de slag', - 'register.subtitle': 'Maak een account aan en begin met het plannen van je droomreizen.', - 'register.feature1': 'Onbeperkte reisplannen', - 'register.feature2': 'Interactieve kaartweergave', - 'register.feature3': 'Beheer plaatsen en categorieën', - 'register.feature4': 'Houd reserveringen bij', - 'register.feature5': 'Maak paklijsten', - 'register.feature6': 'Bewaar foto\'s en bestanden', - 'register.createAccount': 'Account aanmaken', - 'register.startPlanning': 'Begin met het plannen van je reis', - 'register.minChars': 'Min. 6 tekens', - 'register.confirmPassword': 'Bevestig wachtwoord', - 'register.repeatPassword': 'Herhaal wachtwoord', - 'register.registering': 'Registreren...', - 'register.register': 'Registreren', - 'register.hasAccount': 'Heb je al een account?', - 'register.signIn': 'Inloggen', - - // Admin - 'admin.title': 'Beheer', - 'admin.subtitle': 'Gebruikersbeheer en systeeminstellingen', - 'admin.tabs.users': 'Gebruikers', - 'admin.tabs.categories': 'Categorieën', - 'admin.tabs.backup': 'Back-up', - 'admin.tabs.audit': 'Audit', - 'admin.stats.users': 'Gebruikers', - 'admin.stats.trips': 'Reizen', - 'admin.stats.places': 'Plaatsen', - 'admin.stats.photos': 'Foto\'s', - 'admin.stats.files': 'Bestanden', - 'admin.table.user': 'Gebruiker', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Rol', - 'admin.table.created': 'Aangemaakt', - 'admin.table.lastLogin': 'Laatste login', - 'admin.table.actions': 'Acties', - 'admin.you': '(Jij)', - 'admin.editUser': 'Gebruiker bewerken', - 'admin.newPassword': 'Nieuw wachtwoord', - 'admin.newPasswordHint': 'Laat leeg om het huidige wachtwoord te behouden', - 'admin.deleteUser': 'Gebruiker "{name}" verwijderen? Alle reizen worden permanent verwijderd.', - 'admin.deleteUserTitle': 'Gebruiker verwijderen', - 'admin.newPasswordPlaceholder': 'Nieuw wachtwoord invoeren…', - 'admin.toast.loadError': 'Beheergegevens laden mislukt', - 'admin.toast.userUpdated': 'Gebruiker bijgewerkt', - 'admin.toast.updateError': 'Bijwerken mislukt', - 'admin.toast.userDeleted': 'Gebruiker verwijderd', - 'admin.toast.deleteError': 'Verwijderen mislukt', - 'admin.toast.cannotDeleteSelf': 'Je kunt je eigen account niet verwijderen', - 'admin.toast.userCreated': 'Gebruiker aangemaakt', - 'admin.toast.createError': 'Gebruiker aanmaken mislukt', - 'admin.toast.fieldsRequired': 'Gebruikersnaam, e-mail en wachtwoord zijn verplicht', - 'admin.createUser': 'Gebruiker aanmaken', - 'admin.invite.title': 'Uitnodigingslinks', - 'admin.invite.subtitle': 'Eenmalige registratielinks aanmaken', - 'admin.invite.create': 'Link aanmaken', - 'admin.invite.createAndCopy': 'Aanmaken en kopiëren', - 'admin.invite.empty': 'Nog geen uitnodigingslinks aangemaakt', - 'admin.invite.maxUses': 'Max. gebruik', - 'admin.invite.expiry': 'Verloopt na', - 'admin.invite.uses': 'gebruikt', - 'admin.invite.expiresAt': 'verloopt op', - 'admin.invite.createdBy': 'door', - 'admin.invite.active': 'Actief', - 'admin.invite.expired': 'Verlopen', - 'admin.invite.usedUp': 'Opgebruikt', - 'admin.invite.copied': 'Uitnodigingslink gekopieerd', - 'admin.invite.copyLink': 'Link kopiëren', - 'admin.invite.deleted': 'Uitnodigingslink verwijderd', - 'admin.invite.createError': 'Fout bij aanmaken van link', - 'admin.invite.deleteError': 'Fout bij verwijderen van link', - 'admin.tabs.settings': 'Instellingen', - 'admin.allowRegistration': 'Registratie toestaan', - 'admin.allowRegistrationHint': 'Nieuwe gebruikers kunnen zichzelf registreren', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Tweestapsverificatie (2FA) verplicht stellen', - 'admin.requireMfaHint': 'Gebruikers zonder 2FA moeten de installatie in Instellingen voltooien voordat ze de app kunnen gebruiken.', - 'admin.apiKeys': 'API-sleutels', - 'admin.apiKeysHint': 'Optioneel. Schakelt uitgebreide plaatsgegevens in zoals foto\'s en weer.', - 'admin.mapsKey': 'Google Maps API-sleutel', - 'admin.mapsKeyHint': 'Vereist voor het zoeken van plaatsen. Verkrijgbaar op console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Zonder API-sleutel wordt OpenStreetMap gebruikt voor het zoeken van plaatsen. Met een Google API-sleutel kunnen ook foto\'s, beoordelingen en openingstijden worden geladen. Verkrijgbaar op console.cloud.google.com.', - 'admin.recommended': 'Aanbevolen', - 'admin.weatherKey': 'OpenWeatherMap API-sleutel', - 'admin.weatherKeyHint': 'Voor weergegevens. Gratis op openweathermap.org', - 'admin.validateKey': 'Testen', - 'admin.keyValid': 'Verbonden', - 'admin.keyInvalid': 'Ongeldig', - 'admin.keySaved': 'API-sleutels opgeslagen', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Sta inloggen toe via externe providers zoals Google, Apple, Authentik of Keycloak.', - 'admin.oidcDisplayName': 'Weergavenaam', - 'admin.oidcIssuer': 'Issuer-URL', - 'admin.oidcIssuerHint': 'De OpenID Connect Issuer-URL van de provider. Bijv. https://accounts.google.com', - 'admin.oidcSaved': 'OIDC-configuratie opgeslagen', - 'admin.oidcOnlyMode': 'Wachtwoordauthenticatie uitschakelen', - 'admin.oidcOnlyModeHint': 'Indien ingeschakeld, is alleen SSO-login toegestaan. Inloggen en registreren met wachtwoord worden geblokkeerd.', - - // File Types - 'admin.fileTypes': 'Toegestane bestandstypen', - 'admin.fileTypesHint': 'Configureer welke bestandstypen gebruikers kunnen uploaden.', - 'admin.fileTypesFormat': 'Kommagescheiden extensies (bijv. jpg,png,pdf,doc). Gebruik * om alle typen toe te staan.', - 'admin.fileTypesSaved': 'Bestandstype-instellingen opgeslagen', - - 'admin.placesPhotos.title': "Plaatsfoto's", - 'admin.placesPhotos.subtitle': "Haalt foto's op via de Google Places API. Schakel uit om API-quota te besparen. Wikimedia-foto's worden niet beïnvloed.", - 'admin.placesAutocomplete.title': 'Plaatsautocomplete', - 'admin.placesAutocomplete.subtitle': 'Gebruikt de Google Places API voor zoeksuggesties. Schakel uit om API-quota te besparen.', - 'admin.placesDetails.title': 'Plaatsdetails', - 'admin.placesDetails.subtitle': 'Haalt gedetailleerde plaatsinformatie (openingstijden, beoordeling, website) op via de Google Places API. Schakel uit om API-quota te besparen.', - 'admin.bagTracking.title': 'Bagagetracking', - 'admin.bagTracking.subtitle': 'Gewicht en bagagetoewijzing inschakelen voor paklijstitems', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Realtime berichten voor reissamenwerking', - 'admin.collab.notes.title': 'Notities', - 'admin.collab.notes.subtitle': 'Gedeelde notities en documenten', - 'admin.collab.polls.title': 'Polls', - 'admin.collab.polls.subtitle': 'Groepspolls en stemmen', - 'admin.collab.whatsnext.title': 'Wat nu', - 'admin.collab.whatsnext.subtitle': 'Activiteitssuggesties en volgende stappen', - 'admin.tabs.config': 'Personalisatie', - 'admin.tabs.defaults': 'Standaardinstellingen', - 'admin.defaultSettings.title': 'Standaard gebruikersinstellingen', - 'admin.defaultSettings.description': 'Stel instantiebrede standaardwaarden in. Gebruikers die een instelling niet hebben gewijzigd, zien deze waarden. Hun eigen wijzigingen hebben altijd voorrang.', - 'admin.defaultSettings.saved': 'Standaard opgeslagen', - 'admin.defaultSettings.reset': 'Terugzetten naar ingebouwde standaard', - 'admin.defaultSettings.resetToBuiltIn': 'terugzetten', - 'admin.tabs.templates': 'Paksjablonen', - 'admin.packingTemplates.title': 'Paksjablonen', - 'admin.packingTemplates.subtitle': 'Herbruikbare paklijsten maken voor je reizen', - 'admin.packingTemplates.create': 'Nieuw sjabloon', - 'admin.packingTemplates.namePlaceholder': 'Sjabloonnaam (bijv. Strandvakantie)', - 'admin.packingTemplates.empty': 'Nog geen sjablonen aangemaakt', - 'admin.packingTemplates.items': 'items', - 'admin.packingTemplates.categories': 'categorieën', - 'admin.packingTemplates.itemName': 'Itemnaam', - 'admin.packingTemplates.itemCategory': 'Categorie', - 'admin.packingTemplates.categoryName': 'Categorienaam (bijv. Kleding)', - 'admin.packingTemplates.addCategory': 'Categorie toevoegen', - 'admin.packingTemplates.created': 'Sjabloon aangemaakt', - 'admin.packingTemplates.deleted': 'Sjabloon verwijderd', - 'admin.packingTemplates.loadError': 'Fout bij laden van sjablonen', - 'admin.packingTemplates.createError': 'Fout bij aanmaken van sjabloon', - 'admin.packingTemplates.deleteError': 'Fout bij verwijderen van sjabloon', - 'admin.packingTemplates.saveError': 'Fout bij opslaan', - - // Addons - 'admin.tabs.addons': 'Add-ons', - 'admin.addons.title': 'Add-ons', - 'admin.addons.subtitle': 'Schakel functies in of uit om je TREK-ervaring aan te passen.', - 'admin.addons.catalog.memories.name': 'Foto\'s (Immich)', - 'admin.addons.catalog.memories.description': 'Deel reisfoto\'s via je Immich-instantie', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol voor AI-assistent integratie', - 'admin.addons.catalog.packing.name': 'Lijsten', - 'admin.addons.catalog.packing.description': 'Paklijsten en to-dotaken voor je reizen', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Houd uitgaven bij en plan je reisbudget', - 'admin.addons.catalog.documents.name': 'Documenten', - 'admin.addons.catalog.documents.description': 'Bewaar en beheer reisdocumenten', - 'admin.addons.catalog.vacay.name': 'Vakantie', - 'admin.addons.catalog.vacay.description': 'Persoonlijke vakantieplanner met kalenderweergave', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Wereldkaart met bezochte landen en reisstatistieken', - 'admin.addons.catalog.collab.name': 'Samenwerking', - 'admin.addons.catalog.collab.description': 'Realtime notities, polls en chat voor het plannen van reizen', - 'admin.addons.subtitleBefore': 'Schakel functies in of uit om je ', - 'admin.addons.subtitleAfter': '-ervaring aan te passen.', - 'admin.addons.enabled': 'Ingeschakeld', - 'admin.addons.disabled': 'Uitgeschakeld', - 'admin.addons.type.trip': 'Reis', - 'admin.addons.type.global': 'Globaal', - 'admin.addons.type.integration': 'Integratie', - 'admin.addons.tripHint': 'Beschikbaar als tabblad binnen elke reis', - 'admin.addons.globalHint': 'Beschikbaar als zelfstandig onderdeel in de hoofdnavigatie', - 'admin.addons.integrationHint': 'Backenddiensten en API-integraties zonder eigen pagina', - 'admin.addons.toast.updated': 'Add-on bijgewerkt', - 'admin.addons.toast.error': 'Add-on bijwerken mislukt', - 'admin.addons.noAddons': 'Geen add-ons beschikbaar', - // Weather info - 'admin.weather.title': 'Weergegevens', - 'admin.weather.badge': 'Sinds 24 maart 2026', - 'admin.weather.description': 'TREK gebruikt Open-Meteo als weerbron. Open-Meteo is een gratis, open-source weerdienst — geen API-sleutel vereist.', - 'admin.weather.forecast': '16-daagse voorspelling', - 'admin.weather.forecastDesc': 'Voorheen 5 dagen (OpenWeatherMap)', - 'admin.weather.climate': 'Historische klimaatgegevens', - 'admin.weather.climateDesc': 'Gemiddelden over de afgelopen 85 jaar voor dagen buiten de 16-daagse voorspelling', - 'admin.weather.requests': '10.000 verzoeken / dag', - 'admin.weather.requestsDesc': 'Gratis, geen API-sleutel vereist', - 'admin.weather.locationHint': 'Het weer is gebaseerd op de eerste plaats met coördinaten op elke dag. Als er geen plaats aan een dag is toegewezen, wordt een plaats uit de lijst als referentie gebruikt.', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP-toegang', - 'admin.mcpTokens.title': 'MCP-toegang', - 'admin.mcpTokens.subtitle': 'OAuth-sessies en API-tokens van alle gebruikers beheren', - 'admin.mcpTokens.sectionTitle': 'API-tokens', - 'admin.mcpTokens.owner': 'Eigenaar', - 'admin.mcpTokens.tokenName': 'Tokennaam', - 'admin.mcpTokens.created': 'Aangemaakt', - 'admin.mcpTokens.lastUsed': 'Laatst gebruikt', - 'admin.mcpTokens.never': 'Nooit', - 'admin.mcpTokens.empty': 'Er zijn nog geen MCP-tokens aangemaakt', - 'admin.mcpTokens.deleteTitle': 'Token verwijderen', - 'admin.mcpTokens.deleteMessage': 'Dit token wordt onmiddellijk ingetrokken. De gebruiker verliest MCP-toegang via dit token.', - 'admin.mcpTokens.deleteSuccess': 'Token verwijderd', - 'admin.mcpTokens.deleteError': 'Token kon niet worden verwijderd', - 'admin.mcpTokens.loadError': 'Tokens konden niet worden geladen', - 'admin.oauthSessions.sectionTitle': 'OAuth-sessies', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Eigenaar', - 'admin.oauthSessions.scopes': 'Rechten', - 'admin.oauthSessions.created': 'Aangemaakt', - 'admin.oauthSessions.empty': 'Geen actieve OAuth-sessies', - 'admin.oauthSessions.revokeTitle': 'Sessie intrekken', - 'admin.oauthSessions.revokeMessage': 'Deze OAuth-sessie wordt onmiddellijk ingetrokken. De client verliest MCP-toegang.', - 'admin.oauthSessions.revokeSuccess': 'Sessie ingetrokken', - 'admin.oauthSessions.revokeError': 'Sessie kon niet worden ingetrokken', - 'admin.oauthSessions.loadError': 'OAuth-sessies konden niet worden geladen', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Beveiligingsgevoelige en beheerdersgebeurtenissen (back-ups, gebruikers, MFA, instellingen).', - 'admin.audit.empty': 'Nog geen auditregistraties.', - 'admin.audit.refresh': 'Vernieuwen', - 'admin.audit.loadMore': 'Meer laden', - 'admin.audit.showing': '{count} geladen · {total} totaal', - 'admin.audit.col.time': 'Tijd', - 'admin.audit.col.user': 'Gebruiker', - 'admin.audit.col.action': 'Actie', - 'admin.audit.col.resource': 'Bron', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Details', - - 'admin.github.title': 'Release-geschiedenis', - 'admin.github.subtitle': 'Laatste updates van {repo}', - 'admin.github.latest': 'Nieuwste', - 'admin.github.prerelease': 'Pre-release', - 'admin.github.showDetails': 'Details tonen', - 'admin.github.hideDetails': 'Details verbergen', - 'admin.github.loadMore': 'Meer laden', - 'admin.github.loading': 'Laden...', - 'admin.github.support': 'Helpt mij TREK verder te ontwikkelen', - 'admin.github.error': 'Releases laden mislukt', - 'admin.github.by': 'door', - - 'admin.update.available': 'Update beschikbaar', - 'admin.update.text': 'TREK {version} is beschikbaar. Je draait {current}.', - 'admin.update.button': 'Bekijk op GitHub', - 'admin.update.install': 'Update installeren', - 'admin.update.confirmTitle': 'Update installeren?', - 'admin.update.confirmText': 'TREK wordt bijgewerkt van {current} naar {version}. De server herstart automatisch.', - 'admin.update.dataInfo': 'Al je gegevens (reizen, gebruikers, API-sleutels, uploads, Vacay, Atlas, budgetten) worden bewaard.', - 'admin.update.warning': 'De app is kort niet beschikbaar tijdens het herstarten.', - 'admin.update.confirm': 'Nu bijwerken', - 'admin.update.installing': 'Bijwerken…', - 'admin.update.success': 'Update geïnstalleerd! Server herstart…', - 'admin.update.failed': 'Update mislukt', - 'admin.update.backupHint': 'We raden aan een back-up te maken voordat je bijwerkt.', - 'admin.update.backupLink': 'Naar back-up', - 'admin.update.howTo': 'Hoe bij te werken', - 'admin.update.dockerText': 'Je TREK-instantie draait in Docker. Om bij te werken naar {version}, voer de volgende commando\'s uit op je server:', - 'admin.update.reloadHint': 'Herlaad de pagina over een paar seconden.', - - // Vacay addon - 'vacay.subtitle': 'Plan en beheer vakantiedagen', - 'vacay.settings': 'Instellingen', - 'vacay.year': 'Jaar', - 'vacay.addYear': 'Volgend jaar toevoegen', - 'vacay.addPrevYear': 'Vorig jaar toevoegen', - 'vacay.removeYear': 'Jaar verwijderen', - 'vacay.removeYearConfirm': '{year} verwijderen?', - 'vacay.removeYearHint': 'Alle vakantie-invoeren en bedrijfsvakanties voor dit jaar worden permanent verwijderd.', - 'vacay.remove': 'Verwijderen', - 'vacay.persons': 'Personen', - 'vacay.noPersons': 'Geen personen toegevoegd', - 'vacay.addPerson': 'Persoon toevoegen', - 'vacay.editPerson': 'Persoon bewerken', - 'vacay.removePerson': 'Persoon verwijderen', - 'vacay.removePersonConfirm': '{name} verwijderen?', - 'vacay.removePersonHint': 'Alle vakantie-invoeren voor deze persoon worden permanent verwijderd.', - 'vacay.personName': 'Naam', - 'vacay.personNamePlaceholder': 'Naam invoeren', - 'vacay.color': 'Kleur', - 'vacay.add': 'Toevoegen', - 'vacay.legend': 'Legenda', - 'vacay.publicHoliday': 'Feestdag', - 'vacay.companyHoliday': 'Bedrijfsvakantie', - 'vacay.weekend': 'Weekend', - 'vacay.modeVacation': 'Vakantie', - 'vacay.modeCompany': 'Bedrijfsvakantie', - 'vacay.entitlement': 'Recht', - 'vacay.entitlementDays': 'Dagen', - 'vacay.used': 'Gebruikt', - 'vacay.remaining': 'Resterend', - 'vacay.carriedOver': 'van {year}', - 'vacay.blockWeekends': 'Weekenden blokkeren', - 'vacay.blockWeekendsHint': 'Voorkom vakantie-invoeren op zaterdag en zondag', - 'vacay.weekendDays': 'Weekenddagen', - 'vacay.mon': 'Ma', - 'vacay.tue': 'Di', - 'vacay.wed': 'Wo', - 'vacay.thu': 'Do', - 'vacay.fri': 'Vr', - 'vacay.sat': 'Za', - 'vacay.sun': 'Zo', - 'vacay.publicHolidays': 'Feestdagen', - 'vacay.publicHolidaysHint': 'Markeer feestdagen in de kalender', - 'vacay.selectCountry': 'Selecteer land', - 'vacay.selectRegion': 'Selecteer regio (optioneel)', - 'vacay.companyHolidays': 'Bedrijfsvakanties', - 'vacay.companyHolidaysHint': 'Sta het markeren van bedrijfsbrede vakantiedagen toe', - 'vacay.companyHolidaysNoDeduct': 'Bedrijfsvakanties worden niet afgetrokken van vakantiedagen.', - 'vacay.weekStart': 'Week begint op', - 'vacay.weekStartHint': 'Kies of de kalenderweek op maandag of zondag begint', - 'vacay.carryOver': 'Overdracht', - 'vacay.carryOverHint': 'Draag resterende vakantiedagen automatisch over naar het volgende jaar', - 'vacay.sharing': 'Delen', - 'vacay.sharingHint': 'Deel je vakantieplan met andere TREK-gebruikers', - 'vacay.owner': 'Eigenaar', - 'vacay.shareEmailPlaceholder': 'E-mail van TREK-gebruiker', - 'vacay.shareSuccess': 'Plan succesvol gedeeld', - 'vacay.shareError': 'Plan delen mislukt', - 'vacay.dissolve': 'Fusie opheffen', - 'vacay.dissolveHint': 'Kalenders weer scheiden. Je invoeren blijven behouden.', - 'vacay.dissolveAction': 'Opheffen', - 'vacay.dissolved': 'Kalender gescheiden', - 'vacay.fusedWith': 'Gefuseerd met', - 'vacay.you': 'jij', - 'vacay.noData': 'Geen gegevens', - 'vacay.changeColor': 'Kleur wijzigen', - 'vacay.inviteUser': 'Gebruiker uitnodigen', - 'vacay.inviteHint': 'Nodig een andere TREK-gebruiker uit om een gecombineerde vakantiekalender te delen.', - 'vacay.selectUser': 'Selecteer gebruiker', - 'vacay.sendInvite': 'Uitnodiging verzenden', - 'vacay.inviteSent': 'Uitnodiging verzonden', - 'vacay.inviteError': 'Uitnodiging verzenden mislukt', - 'vacay.pending': 'in behandeling', - 'vacay.noUsersAvailable': 'Geen gebruikers beschikbaar', - 'vacay.accept': 'Accepteren', - 'vacay.decline': 'Afwijzen', - 'vacay.acceptFusion': 'Accepteren en fuseren', - 'vacay.inviteTitle': 'Fusieverzoek', - 'vacay.inviteWantsToFuse': 'wil een vakantiekalender met je delen.', - 'vacay.fuseInfo1': 'Jullie zien allebei alle vakantie-invoeren in één gedeelde kalender.', - 'vacay.fuseInfo2': 'Beide partijen kunnen invoeren voor elkaar aanmaken en bewerken.', - 'vacay.fuseInfo3': 'Beide partijen kunnen invoeren verwijderen en vakantierechten wijzigen.', - 'vacay.fuseInfo4': 'Instellingen zoals feestdagen en bedrijfsvakanties worden gedeeld.', - 'vacay.fuseInfo5': 'De fusie kan op elk moment door beide partijen worden opgeheven. Je invoeren blijven behouden.', - 'vacay.addCalendar': 'Kalender toevoegen', - 'vacay.calendarColor': 'Kleur', - 'vacay.calendarLabel': 'Label', - 'vacay.noCalendars': 'Geen kalenders', - 'nav.myTrips': 'Mijn reizen', - - // Atlas addon - 'atlas.subtitle': 'Je reisvoetafdruk over de wereld', - 'atlas.countries': 'Landen', - 'atlas.trips': 'Reizen', - 'atlas.places': 'Plaatsen', - 'atlas.days': 'Dagen', - 'atlas.visitedCountries': 'Bezochte landen', - 'atlas.cities': 'Steden', - 'atlas.noData': 'Nog geen reisgegevens', - 'atlas.noDataHint': 'Maak een reis aan en voeg plaatsen toe om je wereldkaart te zien', - 'atlas.lastTrip': 'Laatste reis', - 'atlas.nextTrip': 'Volgende reis', - 'atlas.daysLeft': 'dagen te gaan', - 'atlas.streak': 'Reeks', - 'atlas.year': 'jaar', - 'atlas.years': 'jaar', - 'atlas.yearInRow': 'jaar op rij', - 'atlas.yearsInRow': 'jaar op rij', - 'atlas.tripIn': 'reis in', - 'atlas.tripsIn': 'reizen in', - 'atlas.since': 'sinds', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Azië', - 'atlas.northAmerica': 'N.-Amerika', - 'atlas.southAmerica': 'Z.-Amerika', - 'atlas.africa': 'Afrika', - 'atlas.oceania': 'Oceanië', - 'atlas.other': 'Overig', - 'atlas.firstVisit': 'Eerste reis', - 'atlas.lastVisitLabel': 'Laatste reis', - 'atlas.tripSingular': 'Reis', - 'atlas.tripPlural': 'Reizen', - 'atlas.placeVisited': 'Bezochte plaats', - 'atlas.placesVisited': 'Bezochte plaatsen', - 'atlas.statsTab': 'Statistieken', - 'atlas.bucketTab': 'Bucketlist', - 'atlas.addBucket': 'Toevoegen aan bucket list', - 'atlas.bucketNamePlaceholder': 'Plaats of bestemming...', - 'atlas.bucketNotesPlaceholder': 'Notities (optioneel)', - 'atlas.bucketEmpty': 'Je bucket list is leeg', - 'atlas.bucketEmptyHint': 'Voeg plekken toe die je wilt bezoeken', - 'atlas.unmark': 'Verwijderen', - 'atlas.confirmMark': 'Dit land als bezocht markeren?', - 'atlas.confirmUnmark': 'Dit land van je bezochte lijst verwijderen?', - 'atlas.confirmUnmarkRegion': 'Deze regio van je bezochte lijst verwijderen?', - 'atlas.markVisited': 'Markeren als bezocht', - 'atlas.markVisitedHint': 'Dit land toevoegen aan je bezochte lijst', - 'atlas.markRegionVisitedHint': 'Deze regio toevoegen aan je bezochte lijst', - 'atlas.addToBucket': 'Aan bucket list toevoegen', - 'atlas.addPoi': 'Plaats toevoegen', - 'atlas.searchCountry': 'Zoek een land...', - 'atlas.month': 'Maand', - 'atlas.addToBucketHint': 'Opslaan als plek die je wilt bezoeken', - 'atlas.bucketWhen': 'Wanneer ben je van plan te gaan?', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transport', - 'trip.tabs.reservations': 'Boekingen', - 'trip.tabs.reservationsShort': 'Boek', - 'trip.tabs.packing': 'Paklijst', - 'trip.tabs.packingShort': 'Inpakken', - 'trip.tabs.lists': 'Lijsten', - 'trip.tabs.listsShort': 'Lijsten', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'Bestanden', - 'trip.loading': 'Reis laden...', - 'trip.loadingPhotos': 'Plaatsfoto laden...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Plaatsen', - 'trip.toast.placeUpdated': 'Plaats bijgewerkt', - 'trip.toast.placeAdded': 'Plaats toegevoegd', - 'trip.toast.placeDeleted': 'Plaats verwijderd', - 'trip.toast.selectDay': 'Selecteer eerst een dag', - 'trip.toast.assignedToDay': 'Plaats toegewezen aan dag', - 'trip.toast.reorderError': 'Herordenen mislukt', - 'trip.toast.reservationUpdated': 'Reservering bijgewerkt', - 'trip.toast.reservationAdded': 'Reservering toegevoegd', - 'trip.toast.deleted': 'Verwijderd', - 'trip.confirm.deletePlace': 'Weet je zeker dat je deze plaats wilt verwijderen?', - 'trip.confirm.deletePlaces': '{count} plaatsen verwijderen?', - 'trip.toast.placesDeleted': '{count} plaatsen verwijderd', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Geen plaatsen gepland voor deze dag', - 'dayplan.addNote': 'Notitie toevoegen', - 'dayplan.editNote': 'Notitie bewerken', - 'dayplan.noteAdd': 'Notitie toevoegen', - 'dayplan.noteEdit': 'Notitie bewerken', - 'dayplan.noteTitle': 'Notitie', - 'dayplan.noteSubtitle': 'Dagnotitie', - 'dayplan.totalCost': 'Totale kosten', - 'dayplan.days': 'Dagen', - 'dayplan.dayN': 'Dag {n}', - 'dayplan.calculating': 'Berekenen...', - 'dayplan.route': 'Route', - 'dayplan.optimize': 'Optimaliseren', - 'dayplan.optimized': 'Route geoptimaliseerd', - 'dayplan.routeError': 'Route berekenen mislukt', - 'dayplan.toast.needTwoPlaces': 'Minimaal twee plaatsen nodig voor route-optimalisatie', - 'dayplan.toast.routeOptimized': 'Route geoptimaliseerd', - 'dayplan.toast.noGeoPlaces': 'Geen plaatsen met coördinaten gevonden voor routeberekening', - 'dayplan.confirmed': 'Bevestigd', - 'dayplan.pendingRes': 'In behandeling', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Dagplan exporteren als PDF', - 'dayplan.pdfError': 'PDF-export mislukt', - 'dayplan.cannotReorderTransport': 'Boekingen met een vast tijdstip kunnen niet worden verplaatst', - 'dayplan.confirmRemoveTimeTitle': 'Tijd verwijderen?', - 'dayplan.confirmRemoveTimeBody': 'Deze plek heeft een vast tijdstip ({time}). Verplaatsen verwijdert het tijdstip en maakt vrije sortering mogelijk.', - 'dayplan.confirmRemoveTimeAction': 'Tijd verwijderen en verplaatsen', - 'dayplan.cannotDropOnTimed': 'Items kunnen niet tussen tijdgebonden items worden geplaatst', - 'dayplan.cannotBreakChronology': 'Dit zou de chronologische volgorde van geplande items en boekingen doorbreken', - - // Places Sidebar - 'places.addPlace': 'Plaats/activiteit toevoegen', - 'places.importFile': 'Bestand importeren', - 'places.sidebarDrop': 'Loslaten om te importeren', - 'places.importFileHint': 'Importeer .gpx-, .kml- of .kmz-bestanden uit tools zoals Google My Maps, Google Earth of een GPS-tracker.', - 'places.importFileDropHere': 'Klik om een bestand te selecteren of sleep het hier naartoe', - 'places.importFileDropActive': 'Laat het bestand los om het te selecteren', - 'places.importFileUnsupported': 'Niet-ondersteund bestandstype. Gebruik .gpx, .kml of .kmz.', - 'places.importFileTooLarge': 'Bestand is te groot. Maximale uploadgrootte is {maxMb} MB.', - 'places.importFileError': 'Importeren mislukt', - 'places.importAllSkipped': 'Alle plaatsen waren al in de reis.', - 'places.gpxImported': '{count} plaatsen geïmporteerd uit GPX', - 'places.gpxImportTypes': 'Wat wil je importeren?', - 'places.gpxImportWaypoints': 'Waypoints', - 'places.gpxImportRoutes': 'Routes', - 'places.gpxImportTracks': 'Tracks (met routegeometrie)', - 'places.gpxImportNoneSelected': 'Selecteer minstens één type om te importeren.', - 'places.kmlImportTypes': 'Wat wil je importeren?', - 'places.kmlImportPoints': 'Punten (Placemarks)', - 'places.kmlImportPaths': 'Paden (LineStrings)', - 'places.kmlImportNoneSelected': 'Selecteer minstens één type.', - 'places.selectionCount': '{count} geselecteerd', - 'places.deleteSelected': 'Selectie verwijderen', - 'places.kmlKmzImported': '{count} plaatsen geïmporteerd uit KMZ/KML', - 'places.urlResolved': 'Plaats geïmporteerd van URL', - 'places.importList': 'Lijst importeren', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Geïmporteerd: {created} • Overgeslagen: {skipped}', - 'places.importGoogleList': 'Google Lijst', - 'places.importNaverList': 'Naver Lijst', - 'places.googleListHint': 'Plak een gedeelde Google Maps lijstlink om alle plaatsen te importeren.', - 'places.googleListImported': '{count} plaatsen geimporteerd uit "{list}"', - 'places.googleListError': 'Google Maps lijst importeren mislukt', - 'places.naverListHint': 'Plak een gedeelde Naver Maps lijstlink om alle plaatsen te importeren.', - 'places.naverListImported': '{count} plaatsen geimporteerd uit "{list}"', - 'places.naverListError': 'Naver Maps lijst importeren mislukt', - 'places.viewDetails': 'Details bekijken', - 'places.assignToDay': 'Aan welke dag toevoegen?', - 'places.all': 'Alle', - 'places.unplanned': 'Ongepland', - 'places.filterTracks': 'Tracks', - 'places.search': 'Plaatsen zoeken...', - 'places.allCategories': 'Alle categorieën', - 'places.categoriesSelected': 'categorieën', - 'places.clearFilter': 'Filter wissen', - 'places.count': '{count} plaatsen', - 'places.countSingular': '1 plaats', - 'places.allPlanned': 'Alle plaatsen zijn gepland', - 'places.noneFound': 'Geen plaatsen gevonden', - 'places.editPlace': 'Plaats bewerken', - 'places.formName': 'Naam', - 'places.formNamePlaceholder': 'bijv. Eiffeltoren', - 'places.formDescription': 'Beschrijving', - 'places.formDescriptionPlaceholder': 'Korte beschrijving...', - 'places.formAddress': 'Adres', - 'places.formAddressPlaceholder': 'Straat, stad, land', - 'places.formLat': 'Breedtegraad (bijv. 48.8566)', - 'places.formLng': 'Lengtegraad (bijv. 2.3522)', - 'places.formCategory': 'Categorie', - 'places.noCategory': 'Geen categorie', - 'places.categoryNamePlaceholder': 'Categorienaam', - 'places.formTime': 'Tijd', - 'places.startTime': 'Starttijd', - 'places.endTime': 'Einde', - 'places.endTimeBeforeStart': 'Eindtijd is vóór de starttijd', - 'places.timeCollision': 'Tijdoverlap met:', - 'places.formWebsite': 'Website', - 'places.formNotes': 'Notities', - 'places.formNotesPlaceholder': 'Persoonlijke notities...', - 'places.formReservation': 'Reservering', - 'places.reservationNotesPlaceholder': 'Reserveringsnotities, bevestigingsnummer...', - 'places.mapsSearchPlaceholder': 'Plaatsen zoeken...', - 'places.mapsSearchError': 'Zoeken naar plaatsen mislukt.', - 'places.loadingDetails': 'Plaatsgegevens laden…', - 'places.osmHint': 'Zoeken via OpenStreetMap (geen foto\'s, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.', - 'places.osmActive': 'Zoeken via OpenStreetMap (geen foto\'s, beoordelingen of openingstijden). Voeg een Google API-sleutel toe in Instellingen voor uitgebreide gegevens.', - 'places.categoryCreateError': 'Categorie aanmaken mislukt', - 'places.nameRequired': 'Voer een naam in', - 'places.saveError': 'Opslaan mislukt', - // Place Inspector - 'inspector.opened': 'Openingstijden', - 'inspector.closed': 'Gesloten', - 'inspector.openingHours': 'Openingstijden', - 'inspector.showHours': 'Openingstijden tonen', - 'inspector.files': 'Bestanden', - 'inspector.filesCount': '{count} bestanden', - 'inspector.removeFromDay': 'Verwijderen van dag', - 'inspector.remove': 'Verwijderen', - 'inspector.addToDay': 'Toevoegen aan dag', - 'inspector.confirmedRes': 'Bevestigde reservering', - 'inspector.pendingRes': 'Reservering in behandeling', - 'inspector.google': 'Openen in Google Maps', - 'inspector.website': 'Website openen', - 'inspector.addRes': 'Reservering', - 'inspector.editRes': 'Reservering bewerken', - 'inspector.participants': 'Deelnemers', - 'inspector.trackStats': 'Routegegevens', - - // Reservations - 'reservations.title': 'Boekingen', - 'reservations.empty': 'Nog geen reserveringen', - 'reservations.emptyHint': 'Voeg reserveringen toe voor vluchten, hotels en meer', - 'reservations.add': 'Reservering toevoegen', - 'reservations.addManual': 'Handmatige boeking', - 'reservations.placeHint': 'Tip: Reserveringen kun je het beste direct vanuit een plaats aanmaken om ze te koppelen aan je dagplan.', - 'reservations.confirmed': 'Bevestigd', - 'reservations.pending': 'In behandeling', - 'reservations.summary': '{confirmed} bevestigd, {pending} in behandeling', - 'reservations.fromPlan': 'Vanuit plan', - 'reservations.showFiles': 'Bestanden tonen', - 'reservations.editTitle': 'Reservering bewerken', - 'reservations.status': 'Status', - 'reservations.datetime': 'Datum en tijd', - 'reservations.startTime': 'Starttijd', - 'reservations.endTime': 'Eindtijd', - 'reservations.date': 'Datum', - 'reservations.time': 'Tijd', - 'reservations.timeAlt': 'Tijd (alternatief, bijv. 19:30)', - 'reservations.notes': 'Notities', - 'reservations.notesPlaceholder': 'Extra notities...', - 'reservations.meta.airline': 'Luchtvaartmaatschappij', - 'reservations.meta.flightNumber': 'Vluchtnr.', - 'reservations.meta.from': 'Van', - 'reservations.meta.to': 'Naar', - 'reservations.needsReview': 'Controleren', - 'reservations.needsReviewHint': 'Luchthaven kon niet automatisch worden herkend — bevestig de locatie.', - 'reservations.searchLocation': 'Station, haven, adres zoeken...', - 'airport.searchPlaceholder': 'Luchthavencode of stad (bijv. FRA)', - 'map.connections': 'Verbindingen', - 'map.showConnections': 'Boekingsroutes tonen', - 'map.hideConnections': 'Boekingsroutes verbergen', - 'settings.bookingLabels': 'Routelabels voor boekingen', - 'settings.bookingLabelsHint': 'Toon station- / luchthavennamen op de kaart. Indien uit, alleen het icoon.', - 'reservations.meta.trainNumber': 'Treinnr.', - 'reservations.meta.platform': 'Perron', - 'reservations.meta.seat': 'Stoel', - 'reservations.meta.checkIn': 'Inchecken', - 'reservations.meta.checkInUntil': 'Check-in tot', - 'reservations.meta.checkOut': 'Uitchecken', - 'reservations.meta.linkAccommodation': 'Accommodatie', - 'reservations.meta.pickAccommodation': 'Koppel aan accommodatie', - 'reservations.meta.noAccommodation': 'Geen', - 'reservations.meta.hotelPlace': 'Accommodatie', - 'reservations.meta.pickHotel': 'Selecteer accommodatie', - 'reservations.meta.fromDay': 'Van', - 'reservations.meta.toDay': 'Tot', - 'reservations.meta.selectDay': 'Selecteer dag', - 'reservations.type.flight': 'Vlucht', - 'reservations.type.hotel': 'Accommodatie', - 'reservations.type.restaurant': 'Restaurant', - 'reservations.type.train': 'Trein', - 'reservations.type.car': 'Auto', - 'reservations.type.cruise': 'Cruise', - 'reservations.type.event': 'Evenement', - 'reservations.type.tour': 'Rondleiding', - 'reservations.type.other': 'Overig', - 'reservations.confirm.delete': 'Weet je zeker dat je de reservering "{name}" wilt verwijderen?', - 'reservations.confirm.deleteTitle': 'Boeking verwijderen?', - 'reservations.confirm.deleteBody': '"{name}" wordt permanent verwijderd.', - 'reservations.toast.updated': 'Reservering bijgewerkt', - 'reservations.toast.removed': 'Reservering verwijderd', - 'reservations.toast.fileUploaded': 'Bestand geüpload', - 'reservations.toast.uploadError': 'Uploaden mislukt', - 'reservations.newTitle': 'Nieuwe reservering', - 'reservations.bookingType': 'Boekingstype', - 'reservations.titleLabel': 'Titel', - 'reservations.titlePlaceholder': 'bijv. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Locatie / Adres', - 'reservations.locationPlaceholder': 'Adres, luchthaven, hotel...', - 'reservations.confirmationCode': 'Boekingscode', - 'reservations.confirmationPlaceholder': 'bijv. ABC12345', - 'reservations.day': 'Dag', - 'reservations.noDay': 'Geen dag', - 'reservations.place': 'Plaats', - 'reservations.noPlace': 'Geen plaats', - 'reservations.pendingSave': 'wordt opgeslagen…', - 'reservations.uploading': 'Uploaden...', - 'reservations.attachFile': 'Bestand bijvoegen', - 'reservations.linkExisting': 'Bestaand bestand koppelen', - 'reservations.toast.saveError': 'Opslaan mislukt', - 'reservations.toast.updateError': 'Bijwerken mislukt', - 'reservations.toast.deleteError': 'Verwijderen mislukt', - 'reservations.confirm.remove': 'Reservering voor "{name}" verwijderen?', - 'reservations.linkAssignment': 'Koppelen aan dagtoewijzing', - 'reservations.pickAssignment': 'Selecteer een toewijzing uit je plan...', - 'reservations.noAssignment': 'Geen koppeling (zelfstandig)', - 'reservations.price': 'Prijs', - 'reservations.budgetCategory': 'Budgetcategorie', - 'reservations.budgetCategoryPlaceholder': 'bijv. Transport, Accommodatie', - 'reservations.budgetCategoryAuto': 'Automatisch (op basis van boekingstype)', - 'reservations.budgetHint': 'Er wordt automatisch een budgetpost aangemaakt bij het opslaan.', - 'reservations.departureDate': 'Vertrek', - 'reservations.arrivalDate': 'Aankomst', - 'reservations.departureTime': 'Vertrektijd', - 'reservations.arrivalTime': 'Aankomsttijd', - 'reservations.pickupDate': 'Ophalen', - 'reservations.returnDate': 'Inleveren', - 'reservations.pickupTime': 'Ophaaltijd', - 'reservations.returnTime': 'Inlevertijd', - 'reservations.endDate': 'Einddatum', - 'reservations.meta.departureTimezone': 'TZ vertrek', - 'reservations.meta.arrivalTimezone': 'TZ aankomst', - 'reservations.span.departure': 'Vertrek', - 'reservations.span.arrival': 'Aankomst', - 'reservations.span.inTransit': 'Onderweg', - 'reservations.span.pickup': 'Ophalen', - 'reservations.span.return': 'Inleveren', - 'reservations.span.active': 'Actief', - 'reservations.span.start': 'Start', - 'reservations.span.end': 'Einde', - 'reservations.span.ongoing': 'Lopend', - 'reservations.validation.endBeforeStart': 'Einddatum/-tijd moet na de startdatum/-tijd liggen', - 'reservations.addBooking': 'Boeking toevoegen', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'CSV exporteren', - 'budget.emptyTitle': 'Nog geen budget aangemaakt', - 'budget.emptyText': 'Maak categorieën en invoeren aan om je reisbudget te plannen', - 'budget.emptyPlaceholder': 'Categorienaam invoeren...', - 'budget.createCategory': 'Categorie aanmaken', - 'budget.category': 'Categorie', - 'budget.categoryName': 'Categorienaam', - 'budget.table.name': 'Naam', - 'budget.table.total': 'Totaal', - 'budget.table.persons': 'Personen', - 'budget.table.days': 'Dagen', - 'budget.table.perPerson': 'Per persoon', - 'budget.table.perDay': 'Per dag', - 'budget.table.perPersonDay': 'P. p. / dag', - 'budget.table.note': 'Notitie', - 'budget.table.date': 'Datum', - 'budget.newEntry': 'Nieuwe invoer', - 'budget.defaultEntry': 'Nieuwe invoer', - 'budget.defaultCategory': 'Nieuwe categorie', - 'budget.total': 'Totaal', - 'budget.totalBudget': 'Totaal budget', - 'budget.byCategory': 'Per categorie', - 'budget.editTooltip': 'Klik om te bewerken', - 'budget.linkedToReservation': 'Gekoppeld aan een reservering — bewerk de naam daar', - 'budget.confirm.deleteCategory': 'Weet je zeker dat je de categorie "{name}" met {count} invoeren wilt verwijderen?', - 'budget.deleteCategory': 'Categorie verwijderen', - 'budget.perPerson': 'Per persoon', - 'budget.paid': 'Betaald', - 'budget.open': 'Openstaand', - 'budget.noMembers': 'Geen leden toegewezen', - 'budget.settlement': 'Afrekening', - 'budget.settlementInfo': 'Klik op de avatar van een lid bij een budgetpost om deze groen te markeren — dit betekent dat diegene heeft betaald. De afrekening toont vervolgens wie wie hoeveel verschuldigd is.', - 'budget.netBalances': 'Nettosaldi', - - // Files - 'files.title': 'Bestanden', - 'files.pageTitle': 'Bestanden en documenten', - 'files.subtitle': '{count} bestanden voor {trip}', - 'files.download': 'Downloaden', - 'files.openError': 'Bestand kon niet worden geopend', - 'files.downloadPdf': 'PDF downloaden', - 'files.count': '{count} bestanden', - 'files.countSingular': '1 bestand', - 'files.uploaded': '{count} geüpload', - 'files.uploadError': 'Uploaden mislukt', - 'files.dropzone': 'Sleep bestanden hierheen', - 'files.dropzoneHint': 'of klik om te bladeren', - 'files.allowedTypes': 'Afbeeldingen, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Uploaden...', - 'files.filterAll': 'Alle', - 'files.filterPdf': 'PDF\'s', - 'files.filterImages': 'Afbeeldingen', - 'files.filterDocs': 'Documenten', - 'files.filterCollab': 'Collab-notities', - 'files.sourceCollab': 'Uit Collab-notities', - 'files.empty': 'Nog geen bestanden', - 'files.emptyHint': 'Upload bestanden om ze aan je reis toe te voegen', - 'files.openTab': 'Openen in nieuw tabblad', - 'files.confirm.delete': 'Weet je zeker dat je dit bestand wilt verwijderen?', - 'files.toast.deleted': 'Bestand verwijderd', - 'files.toast.deleteError': 'Bestand verwijderen mislukt', - 'files.sourcePlan': 'Dagplan', - 'files.sourceBooking': 'Boeking', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Bijvoegen', - 'files.pasteHint': 'Je kunt ook afbeeldingen plakken vanuit het klembord (Ctrl+V)', - 'files.trash': 'Prullenbak', - 'files.trashEmpty': 'Prullenbak is leeg', - 'files.emptyTrash': 'Prullenbak legen', - 'files.restore': 'Herstellen', - 'files.star': 'Ster', - 'files.unstar': 'Ster verwijderen', - 'files.assign': 'Toewijzen', - 'files.assignTitle': 'Bestand toewijzen', - 'files.assignPlace': 'Plaats', - 'files.assignBooking': 'Boeking', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Niet toegewezen', - 'files.unlink': 'Koppeling verwijderen', - 'files.toast.trashed': 'Naar prullenbak verplaatst', - 'files.toast.restored': 'Bestand hersteld', - 'files.toast.trashEmptied': 'Prullenbak geleegd', - 'files.toast.assigned': 'Bestand toegewezen', - 'files.toast.assignError': 'Toewijzing mislukt', - 'files.toast.restoreError': 'Herstellen mislukt', - 'files.confirm.permanentDelete': 'Dit bestand permanent verwijderen? Dit kan niet ongedaan worden gemaakt.', - 'files.confirm.emptyTrash': 'Alle bestanden in de prullenbak permanent verwijderen? Dit kan niet ongedaan worden gemaakt.', - 'files.noteLabel': 'Notitie', - 'files.notePlaceholder': 'Notitie toevoegen...', - - // Packing - 'packing.title': 'Paklijst', - 'packing.empty': 'Paklijst is leeg', - 'packing.import': 'Importeren', - 'packing.importTitle': 'Paklijst importeren', - 'packing.importHint': 'Eén item per regel. Optioneel categorie en aantal gescheiden door komma, puntkomma of tab: Naam, Categorie, Aantal', - 'packing.importPlaceholder': 'Tandenborstel\nZonnebrand, Hygiëne\nT-Shirts, Kleding, 5\nPaspoort, Documenten', - 'packing.importCsv': 'CSV/TXT laden', - 'packing.importAction': '{count} importeren', - 'packing.importSuccess': '{count} items geïmporteerd', - 'packing.importError': 'Import mislukt', - 'packing.importEmpty': 'Geen items om te importeren', - 'packing.progress': '{packed} van {total} ingepakt ({percent}%)', - 'packing.clearChecked': '{count} aangevinkte verwijderen', - 'packing.clearCheckedShort': '{count} verwijderen', - 'packing.suggestions': 'Suggesties', - 'packing.suggestionsTitle': 'Suggesties toevoegen', - 'packing.allSuggested': 'Alle suggesties toegevoegd', - 'packing.allPacked': 'Alles ingepakt!', - 'packing.addPlaceholder': 'Nieuw item toevoegen...', - 'packing.categoryPlaceholder': 'Categorie...', - 'packing.filterAll': 'Alle', - 'packing.filterOpen': 'Openstaand', - 'packing.filterDone': 'Klaar', - 'packing.emptyTitle': 'Paklijst is leeg', - 'packing.emptyHint': 'Voeg items toe of gebruik de suggesties', - 'packing.emptyFiltered': 'Geen items gevonden voor dit filter', - 'packing.menuRename': 'Hernoemen', - 'packing.menuCheckAll': 'Alles aanvinken', - 'packing.menuUncheckAll': 'Alles uitvinken', - 'packing.menuDeleteCat': 'Categorie verwijderen', - 'packing.addItem': 'Item toevoegen', - 'packing.addItemPlaceholder': 'Itemnaam...', - 'packing.addCategory': 'Categorie toevoegen', - 'packing.newCategoryPlaceholder': 'Categorienaam (bijv. Kleding)', - 'packing.applyTemplate': 'Sjabloon toepassen', - 'packing.template': 'Sjabloon', - 'packing.templateApplied': '{count} items toegevoegd vanuit sjabloon', - 'packing.templateError': 'Fout bij toepassen van sjabloon', - 'packing.saveAsTemplate': 'Opslaan als sjabloon', - 'packing.templateName': 'Sjabloonnaam', - 'packing.templateSaved': 'Paklijst opgeslagen als sjabloon', - 'packing.noMembers': 'Geen leden', - 'packing.bags': 'Bagage', - 'packing.noBag': 'Niet toegewezen', - 'packing.totalWeight': 'Totaalgewicht', - 'packing.bagName': 'Naam...', - 'packing.addBag': 'Bagage toevoegen', - 'packing.changeCategory': 'Categorie wijzigen', - 'packing.confirm.clearChecked': 'Weet je zeker dat je {count} aangevinkte items wilt verwijderen?', - 'packing.confirm.deleteCat': 'Weet je zeker dat je de categorie "{name}" met {count} items wilt verwijderen?', - 'packing.defaultCategory': 'Overig', - 'packing.toast.saveError': 'Opslaan mislukt', - 'packing.toast.deleteError': 'Verwijderen mislukt', - 'packing.toast.renameError': 'Hernoemen mislukt', - 'packing.toast.addError': 'Toevoegen mislukt', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Paspoort', category: 'Documenten' }, - { name: 'Identiteitskaart', category: 'Documenten' }, - { name: 'Reisverzekering', category: 'Documenten' }, - { name: 'Vliegtickets', category: 'Documenten' }, - { name: 'Creditcard', category: 'Financiën' }, - { name: 'Contant geld', category: 'Financiën' }, - { name: 'Visum', category: 'Documenten' }, - { name: 'T-shirts', category: 'Kleding' }, - { name: 'Broeken', category: 'Kleding' }, - { name: 'Ondergoed', category: 'Kleding' }, - { name: 'Sokken', category: 'Kleding' }, - { name: 'Jas', category: 'Kleding' }, - { name: 'Slaapkleding', category: 'Kleding' }, - { name: 'Zwemkleding', category: 'Kleding' }, - { name: 'Regenjas', category: 'Kleding' }, - { name: 'Comfortabele schoenen', category: 'Kleding' }, - { name: 'Tandenborstel', category: 'Toiletartikelen' }, - { name: 'Tandpasta', category: 'Toiletartikelen' }, - { name: 'Shampoo', category: 'Toiletartikelen' }, - { name: 'Deodorant', category: 'Toiletartikelen' }, - { name: 'Zonnebrandcrème', category: 'Toiletartikelen' }, - { name: 'Scheermesje', category: 'Toiletartikelen' }, - { name: 'Oplader', category: 'Elektronica' }, - { name: 'Powerbank', category: 'Elektronica' }, - { name: 'Koptelefoon', category: 'Elektronica' }, - { name: 'Reisadapter', category: 'Elektronica' }, - { name: 'Camera', category: 'Elektronica' }, - { name: 'Pijnstillers', category: 'Gezondheid' }, - { name: 'Pleisters', category: 'Gezondheid' }, - { name: 'Ontsmettingsmiddel', category: 'Gezondheid' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Reis delen', - 'members.inviteUser': 'Gebruiker uitnodigen', - 'members.selectUser': 'Selecteer gebruiker…', - 'members.invite': 'Uitnodigen', - 'members.allHaveAccess': 'Alle gebruikers hebben al toegang.', - 'members.access': 'Toegang', - 'members.person': 'persoon', - 'members.persons': 'personen', - 'members.you': 'jij', - 'members.owner': 'Eigenaar', - 'members.leaveTrip': 'Reis verlaten', - 'members.removeAccess': 'Toegang verwijderen', - 'members.confirmLeave': 'Reis verlaten? Je verliest de toegang.', - 'members.confirmRemove': 'Toegang voor deze gebruiker verwijderen?', - 'members.loadError': 'Leden laden mislukt', - 'members.added': 'toegevoegd', - 'members.addError': 'Toevoegen mislukt', - 'members.removed': 'Lid verwijderd', - 'members.removeError': 'Verwijderen mislukt', - - // Categories (Admin) - 'categories.title': 'Categorieën', - 'categories.subtitle': 'Beheer categorieën voor plaatsen', - 'categories.new': 'Nieuwe categorie', - 'categories.empty': 'Nog geen categorieën', - 'categories.namePlaceholder': 'Categorienaam', - 'categories.icon': 'Pictogram', - 'categories.color': 'Kleur', - 'categories.customColor': 'Kies een aangepaste kleur', - 'categories.preview': 'Voorbeeld', - 'categories.defaultName': 'Categorie', - 'categories.update': 'Bijwerken', - 'categories.create': 'Aanmaken', - 'categories.confirm.delete': 'Categorie verwijderen? Plaatsen in deze categorie worden niet verwijderd.', - 'categories.toast.loadError': 'Categorieën laden mislukt', - 'categories.toast.nameRequired': 'Voer een naam in', - 'categories.toast.updated': 'Categorie bijgewerkt', - 'categories.toast.created': 'Categorie aangemaakt', - 'categories.toast.saveError': 'Opslaan mislukt', - 'categories.toast.deleted': 'Categorie verwijderd', - 'categories.toast.deleteError': 'Verwijderen mislukt', - - // Backup (Admin) - 'backup.title': 'Gegevensback-up', - 'backup.subtitle': 'Database en alle geüploade bestanden', - 'backup.refresh': 'Vernieuwen', - 'backup.upload': 'Back-up uploaden', - 'backup.uploading': 'Uploaden…', - 'backup.create': 'Back-up aanmaken', - 'backup.creating': 'Aanmaken…', - 'backup.empty': 'Nog geen back-ups', - 'backup.createFirst': 'Eerste back-up aanmaken', - 'backup.download': 'Downloaden', - 'backup.restore': 'Herstellen', - 'backup.confirm.restore': 'Back-up "{name}" herstellen?\n\nAlle huidige gegevens worden vervangen door de back-up.', - 'backup.confirm.uploadRestore': 'Back-upbestand "{name}" uploaden en herstellen?\n\nAlle huidige gegevens worden overschreven.', - 'backup.confirm.delete': 'Back-up "{name}" verwijderen?', - 'backup.toast.loadError': 'Back-ups laden mislukt', - 'backup.toast.created': 'Back-up succesvol aangemaakt', - 'backup.toast.createError': 'Back-up aanmaken mislukt', - 'backup.toast.restored': 'Back-up hersteld. Pagina wordt herladen…', - 'backup.toast.restoreError': 'Herstellen mislukt', - 'backup.toast.uploadError': 'Uploaden mislukt', - 'backup.toast.deleted': 'Back-up verwijderd', - 'backup.toast.deleteError': 'Verwijderen mislukt', - 'backup.toast.downloadError': 'Downloaden mislukt', - 'backup.toast.settingsSaved': 'Auto-back-up-instellingen opgeslagen', - 'backup.toast.settingsError': 'Instellingen opslaan mislukt', - 'backup.auto.title': 'Auto-back-up', - 'backup.auto.subtitle': 'Automatische back-up volgens schema', - 'backup.auto.enable': 'Auto-back-up inschakelen', - 'backup.auto.enableHint': 'Back-ups worden automatisch aangemaakt volgens het gekozen schema', - 'backup.auto.interval': 'Interval', - 'backup.auto.hour': 'Uitvoeren om', - 'backup.auto.hourHint': 'Lokale servertijd ({format}-notatie)', - 'backup.auto.dayOfWeek': 'Dag van de week', - 'backup.auto.dayOfMonth': 'Dag van de maand', - 'backup.auto.dayOfMonthHint': 'Beperkt tot 1–28 voor compatibiliteit met alle maanden', - 'backup.auto.scheduleSummary': 'Planning', - 'backup.auto.summaryDaily': 'Elke dag om {hour}:00', - 'backup.auto.summaryWeekly': 'Elke {day} om {hour}:00', - 'backup.auto.summaryMonthly': 'Dag {day} van elke maand om {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Auto-back-up is geconfigureerd via Docker-omgevingsvariabelen. Pas je docker-compose.yml aan en herstart de container om deze instellingen te wijzigen.', - 'backup.auto.copyEnv': 'Docker-omgevingsvariabelen kopiëren', - 'backup.auto.envCopied': 'Docker-omgevingsvariabelen gekopieerd naar klembord', - 'backup.auto.keepLabel': 'Oude back-ups verwijderen na', - 'backup.dow.sunday': 'Zo', - 'backup.dow.monday': 'Ma', - 'backup.dow.tuesday': 'Di', - 'backup.dow.wednesday': 'Wo', - 'backup.dow.thursday': 'Do', - 'backup.dow.friday': 'Vr', - 'backup.dow.saturday': 'Za', - 'backup.interval.hourly': 'Elk uur', - 'backup.interval.daily': 'Dagelijks', - 'backup.interval.weekly': 'Wekelijks', - 'backup.interval.monthly': 'Maandelijks', - 'backup.keep.1day': '1 dag', - 'backup.keep.3days': '3 dagen', - 'backup.keep.7days': '7 dagen', - 'backup.keep.14days': '14 dagen', - 'backup.keep.30days': '30 dagen', - 'backup.keep.forever': 'Voor altijd bewaren', - - // Photos - 'photos.title': 'Foto\'s', - 'photos.subtitle': '{count} foto\'s voor {trip}', - 'photos.dropHere': 'Foto\'s hier neerzetten...', - 'photos.dropHereActive': 'Foto\'s hier neerzetten', - 'photos.captionForAll': 'Bijschrift (voor alle)', - 'photos.captionPlaceholder': 'Optioneel bijschrift...', - 'photos.addCaption': 'Bijschrift toevoegen...', - 'photos.allDays': 'Alle dagen', - 'photos.noPhotos': 'Nog geen foto\'s', - 'photos.uploadHint': 'Upload je reisfoto\'s', - 'photos.clickToSelect': 'of klik om te selecteren', - 'photos.linkPlace': 'Koppel plaats', - 'photos.noPlace': 'Geen plaats', - 'photos.uploadN': '{n} foto(\'s) uploaden', - 'photos.linkDay': 'Dag koppelen', - 'photos.noDay': 'Geen dag', - 'photos.dayLabel': 'Dag {number}', - 'photos.photoSelected': 'Foto geselecteerd', - 'photos.photosSelected': "Foto's geselecteerd", - 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 MB · tot 30 foto's", - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Back-up herstellen?', - 'backup.restoreWarning': 'Alle huidige gegevens (reizen, plaatsen, gebruikers, uploads) worden permanent vervangen door de back-up. Deze actie kan niet ongedaan worden gemaakt.', - 'backup.restoreTip': 'Tip: Maak een back-up van de huidige status voordat je herstelt.', - 'backup.restoreConfirm': 'Ja, herstellen', - - // PDF - 'pdf.travelPlan': 'Reisplan', - 'pdf.planned': 'Gepland', - 'pdf.costLabel': 'Kosten EUR', - 'pdf.preview': 'PDF-voorbeeld', - 'pdf.saveAsPdf': 'Opslaan als PDF', - - // Planner - 'planner.places': 'Plaatsen', - 'planner.bookings': 'Boekingen', - 'planner.packingList': 'Paklijst', - 'planner.documents': 'Documenten', - 'planner.dayPlan': 'Dagplan', - 'planner.reservations': 'Reserveringen', - 'planner.minTwoPlaces': 'Minimaal 2 plaatsen met coördinaten nodig', - 'planner.noGeoPlaces': 'Geen plaatsen met coördinaten beschikbaar', - 'planner.routeCalculated': 'Route berekend', - 'planner.routeCalcFailed': 'Route kon niet worden berekend', - 'planner.routeError': 'Fout bij routeberekening', - 'planner.icsExportFailed': 'ICS-export mislukt', - 'planner.routeOptimized': 'Route geoptimaliseerd', - 'planner.reservationUpdated': 'Reservering bijgewerkt', - 'planner.reservationAdded': 'Reservering toegevoegd', - 'planner.confirmDeleteReservation': 'Reservering verwijderen?', - 'planner.reservationDeleted': 'Reservering verwijderd', - 'planner.days': 'Dagen', - 'planner.allPlaces': 'Alle plaatsen', - 'planner.totalPlaces': '{n} plaatsen totaal', - 'planner.noDaysPlanned': 'Nog geen dagen gepland', - 'planner.editTrip': 'Reis bewerken \u2192', - 'planner.placeOne': '1 plaats', - 'planner.placeN': '{n} plaatsen', - 'planner.addNote': 'Notitie toevoegen', - 'planner.noEntries': 'Geen invoeren voor deze dag', - 'planner.addPlace': 'Plaats/activiteit toevoegen', - 'planner.addPlaceShort': '+ Plaats/activiteit toevoegen', - 'planner.resPending': 'Reservering in behandeling · ', - 'planner.resConfirmed': 'Reservering bevestigd · ', - 'planner.notePlaceholder': 'Notitie…', - 'planner.noteTimePlaceholder': 'Tijd (optioneel)', - 'planner.noteExamplePlaceholder': 'bijv. S3 om 14:30 vanaf centraal station, veerboot van pier 7, lunchpauze…', - 'planner.totalCost': 'Totale kosten', - 'planner.searchPlaces': 'Plaatsen zoeken…', - 'planner.allCategories': 'Alle categorieën', - 'planner.noPlacesFound': 'Geen plaatsen gevonden', - 'planner.addFirstPlace': 'Eerste plaats toevoegen', - 'planner.noReservations': 'Geen reserveringen', - 'planner.addFirstReservation': 'Eerste reservering toevoegen', - 'planner.new': 'Nieuw', - 'planner.addToDay': '+ Dag', - 'planner.calculating': 'Berekenen…', - 'planner.route': 'Route', - 'planner.optimize': 'Optimaliseren', - 'planner.openGoogleMaps': 'Openen in Google Maps', - 'planner.selectDayHint': 'Selecteer een dag uit de lijst links om het dagplan te bekijken', - 'planner.noPlacesForDay': 'Nog geen plaatsen voor deze dag', - 'planner.addPlacesLink': 'Plaatsen toevoegen \u2192', - 'planner.minTotal': 'min. totaal', - 'planner.noReservation': 'Geen reservering', - 'planner.removeFromDay': 'Verwijderen van dag', - 'planner.addToThisDay': 'Toevoegen aan dag', - 'planner.overview': 'Overzicht', - 'planner.noDays': 'Geen dagen', - 'planner.editTripToAddDays': 'Bewerk de reis om dagen toe te voegen', - 'planner.dayCount': '{n} dagen', - 'planner.clickToUnlock': 'Klik om te ontgrendelen', - 'planner.keepPosition': 'Positie behouden tijdens route-optimalisatie', - 'planner.dayDetails': 'Dagdetails', - 'planner.dayN': 'Dag {n}', - - // Dashboard Stats - 'stats.countries': 'Landen', - 'stats.cities': 'Steden', - 'stats.trips': 'Reizen', - 'stats.places': 'Plaatsen', - 'stats.worldProgress': 'Wereldvoortgang', - 'stats.visited': 'bezocht', - 'stats.remaining': 'resterend', - 'stats.visitedCountries': 'Bezochte landen', - - // Day Detail Panel - 'day.precipProb': 'Regenkans', - 'day.precipitation': 'Neerslag', - 'day.wind': 'Wind', - 'day.sunrise': 'Zonsopgang', - 'day.sunset': 'Zonsondergang', - 'day.hourlyForecast': 'Uurlijkse voorspelling', - 'day.climateHint': 'Historische gemiddelden — echte voorspelling beschikbaar binnen 16 dagen voor deze datum.', - 'day.noWeather': 'Geen weergegevens beschikbaar. Voeg een plaats met coördinaten toe.', - 'day.overview': 'Dagoverzicht', - 'day.accommodation': 'Accommodatie', - 'day.addAccommodation': 'Accommodatie toevoegen', - 'day.hotelDayRange': 'Toepassen op dagen', - 'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis', - 'day.allDays': 'Alle', - 'day.checkIn': 'Inchecken', - 'day.checkInUntil': 'Tot', - 'day.checkOut': 'Uitchecken', - 'day.confirmation': 'Bevestiging', - 'day.editAccommodation': 'Accommodatie bewerken', - 'day.reservations': 'Reserveringen', - - // Memories / Immich - 'memories.title': 'Foto\'s', - 'memories.notConnected': 'Immich niet verbonden', - 'memories.notConnectedHint': 'Verbind je Immich-instantie in Instellingen om je reisfoto\'s hier te zien.', - 'memories.notConnectedMultipleHint': 'Verbind een van deze fotoproviders: {provider_names} in Instellingen om foto\'s aan dit reisplan toe te voegen.', - 'memories.noDates': 'Voeg data toe aan je reis om foto\'s te laden.', - 'memories.noPhotos': 'Geen foto\'s gevonden', - 'memories.noPhotosHint': 'Geen foto\'s gevonden in Immich voor de datumreeks van deze reis.', - 'memories.photosFound': 'foto\'s', - 'memories.fromOthers': 'van anderen', - 'memories.sharePhotos': 'Foto\'s delen', - 'memories.sharing': 'Wordt gedeeld', - 'memories.reviewTitle': 'Je foto\'s bekijken', - 'memories.reviewHint': 'Klik op foto\'s om ze uit te sluiten van delen.', - 'memories.shareCount': '{count} foto\'s delen', - 'memories.providerUrl': 'Server-URL', - 'memories.providerApiKey': 'API-sleutel', - 'memories.providerUsername': 'Gebruikersnaam', - 'memories.providerPassword': 'Wachtwoord', - 'memories.providerOTP': 'MFA-code (indien ingeschakeld)', - 'memories.skipSSLVerification': 'SSL-certificaatverificatie overslaan', - 'memories.immichAutoUpload': 'Journey-foto\'s bij upload ook naar Immich spiegelen', - 'memories.providerUrlHintSynology': 'Voeg het pad van de Photos-app toe aan de URL, bijv. https://nas:5001/photo', - 'memories.testConnection': 'Verbinding testen', - 'memories.testShort': 'Testen', - 'memories.testFirst': 'Test eerst de verbinding', - 'memories.connected': 'Verbonden', - 'memories.disconnected': 'Niet verbonden', - 'memories.connectionSuccess': 'Verbonden met Immich', - 'memories.connectionError': 'Kon niet verbinden met Immich', - 'memories.saved': '{provider_name}-instellingen opgeslagen', - 'memories.providerDisconnectedBanner': 'Je {provider_name}-verbinding is verbroken. Maak opnieuw verbinding in Instellingen om foto\'s te bekijken.', - 'memories.saveError': '{provider_name}-instellingen konden niet worden opgeslagen', - 'memories.saveRouteNotConfigured': 'Opslagroute is niet geconfigureerd voor deze provider', - 'memories.testRouteNotConfigured': 'Testroute is niet geconfigureerd voor deze provider', - 'memories.fillRequiredFields': 'Vul alle verplichte velden in', - 'memories.oldest': 'Oudste eerst', - 'memories.newest': 'Nieuwste eerst', - 'memories.allLocations': 'Alle locaties', - 'memories.addPhotos': 'Foto\'s toevoegen', - 'memories.linkAlbum': 'Album koppelen', - 'memories.selectAlbum': 'Immich-album selecteren', - 'memories.selectAlbumMultiple': 'Album selecteren', - 'memories.noAlbums': 'Geen albums gevonden', - 'memories.syncAlbum': 'Album synchroniseren', - 'memories.unlinkAlbum': 'Ontkoppelen', - 'memories.photos': 'fotos', - 'memories.selectPhotos': 'Selecteer foto\'s uit Immich', - 'memories.selectPhotosMultiple': 'Foto\'s selecteren', - 'memories.selectHint': 'Tik op foto\'s om ze te selecteren.', - 'memories.selected': 'geselecteerd', - 'memories.addSelected': '{count} foto\'s toevoegen', - 'memories.alreadyAdded': 'Toegevoegd', - 'memories.private': 'Privé', - 'memories.stopSharing': 'Delen stoppen', - 'memories.tripDates': 'Reisdata', - 'memories.allPhotos': 'Alle foto\'s', - 'memories.confirmShareTitle': 'Delen met reisgenoten?', - 'memories.confirmShareHint': '{count} foto\'s worden zichtbaar voor alle leden van deze reis. Je kunt individuele foto\'s later privé maken.', - 'memories.confirmShareButton': 'Foto\'s delen', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Notities', - 'collab.tabs.polls': 'Polls', - 'collab.whatsNext.title': 'Wat komt er', - 'collab.whatsNext.today': 'Vandaag', - 'collab.whatsNext.tomorrow': 'Morgen', - 'collab.whatsNext.empty': 'Geen komende activiteiten', - 'collab.whatsNext.until': 'tot', - 'collab.whatsNext.emptyHint': 'Activiteiten met tijden verschijnen hier', - 'collab.chat.send': 'Verzenden', - 'collab.chat.placeholder': 'Typ een bericht...', - 'collab.chat.empty': 'Start het gesprek', - 'collab.chat.emptyHint': 'Berichten worden gedeeld met alle reisleden', - 'collab.chat.emptyDesc': 'Deel ideeën, plannen en updates met je reisgroep', - 'collab.chat.today': 'Vandaag', - 'collab.chat.yesterday': 'Gisteren', - 'collab.chat.deletedMessage': 'heeft een bericht verwijderd', - 'collab.chat.reply': 'Beantwoorden', - 'collab.chat.loadMore': 'Oudere berichten laden', - 'collab.chat.justNow': 'zojuist', - 'collab.chat.minutesAgo': '{n} min. geleden', - 'collab.chat.hoursAgo': '{n} uur geleden', - 'collab.notes.title': 'Notities', - 'collab.notes.new': 'Nieuwe notitie', - 'collab.notes.empty': 'Nog geen notities', - 'collab.notes.emptyHint': 'Begin met het vastleggen van ideeën en plannen', - 'collab.notes.all': 'Alle', - 'collab.notes.titlePlaceholder': 'Notitietitel', - 'collab.notes.contentPlaceholder': 'Schrijf iets...', - 'collab.notes.categoryPlaceholder': 'Categorie', - 'collab.notes.newCategory': 'Nieuwe categorie...', - 'collab.notes.category': 'Categorie', - 'collab.notes.noCategory': 'Geen categorie', - 'collab.notes.color': 'Kleur', - 'collab.notes.save': 'Opslaan', - 'collab.notes.cancel': 'Annuleren', - 'collab.notes.edit': 'Bewerken', - 'collab.notes.delete': 'Verwijderen', - 'collab.notes.pin': 'Vastpinnen', - 'collab.notes.unpin': 'Losmaken', - 'collab.notes.daysAgo': '{n}d geleden', - 'collab.notes.categorySettings': 'Categorieën beheren', - 'collab.notes.create': 'Aanmaken', - 'collab.notes.website': 'Website', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Bestanden bijvoegen', - 'collab.notes.noCategoriesYet': 'Nog geen categorieën', - 'collab.notes.emptyDesc': 'Maak een notitie om te beginnen', - 'collab.polls.title': 'Polls', - 'collab.polls.new': 'Nieuwe poll', - 'collab.polls.empty': 'Nog geen polls', - 'collab.polls.emptyHint': 'Stel de groep een vraag en stem samen', - 'collab.polls.question': 'Vraag', - 'collab.polls.questionPlaceholder': 'Wat zullen we doen?', - 'collab.polls.addOption': '+ Optie toevoegen', - 'collab.polls.optionPlaceholder': 'Optie {n}', - 'collab.polls.create': 'Poll aanmaken', - 'collab.polls.close': 'Sluiten', - 'collab.polls.closed': 'Gesloten', - 'collab.polls.votes': '{n} stemmen', - 'collab.polls.vote': '{n} stem', - 'collab.polls.multipleChoice': 'Meerkeuze', - 'collab.polls.multiChoice': 'Meerkeuze', - 'collab.polls.deadline': 'Deadline', - 'collab.polls.option': 'Optie', - 'collab.polls.options': 'Opties', - 'collab.polls.delete': 'Verwijderen', - 'collab.polls.closedSection': 'Gesloten', - - // Permissions - 'admin.tabs.permissions': 'Rechten', - 'perm.title': 'Rechtinstellingen', - 'perm.subtitle': 'Bepaal wie welke acties mag uitvoeren in de applicatie', - 'perm.saved': 'Rechtinstellingen opgeslagen', - 'perm.resetDefaults': 'Standaardwaarden herstellen', - 'perm.customized': 'aangepast', - 'perm.level.admin': 'Alleen beheerder', - 'perm.level.tripOwner': 'Reiseigenaar', - 'perm.level.tripMember': 'Reisleden', - 'perm.level.everybody': 'Iedereen', - 'perm.cat.trip': 'Reisbeheer', - 'perm.cat.members': 'Ledenbeheer', - 'perm.cat.files': 'Bestanden', - 'perm.cat.content': 'Inhoud & planning', - 'perm.cat.extras': 'Budget, paklijsten & samenwerking', - 'perm.action.trip_create': 'Reizen aanmaken', - 'perm.action.trip_edit': 'Reisdetails bewerken', - 'perm.action.trip_delete': 'Reizen verwijderen', - 'perm.action.trip_archive': 'Reizen archiveren / dearchiveren', - 'perm.action.trip_cover_upload': 'Omslagfoto uploaden', - 'perm.action.member_manage': 'Leden toevoegen / verwijderen', - 'perm.action.file_upload': 'Bestanden uploaden', - 'perm.action.file_edit': 'Bestandsmetadata bewerken', - 'perm.action.file_delete': 'Bestanden verwijderen', - 'perm.action.place_edit': 'Plaatsen toevoegen / bewerken / verwijderen', - 'perm.action.day_edit': 'Dagen, notities & toewijzingen bewerken', - 'perm.action.reservation_edit': 'Reserveringen beheren', - 'perm.action.budget_edit': 'Budget beheren', - 'perm.action.packing_edit': 'Paklijsten beheren', - 'perm.action.collab_edit': 'Samenwerking (notities, polls, chat)', - 'perm.action.share_manage': 'Deellinks beheren', - 'perm.actionHint.trip_create': 'Wie kan nieuwe reizen aanmaken', - 'perm.actionHint.trip_edit': 'Wie kan reisnaam, data, beschrijving en valuta wijzigen', - 'perm.actionHint.trip_delete': 'Wie kan een reis permanent verwijderen', - 'perm.actionHint.trip_archive': 'Wie kan een reis archiveren of dearchiveren', - 'perm.actionHint.trip_cover_upload': 'Wie kan de omslagfoto uploaden of wijzigen', - 'perm.actionHint.member_manage': 'Wie kan reisleden uitnodigen of verwijderen', - 'perm.actionHint.file_upload': 'Wie kan bestanden uploaden naar een reis', - 'perm.actionHint.file_edit': 'Wie kan bestandsbeschrijvingen en links bewerken', - 'perm.actionHint.file_delete': 'Wie kan bestanden naar de prullenbak verplaatsen of permanent verwijderen', - 'perm.actionHint.place_edit': 'Wie kan plaatsen toevoegen, bewerken of verwijderen', - 'perm.actionHint.day_edit': 'Wie kan dagen, dagnotities en plaatstoewijzingen bewerken', - 'perm.actionHint.reservation_edit': 'Wie kan reserveringen aanmaken, bewerken of verwijderen', - 'perm.actionHint.budget_edit': 'Wie kan budgetposten aanmaken, bewerken of verwijderen', - 'perm.actionHint.packing_edit': 'Wie kan pakitems en tassen beheren', - 'perm.actionHint.collab_edit': 'Wie kan notities, polls aanmaken en berichten versturen', - 'perm.actionHint.share_manage': 'Wie kan openbare deellinks aanmaken of verwijderen', - // Undo - 'undo.button': 'Ongedaan maken', - 'undo.tooltip': 'Ongedaan maken: {action}', - 'undo.assignPlace': 'Locatie aan dag toegewezen', - 'undo.removeAssignment': 'Locatie uit dag verwijderd', - 'undo.reorder': 'Locaties hergeordend', - 'undo.optimize': 'Route geoptimaliseerd', - 'undo.deletePlace': 'Locatie verwijderd', - 'undo.deletePlaces': 'Plaatsen verwijderd', - 'undo.moveDay': 'Locatie naar andere dag verplaatst', - 'undo.lock': 'Vergrendeling locatie gewijzigd', - 'undo.importGpx': 'GPX-import', - 'undo.importKeyholeMarkup': 'KMZ/KML-import', - 'undo.importGoogleList': 'Google Maps-import', - 'undo.importNaverList': 'Naver Maps-import', - - // Notifications - 'notifications.title': 'Meldingen', - 'notifications.markAllRead': 'Alles als gelezen markeren', - 'notifications.deleteAll': 'Alles verwijderen', - 'notifications.showAll': 'Alle meldingen weergeven', - 'notifications.empty': 'Geen meldingen', - 'notifications.emptyDescription': 'Je bent helemaal bijgewerkt!', - 'notifications.all': 'Alle', - 'notifications.unreadOnly': 'Ongelezen', - 'notifications.markRead': 'Markeren als gelezen', - 'notifications.markUnread': 'Markeren als ongelezen', - 'notifications.delete': 'Verwijderen', - 'notifications.system': 'Systeem', - 'notifications.synologySessionCleared.title': 'Synology Photos verbroken', - 'notifications.synologySessionCleared.text': 'Je server of account is gewijzigd — ga naar Instellingen om je verbinding opnieuw te testen.', - 'memories.error.loadAlbums': 'Albums laden mislukt', - 'memories.error.linkAlbum': 'Album koppelen mislukt', - 'memories.error.unlinkAlbum': 'Album ontkoppelen mislukt', - 'memories.error.syncAlbum': 'Album synchroniseren mislukt', - 'memories.error.loadPhotos': 'Foto\'s laden mislukt', - 'memories.error.addPhotos': 'Foto\'s toevoegen mislukt', - 'memories.error.removePhoto': 'Foto verwijderen mislukt', - 'memories.error.toggleSharing': 'Delen bijwerken mislukt', - 'undo.addPlace': 'Locatie toegevoegd', - 'undo.done': 'Ongedaan gemaakt: {action}', - 'notifications.test.title': 'Testmelding van {actor}', - 'notifications.test.text': 'Dit is een eenvoudige testmelding.', - 'notifications.test.booleanTitle': '{actor} vraagt om uw goedkeuring', - 'notifications.test.booleanText': 'Booleaanse testmelding.', - 'notifications.test.accept': 'Goedkeuren', - 'notifications.test.decline': 'Afwijzen', - 'notifications.test.navigateTitle': 'Bekijk iets', - 'notifications.test.navigateText': 'Navigatie-testmelding.', - 'notifications.test.goThere': 'Ga erheen', - 'notifications.test.adminTitle': 'Admin-broadcast', - 'notifications.test.adminText': '{actor} heeft een testmelding naar alle admins gestuurd.', - 'notifications.test.tripTitle': '{actor} heeft gepost in uw reis', - 'notifications.test.tripText': 'Testmelding voor reis "{trip}".', - - // Todo - 'todo.subtab.packing': 'Paklijst', - 'todo.subtab.todo': 'Taken', - 'todo.completed': 'voltooid', - 'todo.filter.all': 'Alles', - 'todo.filter.open': 'Openstaand', - 'todo.filter.done': 'Klaar', - 'todo.uncategorized': 'Zonder categorie', - 'todo.namePlaceholder': 'Taaknaam', - 'todo.descriptionPlaceholder': 'Beschrijving (optioneel)', - 'todo.unassigned': 'Niet toegewezen', - 'todo.noCategory': 'Geen categorie', - 'todo.hasDescription': 'Heeft beschrijving', - 'todo.addItem': 'Nieuwe taak', - 'todo.sidebar.sortBy': 'Sorteren op', - 'todo.priority': 'Prioriteit', - 'todo.newCategoryLabel': 'nieuw', - 'budget.categoriesLabel': 'categorieën', - 'todo.newCategory': 'Categorienaam', - 'todo.addCategory': 'Categorie toevoegen', - 'todo.newItem': 'Nieuwe taak', - 'todo.empty': 'Nog geen taken. Voeg een taak toe om te beginnen!', - 'todo.filter.my': 'Mijn taken', - 'todo.filter.overdue': 'Verlopen', - 'todo.sidebar.tasks': 'Taken', - 'todo.sidebar.categories': 'Categorieën', - 'todo.detail.title': 'Taak', - 'todo.detail.description': 'Beschrijving', - 'todo.detail.category': 'Categorie', - 'todo.detail.dueDate': 'Vervaldatum', - 'todo.detail.assignedTo': 'Toegewezen aan', - 'todo.detail.delete': 'Verwijderen', - 'todo.detail.save': 'Wijzigingen opslaan', - 'todo.detail.create': 'Taak aanmaken', - 'todo.detail.priority': 'Prioriteit', - 'todo.detail.noPriority': 'Geen', - 'todo.sortByPrio': 'Prioriteit', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Nieuwe versie beschikbaar', - 'settings.notificationPreferences.noChannels': 'Er zijn geen meldingskanalen geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te stellen.', - 'settings.webhookUrl.label': 'Webhook-URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Voer je Discord-, Slack- of aangepaste webhook-URL in om meldingen te ontvangen.', - 'settings.webhookUrl.saved': 'Webhook-URL opgeslagen', - 'settings.webhookUrl.test': 'Testen', - 'settings.webhookUrl.testSuccess': 'Test-webhook succesvol verzonden', - 'settings.webhookUrl.testFailed': 'Test-webhook mislukt', - 'settings.ntfyUrl.topicLabel': 'Ntfy-onderwerp', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy-server-URL (optioneel)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Voer je Ntfy-onderwerp in om pushmeldingen te ontvangen. Laat het serverveld leeg om de standaard te gebruiken die door je beheerder is ingesteld.', - 'settings.ntfyUrl.tokenLabel': 'Toegangstoken (optioneel)', - 'settings.ntfyUrl.tokenHint': 'Vereist voor onderwerpen die met een wachtwoord zijn beveiligd.', - 'settings.ntfyUrl.saved': 'Ntfy-instellingen opgeslagen', - 'settings.ntfyUrl.test': 'Testen', - 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-melding succesvol verzonden', - 'settings.ntfyUrl.testFailed': 'Test-Ntfy-melding mislukt', - 'settings.ntfyUrl.tokenCleared': 'Toegangstoken gewist', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'In-app-meldingen zijn altijd actief en kunnen niet globaal worden uitgeschakeld.', - 'admin.notifications.adminWebhookPanel.title': 'Admin-webhook', - 'admin.notifications.adminWebhookPanel.hint': 'Deze webhook wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Hij staat los van gebruikerswebhooks en verstuurt automatisch als er een URL is ingesteld.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin-webhook-URL opgeslagen', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Test-webhook succesvol verzonden', - 'admin.notifications.adminWebhookPanel.testFailed': 'Test-webhook mislukt', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin-webhook verstuurt automatisch als er een URL is ingesteld', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Hiermee kunnen gebruikers hun eigen ntfy-onderwerpen instellen voor pushmeldingen. Stel de standaardserver hieronder in om de gebruikersinstellingen vooraf in te vullen.', - 'admin.notifications.testNtfy': 'Test-Ntfy verzenden', - 'admin.notifications.testNtfySuccess': 'Test-Ntfy succesvol verzonden', - 'admin.notifications.testNtfyFailed': 'Test-Ntfy mislukt', - 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Dit Ntfy-onderwerp wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Het staat los van onderwerpen per gebruiker en verstuurt altijd wanneer het geconfigureerd is.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-server-URL', - 'admin.notifications.adminNtfyPanel.serverHint': 'Wordt ook gebruikt als standaardserver voor ntfy-meldingen van gebruikers. Laat leeg om ntfy.sh te gebruiken. Gebruikers kunnen dit aanpassen in hun eigen instellingen.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-onderwerp', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Toegangstoken (optioneel)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Admin-toegangstoken gewist', - 'admin.notifications.adminNtfyPanel.saved': 'Admin-Ntfy-instellingen opgeslagen', - 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy verzenden', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test-Ntfy succesvol verzonden', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy mislukt', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin-Ntfy verstuurt altijd wanneer een onderwerp is geconfigureerd', - 'admin.notifications.adminNotificationsHint': 'Stel in via welke kanalen admin-meldingen worden bezorgd (bijv. versie-updates). De webhook verstuurt automatisch als er een admin-webhook-URL is ingesteld.', - 'admin.notifications.tripReminders.title': 'Reisherinneringen', - 'admin.notifications.tripReminders.hint': 'Stuurt een herinneringsmelding voor de start van een reis (vereist ingestelde herinneringsdagen bij de reis).', - 'admin.notifications.tripReminders.enabled': 'Reisherinneringen ingeschakeld', - 'admin.notifications.tripReminders.disabled': 'Reisherinneringen uitgeschakeld', - 'admin.tabs.notifications': 'Meldingen', - 'notifications.versionAvailable.title': 'Update beschikbaar', - 'notifications.versionAvailable.text': 'TREK {version} is nu beschikbaar.', - 'notifications.versionAvailable.button': 'Details bekijken', - 'notif.test.title': '[Test] Melding', - 'notif.test.simple.text': 'Dit is een eenvoudige testmelding.', - 'notif.test.boolean.text': 'Accepteer je deze testmelding?', - 'notif.test.navigate.text': 'Klik hieronder om naar het dashboard te gaan.', - - // Notifications - 'notif.trip_invite.title': 'Reisuitnodiging', - 'notif.trip_invite.text': '{actor} heeft je uitgenodigd voor {trip}', - 'notif.booking_change.title': 'Boeking bijgewerkt', - 'notif.booking_change.text': '{actor} heeft een boeking bijgewerkt in {trip}', - 'notif.trip_reminder.title': 'Reisherinnering', - 'notif.trip_reminder.text': 'Je reis {trip} komt eraan!', - 'notif.todo_due.title': 'Taak verloopt', - 'notif.todo_due.text': '{todo} in {trip} verloopt op {due}', - 'notif.vacay_invite.title': 'Vacay Fusion-uitnodiging', - 'notif.vacay_invite.text': '{actor} nodigt je uit om vakantieplannen te fuseren', - 'notif.photos_shared.title': 'Foto\'s gedeeld', - 'notif.photos_shared.text': '{actor} heeft {count} foto(\'s) gedeeld in {trip}', - 'notif.collab_message.title': 'Nieuw bericht', - 'notif.collab_message.text': '{actor} heeft een bericht gestuurd in {trip}', - 'notif.packing_tagged.title': 'Paklijsttaak', - 'notif.packing_tagged.text': '{actor} heeft je toegewezen aan {category} in {trip}', - 'notif.version_available.title': 'Nieuwe versie beschikbaar', - 'notif.version_available.text': 'TREK {version} is nu beschikbaar', - 'notif.action.view_trip': 'Reis bekijken', - 'notif.action.view_collab': 'Berichten bekijken', - 'notif.action.view_packing': 'Paklijst bekijken', - 'notif.action.view_photos': 'Foto\'s bekijken', - 'notif.action.view_vacay': 'Vacay bekijken', - 'notif.action.view_admin': 'Naar admin', - 'notif.action.view': 'Bekijken', - 'notif.action.accept': 'Accepteren', - 'notif.action.decline': 'Weigeren', - 'notif.generic.title': 'Melding', - 'notif.generic.text': 'Je hebt een nieuwe melding', - 'notif.dev.unknown_event.title': '[DEV] Onbekende gebeurtenis', - 'notif.dev.unknown_event.text': 'Gebeurtenistype "{event}" is niet geregistreerd in EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'zojuist', - 'common.hoursAgo': '{count}u geleden', - 'common.daysAgo': '{count}d geleden', - 'journey.search.placeholder': 'Reizen zoeken…', - 'journey.search.noResults': 'Geen reizen komen overeen met "{query}"', - 'journey.title': 'Reisverslag', - 'journey.subtitle': 'Leg je reizen vast terwijl je onderweg bent', - 'journey.new': 'Nieuw reisverslag', - 'journey.create': 'Aanmaken', - 'journey.titlePlaceholder': 'Waar ga je naartoe?', - 'journey.empty': 'Nog geen reisverslagen', - 'journey.emptyHint': 'Begin met het vastleggen van je volgende reis', - 'journey.deleted': 'Reisverslag verwijderd', - 'journey.createError': 'Kon reisverslag niet aanmaken', - 'journey.deleteError': 'Kon reisverslag niet verwijderen', - 'journey.deleteConfirmTitle': 'Verwijderen', - 'journey.deleteConfirmMessage': '"{title}" verwijderen? Dit kan niet ongedaan worden gemaakt.', - 'journey.deleteConfirmGeneric': 'Weet je zeker dat je dit wilt verwijderen?', - 'journey.notFound': 'Reisverslag niet gevonden', - 'journey.photos': 'Foto\'s', - 'journey.timelineEmpty': 'Nog geen stops', - 'journey.timelineEmptyHint': 'Voeg een check-in toe of schrijf een dagboekvermelding om te beginnen', - 'journey.status.draft': 'Concept', - 'journey.status.active': 'Actief', - 'journey.status.completed': 'Voltooid', - 'journey.status.upcoming': 'Gepland', - 'journey.status.archived': 'Gearchiveerd', - 'journey.checkin.add': 'Inchecken', - 'journey.checkin.namePlaceholder': 'Locatienaam', - 'journey.checkin.notesPlaceholder': 'Notities (optioneel)', - 'journey.checkin.save': 'Opslaan', - 'journey.checkin.error': 'Kon check-in niet opslaan', - 'journey.entry.add': 'Dagboek', - 'journey.entry.edit': 'Vermelding bewerken', - 'journey.entry.titlePlaceholder': 'Titel (optioneel)', - 'journey.entry.bodyPlaceholder': 'Wat is er vandaag gebeurd?', - 'journey.entry.save': 'Opslaan', - 'journey.entry.error': 'Kon vermelding niet opslaan', - 'journey.photo.add': 'Foto', - 'journey.photo.uploadError': 'Uploaden mislukt', - 'journey.share.share': 'Delen', - 'journey.share.public': 'Openbaar', - 'journey.share.linkCopied': 'Openbare link gekopieerd', - 'journey.share.disabled': 'Openbaar delen uitgeschakeld', - 'journey.editor.titlePlaceholder': 'Geef dit moment een naam...', - 'journey.editor.bodyPlaceholder': 'Vertel het verhaal van deze dag...', - 'journey.editor.placePlaceholder': 'Locatie (optioneel)', - 'journey.editor.tagsPlaceholder': 'Tags: verborgen parel, beste maaltijd, moet terugkomen...', - 'journey.visibility.private': 'Privé', - 'journey.visibility.shared': 'Gedeeld', - 'journey.visibility.public': 'Openbaar', - 'journey.emptyState.title': 'Je verhaal begint hier', - 'journey.emptyState.subtitle': 'Check in op een plek of schrijf je eerste dagboekvermelding', - 'journey.frontpage.subtitle': 'Maak van je reizen verhalen die je nooit vergeet', - 'journey.frontpage.createJourney': 'Reisverslag aanmaken', - 'journey.frontpage.activeJourney': 'Actief reisverslag', - 'journey.frontpage.allJourneys': 'Alle reisverslagen', - 'journey.frontpage.journeys': 'reisverslagen', - 'journey.frontpage.createNew': 'Nieuw reisverslag aanmaken', - 'journey.frontpage.createNewSub': 'Kies reizen, schrijf verhalen, deel je avonturen', - 'journey.frontpage.live': 'Live', - 'journey.frontpage.synced': 'Gesynchroniseerd', - 'journey.frontpage.continueWriting': 'Verder schrijven', - 'journey.frontpage.updated': 'Bijgewerkt {time}', - 'journey.frontpage.suggestionLabel': 'Reis net afgelopen', - 'journey.frontpage.suggestionText': 'Maak van {title} een reisverslag', - 'journey.frontpage.dismiss': 'Sluiten', - 'journey.frontpage.journeyName': 'Naam reisverslag', - 'journey.frontpage.namePlaceholder': 'bijv. Zuidoost-Azië 2026', - 'journey.frontpage.selectTrips': 'Selecteer reizen', - 'journey.frontpage.tripsSelected': 'reizen geselecteerd', - 'journey.frontpage.trips': 'reizen', - 'journey.frontpage.placesImported': 'plaatsen worden geïmporteerd', - 'journey.frontpage.places': 'plaatsen', - 'journey.detail.backToJourney': 'Terug naar reisverslag', - 'journey.detail.syncedWithTrips': 'Gesynchroniseerd met reizen', - 'journey.detail.addEntry': 'Vermelding toevoegen', - 'journey.detail.newEntry': 'Nieuwe vermelding', - 'journey.detail.editEntry': 'Vermelding bewerken', - 'journey.detail.noEntries': 'Nog geen vermeldingen', - 'journey.detail.noEntriesHint': 'Voeg een reis toe om te beginnen met skeletvermeldingen', - 'journey.detail.noPhotos': 'Nog geen foto\'s', - 'journey.detail.noPhotosHint': 'Upload foto\'s naar vermeldingen of blader door je Immich/Synology-bibliotheek', - 'journey.detail.journeyStats': 'Reisstatistieken', - 'journey.detail.syncedTrips': 'Gesynchroniseerde reizen', - 'journey.detail.noTripsLinked': 'Nog geen reizen gekoppeld', - 'journey.detail.contributors': 'Bijdragers', - 'journey.detail.readMore': 'Lees meer', - 'journey.detail.prosCons': 'Voor- & nadelen', - 'journey.detail.photos': 'foto\'s', - 'journey.detail.day': 'Dag {number}', - 'journey.detail.places': 'plaatsen', - 'journey.stats.days': 'Dagen', - 'journey.stats.cities': 'Steden', - 'journey.stats.entries': 'Vermeldingen', - 'journey.stats.photos': 'Foto\'s', - 'journey.stats.places': 'Plaatsen', - 'journey.skeletons.show': 'Suggesties tonen', - 'journey.skeletons.hide': 'Suggesties verbergen', - 'journey.verdict.lovedIt': 'Geweldig', - 'journey.verdict.couldBeBetter': 'Kan beter', - 'journey.synced.places': 'plaatsen', - 'journey.synced.synced': 'gesynchroniseerd', - 'journey.editor.discardChangesConfirm': 'Je hebt niet-opgeslagen wijzigingen. Verwerpen?', - 'journey.editor.uploadFailed': 'Foto uploaden mislukt', - 'journey.editor.uploadPhotos': 'Foto\'s uploaden', - 'journey.editor.uploading': 'Uploaden...', - 'journey.editor.uploadingProgress': 'Uploaden {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} van {total} foto\'s mislukt — sla opnieuw op om het opnieuw te proberen', - 'journey.editor.fromGallery': 'Uit galerij', - 'journey.editor.allPhotosAdded': 'Alle foto\'s al toegevoegd', - 'journey.editor.writeStory': 'Schrijf je verhaal...', - 'journey.editor.prosCons': 'Voor- & nadelen', - 'journey.editor.pros': 'Voordelen', - 'journey.editor.cons': 'Nadelen', - 'journey.editor.proPlaceholder': 'Iets geweldigs...', - 'journey.editor.conPlaceholder': 'Niet zo geweldig...', - 'journey.editor.addAnother': 'Nog een toevoegen', - 'journey.editor.date': 'Datum', - 'journey.editor.location': 'Locatie', - 'journey.editor.searchLocation': 'Locatie zoeken...', - 'journey.editor.mood': 'Stemming', - 'journey.editor.weather': 'Weer', - 'journey.editor.photoFirst': '1e', - 'journey.editor.makeFirst': 'Maak 1e', - 'journey.editor.searching': 'Zoeken...', - 'journey.mood.amazing': 'Fantastisch', - 'journey.mood.good': 'Goed', - 'journey.mood.neutral': 'Neutraal', - 'journey.mood.rough': 'Zwaar', - 'journey.weather.sunny': 'Zonnig', - 'journey.weather.partly': 'Halfbewolkt', - 'journey.weather.cloudy': 'Bewolkt', - 'journey.weather.rainy': 'Regenachtig', - 'journey.weather.stormy': 'Stormachtig', - 'journey.weather.cold': 'Sneeuw', - 'journey.trips.linkTrip': 'Reis koppelen', - 'journey.trips.searchTrip': 'Reis zoeken', - 'journey.trips.searchPlaceholder': 'Reisnaam of bestemming...', - 'journey.trips.noTripsAvailable': 'Geen reizen beschikbaar', - 'journey.trips.link': 'Koppelen', - 'journey.trips.tripLinked': 'Reis gekoppeld', - 'journey.trips.linkFailed': 'Koppelen van reis mislukt', - 'journey.trips.addTrip': 'Reis toevoegen', - 'journey.trips.unlinkTrip': 'Reis ontkoppelen', - 'journey.trips.unlinkMessage': '"{title}" ontkoppelen? Alle gesynchroniseerde vermeldingen en foto\'s van deze reis worden permanent verwijderd. Dit kan niet ongedaan worden gemaakt.', - 'journey.trips.unlink': 'Ontkoppelen', - 'journey.trips.tripUnlinked': 'Reis ontkoppeld', - 'journey.trips.unlinkFailed': 'Ontkoppelen van reis mislukt', - 'journey.trips.noTripsLinkedSettings': 'Geen reizen gekoppeld', - 'journey.contributors.invite': 'Bijdrager uitnodigen', - 'journey.contributors.searchUser': 'Gebruiker zoeken', - 'journey.contributors.searchPlaceholder': 'Gebruikersnaam of e-mail...', - 'journey.contributors.noUsers': 'Geen gebruikers gevonden', - 'journey.contributors.role': 'Rol', - 'journey.contributors.added': 'Bijdrager toegevoegd', - 'journey.contributors.addFailed': 'Toevoegen van bijdrager mislukt', - 'journey.share.publicShare': 'Openbaar delen', - 'journey.share.createLink': 'Deellink aanmaken', - 'journey.share.linkCreated': 'Deellink aangemaakt', - 'journey.share.createFailed': 'Aanmaken van link mislukt', - 'journey.share.copy': 'Kopiëren', - 'journey.share.copied': 'Gekopieerd!', - 'journey.share.timeline': 'Tijdlijn', - 'journey.share.gallery': 'Galerij', - 'journey.share.map': 'Kaart', - 'journey.share.removeLink': 'Deellink verwijderen', - 'journey.share.linkDeleted': 'Deellink verwijderd', - 'journey.share.deleteFailed': 'Verwijderen mislukt', - 'journey.share.updateFailed': 'Bijwerken mislukt', - - // Journey — Invite - 'journey.invite.role': 'Rol', - 'journey.invite.viewer': 'Kijker', - 'journey.invite.editor': 'Bewerker', - 'journey.invite.invite': 'Uitnodigen', - 'journey.invite.inviting': 'Uitnodigen...', - 'journey.settings.title': 'Reisverslaginstellingen', - 'journey.settings.coverImage': 'Omslagfoto', - 'journey.settings.changeCover': 'Omslag wijzigen', - 'journey.settings.addCover': 'Omslagfoto toevoegen', - 'journey.settings.name': 'Naam', - 'journey.settings.subtitle': 'Ondertitel', - 'journey.settings.subtitlePlaceholder': 'bijv. Thailand, Vietnam & Cambodja', - 'journey.settings.endJourney': 'Reis archiveren', - 'journey.settings.reopenJourney': 'Reis herstellen', - 'journey.settings.archived': 'Reis gearchiveerd', - 'journey.settings.reopened': 'Reis heropend', - 'journey.settings.endDescription': 'Verbergt het Live-badge. Je kunt het altijd heropenen.', - 'journey.settings.delete': 'Verwijderen', - 'journey.settings.deleteJourney': 'Reisverslag verwijderen', - 'journey.settings.deleteMessage': '"{title}" verwijderen? Alle vermeldingen en foto\'s gaan verloren.', - 'journey.settings.saved': 'Instellingen opgeslagen', - 'journey.settings.saveFailed': 'Opslaan mislukt', - 'journey.settings.coverUpdated': 'Omslag bijgewerkt', - 'journey.settings.coverFailed': 'Uploaden mislukt', - 'journey.settings.failedToDelete': 'Verwijderen mislukt', - 'journey.entries.deleteTitle': 'Vermelding verwijderen', - 'journey.photosUploaded': "{count} foto's geüpload", - 'journey.photosUploadFailed': "Sommige foto's konden niet worden geüpload", - 'journey.photosAdded': "{count} foto's toegevoegd", - 'journey.public.notFound': 'Niet gevonden', - 'journey.public.notFoundMessage': 'Dit reisverslag bestaat niet of de link is verlopen.', - 'journey.public.readOnly': 'Alleen-lezen · Openbaar reisverslag', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Gedeeld via', - 'journey.public.madeWith': 'Gemaakt met', - 'journey.pdf.journeyBook': 'Reisboek', - 'journey.pdf.madeWith': 'Gemaakt met TREK', - 'journey.pdf.day': 'Dag', - 'journey.pdf.theEnd': 'Einde', - 'journey.pdf.saveAsPdf': 'Opslaan als PDF', - 'journey.pdf.pages': 'pagina\'s', - 'journey.picker.tripPeriod': 'Reisperiode', - 'journey.picker.dateRange': 'Datumbereik', - 'journey.picker.allPhotos': 'Alle foto\'s', - 'journey.picker.albums': 'Albums', - 'journey.picker.selected': 'geselecteerd', - 'journey.picker.addTo': 'Toevoegen aan', - 'journey.picker.newGallery': 'Nieuwe galerij', - 'journey.picker.selectAll': 'Alles selecteren', - 'journey.picker.deselectAll': 'Alles deselecteren', - 'journey.picker.noAlbums': 'Geen albums gevonden', - 'journey.picker.selectDate': 'Selecteer datum', - 'journey.picker.search': 'Zoeken', - 'dashboard.greeting.morning': 'Goedemorgen,', - 'dashboard.greeting.afternoon': 'Goedemiddag,', - 'dashboard.greeting.evening': 'Goedenavond,', - 'dashboard.mobile.liveNow': 'Nu live', - 'dashboard.mobile.tripProgress': 'Reisvoortgang', - 'dashboard.mobile.daysLeft': '{count} dagen over', - 'dashboard.mobile.places': 'Plaatsen', - 'dashboard.mobile.buddies': 'Reisgenoten', - 'dashboard.mobile.newTrip': 'Nieuwe reis', - 'dashboard.mobile.currency': 'Valuta', - 'dashboard.mobile.timezone': 'Tijdzone', - 'dashboard.mobile.upcomingTrips': 'Aankomende reizen', - 'dashboard.mobile.yourTrips': 'Jouw reizen', - 'dashboard.mobile.trips': 'reizen', - 'dashboard.mobile.starts': 'Begint', - 'dashboard.mobile.duration': 'Duur', - 'dashboard.mobile.day': 'dag', - 'dashboard.mobile.days': 'dagen', - 'dashboard.mobile.ongoing': 'Bezig', - 'dashboard.mobile.startsToday': 'Begint vandaag', - 'dashboard.mobile.tomorrow': 'Morgen', - 'dashboard.mobile.inDays': 'Over {count} dagen', - 'dashboard.mobile.inMonths': 'Over {count} maanden', - 'dashboard.mobile.completed': 'Voltooid', - 'dashboard.mobile.currencyConverter': 'Valutaomrekener', - 'nav.profile': 'Profiel', - 'nav.bottomSettings': 'Instellingen', - 'nav.bottomAdmin': 'Beheerdersinstellingen', - 'nav.bottomLogout': 'Uitloggen', - 'nav.bottomAdminBadge': 'Beheerder', - 'dayplan.mobile.addPlace': 'Plaats toevoegen', - 'dayplan.mobile.searchPlaces': 'Plaatsen zoeken...', - 'dayplan.mobile.allAssigned': 'Alle plaatsen toegewezen', - 'dayplan.mobile.noMatch': 'Geen resultaat', - 'dayplan.mobile.createNew': 'Nieuwe plaats aanmaken', - 'admin.addons.catalog.journey.name': 'Reisverslag', - 'admin.addons.catalog.journey.description': 'Reistracking & reisdagboek met check-ins, foto\'s en dagelijkse verhalen', - // OAuth scope groups - 'oauth.scope.group.trips': 'Reizen', - 'oauth.scope.group.places': 'Plaatsen', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Paklijst', - 'oauth.scope.group.todos': 'Taken', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Reserveringen', - 'oauth.scope.group.collab': 'Samenwerking', - 'oauth.scope.group.notifications': 'Meldingen', - 'oauth.scope.group.vacay': 'Vakantie', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Weer', - 'oauth.scope.group.journey': 'Reisverslag', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Reizen en reisplannen bekijken', - 'oauth.scope.trips:read.description': 'Reizen, dagen, notities en leden lezen', - 'oauth.scope.trips:write.label': 'Reizen en reisplannen bewerken', - 'oauth.scope.trips:write.description': 'Reizen, dagen en notities aanmaken, bijwerken en leden beheren', - 'oauth.scope.trips:delete.label': 'Reizen verwijderen', - 'oauth.scope.trips:delete.description': 'Hele reizen permanent verwijderen — deze actie is onomkeerbaar', - 'oauth.scope.trips:share.label': 'Deellinks beheren', - 'oauth.scope.trips:share.description': 'Publieke deellinks aanmaken, bijwerken en intrekken', - 'oauth.scope.places:read.label': 'Plaatsen en kaartgegevens bekijken', - 'oauth.scope.places:read.description': 'Plaatsen, dagtoewijzingen, tags en categorieën lezen', - 'oauth.scope.places:write.label': 'Plaatsen beheren', - 'oauth.scope.places:write.description': 'Plaatsen, toewijzingen en tags aanmaken, bijwerken en verwijderen', - 'oauth.scope.atlas:read.label': 'Atlas bekijken', - 'oauth.scope.atlas:read.description': 'Bezochte landen, regio\'s en bucketlist lezen', - 'oauth.scope.atlas:write.label': 'Atlas beheren', - 'oauth.scope.atlas:write.description': 'Landen en regio\'s markeren als bezocht, bucketlist beheren', - 'oauth.scope.packing:read.label': 'Paklijsten bekijken', - 'oauth.scope.packing:read.description': 'Pakartikelen, tassen en categorietoewijzingen lezen', - 'oauth.scope.packing:write.label': 'Paklijsten beheren', - 'oauth.scope.packing:write.description': 'Pakartikelen en tassen toevoegen, bijwerken, verwijderen, omschakelen en herordenen', - 'oauth.scope.todos:read.label': 'Takenlijsten bekijken', - 'oauth.scope.todos:read.description': 'Reistaakitems en categorietoewijzingen lezen', - 'oauth.scope.todos:write.label': 'Takenlijsten beheren', - 'oauth.scope.todos:write.description': 'Taakitems aanmaken, bijwerken, omschakelen, verwijderen en herordenen', - 'oauth.scope.budget:read.label': 'Budget bekijken', - 'oauth.scope.budget:read.description': 'Budgetitems en kostenspecificatie lezen', - 'oauth.scope.budget:write.label': 'Budget beheren', - 'oauth.scope.budget:write.description': 'Budgetitems aanmaken, bijwerken en verwijderen', - 'oauth.scope.reservations:read.label': 'Reserveringen bekijken', - 'oauth.scope.reservations:read.description': 'Reserveringen en accommodatiedetails lezen', - 'oauth.scope.reservations:write.label': 'Reserveringen beheren', - 'oauth.scope.reservations:write.description': 'Reserveringen aanmaken, bijwerken, verwijderen en herordenen', - 'oauth.scope.collab:read.label': 'Samenwerking bekijken', - 'oauth.scope.collab:read.description': 'Samenwerkingsnotities, polls en berichten lezen', - 'oauth.scope.collab:write.label': 'Samenwerking beheren', - 'oauth.scope.collab:write.description': 'Samenwerkingsnotities, polls en berichten aanmaken, bijwerken en verwijderen', - 'oauth.scope.notifications:read.label': 'Meldingen bekijken', - 'oauth.scope.notifications:read.description': 'In-app meldingen en ongelezen aantallen lezen', - 'oauth.scope.notifications:write.label': 'Meldingen beheren', - 'oauth.scope.notifications:write.description': 'Meldingen als gelezen markeren en erop reageren', - 'oauth.scope.vacay:read.label': 'Vakantieplannen bekijken', - 'oauth.scope.vacay:read.description': 'Vakantieplanningsgegevens, invoeren en statistieken lezen', - 'oauth.scope.vacay:write.label': 'Vakantieplannen beheren', - 'oauth.scope.vacay:write.description': 'Vakantie-invoeren, feestdagen en teamplannen aanmaken en beheren', - 'oauth.scope.geo:read.label': 'Kaarten & geocodering', - 'oauth.scope.geo:read.description': 'Locaties zoeken, kaart-URL\'s oplossen en coördinaten omgekeerd geocoderen', - 'oauth.scope.weather:read.label': 'Weersverwachtingen', - 'oauth.scope.weather:read.description': 'Weersverwachtingen ophalen voor reislocaties en -datums', - 'oauth.scope.journey:read.label': 'Reisverslagen bekijken', - 'oauth.scope.journey:read.description': 'Reisverslagen, vermeldingen en lijst van bijdragers lezen', - 'oauth.scope.journey:write.label': 'Reisverslagen beheren', - 'oauth.scope.journey:write.description': 'Reisverslagen en hun vermeldingen aanmaken, bijwerken en verwijderen', - 'oauth.scope.journey:share.label': 'Reisverslag-links beheren', - 'oauth.scope.journey:share.description': 'Publieke deellinks voor reisverslagen aanmaken, bijwerken en intrekken', - - // System notices - 'system_notice.welcome_v1.title': 'Welkom bij TREK', - 'system_notice.welcome_v1.body': 'Jouw alles-in-één reisplanner. Maak reisschema\'s, deel trips met vrienden en blijf georganiseerd — online en offline.', - 'system_notice.welcome_v1.cta_label': 'Reis plannen', - 'system_notice.welcome_v1.hero_alt': 'Schilderachtige reisbestemming met TREK interface', - 'system_notice.welcome_v1.highlight_plan': 'Dag-voor-dag reisschema\'s', - 'system_notice.welcome_v1.highlight_share': 'Samenwerken met reisgezelschap', - 'system_notice.welcome_v1.highlight_offline': 'Werkt offline op mobiel', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Vorige melding', - 'system_notice.pager.next': 'Volgende melding', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Ga naar melding {n}', - 'system_notice.pager.position': 'Melding {current} van {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': "Foto's zijn verplaatst in 3.0", - 'system_notice.v3_photos.body': "**Foto's** in de Reisplanner zijn verwijderd. Je foto's zijn veilig — TREK heeft je Immich- of Synology-bibliotheek nooit gewijzigd.\n\nFoto's leven nu in de **Journey**-addon. Journey is optioneel — als het nog niet beschikbaar is, vraag je admin het te activeren via Admin → Addons.", - 'system_notice.v3_journey.title': 'Maak kennis met Journey — reisdagboek', - 'system_notice.v3_journey.body': 'Documenteer je reizen als rijke verhalen met tijdlijnen, fotogalerijen en interactieve kaarten.', - 'system_notice.v3_journey.cta_label': 'Journey openen', - 'system_notice.v3_journey.highlight_timeline': 'Dag-voor-dag tijdlijn & galerij', - 'system_notice.v3_journey.highlight_photos': 'Importeer van Immich of Synology', - 'system_notice.v3_journey.highlight_share': 'Openbaar delen — geen login vereist', - 'system_notice.v3_journey.highlight_export': 'Exporteer als PDF-fotoboek', - 'system_notice.v3_features.title': 'Meer hoogtepunten in 3.0', - 'system_notice.v3_features.body': 'Nog een paar dingen die het weten waard zijn in deze release.', - 'system_notice.v3_features.highlight_dashboard': 'Mobile-first dashboard herontwerp', - 'system_notice.v3_features.highlight_offline': 'Volledige offline modus als PWA', - 'system_notice.v3_features.highlight_search': 'Realtime plaatsautocomplete', - 'system_notice.v3_features.highlight_import': 'Importeer plaatsen uit KMZ/KML-bestanden', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1-upgrade', - 'system_notice.v3_mcp.body': 'De MCP-integratie is volledig vernieuwd. OAuth 2.1 is nu de aanbevolen authenticatiemethode. Statische tokens (trek_…) zijn verouderd en worden verwijderd in een toekomstige versie.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 aanbevolen (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 gedetailleerde toestemmingsscopes', - 'system_notice.v3_mcp.highlight_deprecated': 'Statische trek_-tokens verouderd', - 'system_notice.v3_mcp.highlight_tools': 'Uitgebreide tools & prompts', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Een persoonlijk woord van mij', - 'system_notice.v3_thankyou.body': 'Voordat je verdergaat — ik wil even stilstaan.\n\nTREK begon als een zijproject dat ik bouwde voor mijn eigen reizen. Ik had nooit gedacht dat het zou uitgroeien tot iets waar 4.000 van jullie op vertrouwen om avonturen te plannen. Elke ster, elke issue, elk functieverzoek — ik lees ze allemaal, en ze houden me op de been tijdens de late avonden tussen een fulltime baan en de universiteit.\n\nIk wil dat jullie weten: TREK zal altijd open source zijn, altijd self-hosted, altijd van jullie. Geen tracking, geen abonnementen, geen addertjes. Gewoon een tool gebouwd door iemand die net zo veel van reizen houdt als jullie.\n\nSpeciale dank aan [jubnl](https://github.com/jubnl) — je bent een ongelooflijke medewerker geworden. Zo veel van wat 3.0 geweldig maakt draagt jouw vingerafdruk. Bedankt dat je in dit project geloofde toen het nog ruw was.\n\nEn aan ieder van jullie die een bug meldde, een string vertaalde, TREK deelde met een vriend of het simpelweg gebruikte om een reis te plannen — **bedankt**. Jullie zijn de reden dat dit bestaat.\n\nOp nog vele avonturen samen.\n\n— Maurice\n\n---\n\n[Sluit je aan bij de community op Discord](https://discord.gg/7Q6M6jDwzf)\n\nAls TREK je reizen beter maakt, houdt een [klein kopje koffie](https://ko-fi.com/mauriceboe) altijd de lichten aan.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Actie vereist: gebruikersaccountconflict', - 'system_notice.v3014_whitespace_collision.body': 'De 3.0.14-upgrade heeft één of meer conflicten in gebruikersnaam of e-mailadres gedetecteerd, veroorzaakt door spaties aan het begin of einde van opgeslagen waarden. Getroffen accounts zijn automatisch hernoemd. Controleer de serverlogboeken op regels die beginnen met **[migration] WHITESPACE COLLISION** om te achterhalen welke accounts moeten worden beoordeeld.', - 'transport.addTransport': 'Vervoer toevoegen', - 'transport.modalTitle.create': 'Vervoer toevoegen', - 'transport.modalTitle.edit': 'Vervoer bewerken', - 'transport.title': 'Transport', - 'transport.addManual': 'Handmatig transport', -} - -export default nl - diff --git a/client/src/i18n/translations/pl.ts b/client/src/i18n/translations/pl.ts deleted file mode 100644 index ce085b8f..00000000 --- a/client/src/i18n/translations/pl.ts +++ /dev/null @@ -1,2359 +0,0 @@ -const pl: Record = { - // Common - 'common.save': 'Zapisz', - 'common.showMore': 'Pokaż więcej', - 'common.showLess': 'Pokaż mniej', - 'common.cancel': 'Anuluj', - 'common.clear': 'Wyczyść', - 'common.delete': 'Usuń', - 'common.edit': 'Edytuj', - 'common.add': 'Dodaj', - 'common.loading': 'Ładowanie...', - 'common.error': 'Błąd', - 'common.unknownError': 'Nieznany błąd', - 'common.tooManyAttempts': 'Zbyt wiele prób. Spróbuj ponownie później.', - 'common.back': 'Wstecz', - 'common.all': 'Wszystko', - 'common.close': 'Zamknij', - 'common.open': 'Otwórz', - 'common.upload': 'Prześlij', - 'common.search': 'Szukaj', - 'common.confirm': 'Potwierdź', - 'common.ok': 'OK', - 'common.yes': 'Tak', - 'common.no': 'Nie', - 'common.or': 'lub', - 'common.none': 'Brak', - 'common.date': 'Data', - 'common.rename': 'Zmień nazwę', - 'common.discardChanges': 'Odrzuć zmiany', - 'common.discard': 'Odrzuć', - 'common.name': 'Nazwa', - 'common.email': 'E-mail', - 'common.password': 'Hasło', - 'common.saving': 'Zapisywanie...', - 'trips.memberRemoved': '{username} usunięty', - 'trips.memberRemoveError': 'Nie udało się usunąć', - 'trips.memberAdded': '{username} dodany', - 'trips.memberAddError': 'Nie udało się dodać', - 'common.expand': 'Rozwiń', - 'common.collapse': 'Zwiń', - 'common.update': 'Aktualizuj', - 'common.change': 'Zmień', - 'common.uploading': 'Przesyłanie...', - 'common.backToPlanning': 'Powrót do planowania', - 'common.reset': 'Resetuj', - - // Navbar - 'nav.trip': 'Podróż', - 'nav.share': 'Udostępnij', - 'nav.settings': 'Ustawienia', - 'nav.admin': 'Admin', - 'nav.logout': 'Wyloguj', - 'nav.lightMode': 'Tryb jasny', - 'nav.darkMode': 'Tryb ciemny', - 'nav.autoMode': 'Tryb automatyczny', - 'nav.administrator': 'Administrator', - - // Dashboard - 'dashboard.title': 'Moje podróże', - 'dashboard.subtitle.loading': 'Ładowanie podróży...', - 'dashboard.subtitle.trips': '{count} podróży ({archived} zarchiwizowanych)', - 'dashboard.subtitle.empty': 'Rozpocznij swoją pierwszą podróż', - 'dashboard.subtitle.activeOne': '{count} aktywna podróż', - 'dashboard.subtitle.activeMany': '{count} aktywnych podróży', - 'dashboard.subtitle.archivedSuffix': ' · {count} zarchiwizowanych', - 'dashboard.newTrip': 'Nowa podróż', - 'dashboard.gridView': 'Widok siatki', - 'dashboard.listView': 'Widok listy', - 'dashboard.currency': 'Waluta', - 'dashboard.timezone': 'Strefy czasowe', - 'dashboard.localTime': 'Czas lokalny', - 'dashboard.timezoneCustomTitle': 'Własna strefa czasowa', - 'dashboard.timezoneCustomLabelPlaceholder': 'Nazwa (opcjonalnie)', - 'dashboard.timezoneCustomTzPlaceholder': 'np. Europe/Warsaw', - 'dashboard.timezoneCustomAdd': 'Dodaj', - 'dashboard.timezoneCustomErrorEmpty': 'Podaj identyfikator strefy czasowej', - 'dashboard.timezoneCustomErrorInvalid': 'Nieprawidłowa strefa czasowa. Użyj formatu takiego jak Europe/Warsaw', - 'dashboard.timezoneCustomErrorDuplicate': 'Już dodana', - 'dashboard.emptyTitle': 'Brak podróży', - 'dashboard.emptyText': 'Utwórz swoją pierwszą podróż i zacznij planować!', - 'dashboard.emptyButton': 'Utwórz pierwszą podróż', - 'dashboard.nextTrip': 'Następna podróż', - 'dashboard.shared': 'Udostępniona', - 'dashboard.sharedBy': 'Udostępniona przez {name}', - 'dashboard.days': 'Dni', - 'dashboard.places': 'Miejsca', - 'dashboard.archive': 'Archiwizuj', - 'dashboard.restore': 'Przywróć', - 'dashboard.archived': 'Zarchiwizowana', - 'dashboard.status.ongoing': 'W trakcie', - 'dashboard.status.today': 'Dzisiaj', - 'dashboard.status.tomorrow': 'Jutro', - 'dashboard.status.past': 'Zakończona', - 'dashboard.status.daysLeft': '{count} dni do końca', - 'dashboard.toast.loadError': 'Nie udało się załadować podróży', - 'dashboard.toast.created': 'Podróż została utworzona pomyślnie!', - 'dashboard.toast.createError': 'Nie udało się utworzyć podróży', - 'dashboard.toast.updated': 'Podróż została zaktualizowana!', - 'dashboard.toast.updateError': 'Nie udało się zaktualizować podróży', - 'dashboard.toast.deleted': 'Podróż została usunięta', - 'dashboard.toast.deleteError': 'Nie udało się usunąć podróży', - 'dashboard.toast.archived': 'Podróż została zarchiwizowana', - 'dashboard.toast.archiveError': 'Nie udało się zarchiwizować podróży', - 'dashboard.toast.restored': 'Podróż została przywrócona', - 'dashboard.toast.restoreError': 'Nie udało się przywrócić podróży', - 'dashboard.confirm.delete': 'Usunąć podróż "{title}"? Wszystkie miejsca i plany zostaną trwale usunięte.', - 'dashboard.editTrip': 'Edytuj podróż', - 'dashboard.createTrip': 'Utwórz nową podróż', - 'dashboard.tripTitle': 'Nazwa podróży', - 'dashboard.tripTitlePlaceholder': 'np. Lato w Japonii', - 'dashboard.tripDescription': 'Opis', - 'dashboard.tripDescriptionPlaceholder': 'Opisz swoją podróż', - 'dashboard.startDate': 'Data rozpoczęcia', - 'dashboard.endDate': 'Data zakończenia', - 'dashboard.dayCount': 'Liczba dni', - 'dashboard.dayCountHint': 'Ile dni zaplanować, gdy nie ustawiono dat podróży.', - 'dashboard.noDateHint': 'Nie ustawiono daty — zostanie utworzonych 7 domyślnych dni. Możesz to zmienić w dowolnym momencie.', - 'dashboard.coverImage': 'Okładka', - 'dashboard.addCoverImage': 'Dodaj okładkę (lub przeciągnij i upuść)', - 'dashboard.addMembers': 'Uczestnicy podróży', - 'dashboard.addMember': 'Dodaj uczestnika', - 'dashboard.coverSaved': 'Okładka została zapisana', - 'dashboard.coverUploadError': 'Nie udało się przesłać okładki', - 'dashboard.coverRemoveError': 'Nie udało się usunąć okładki', - 'dashboard.titleRequired': 'Nazwa podróży jest wymagana', - 'dashboard.endDateError': 'Data zakończenia musi być po dacie rozpoczęcia', - - // Settings - 'settings.title': 'Ustawienia', - 'settings.subtitle': 'Skonfiguruj swoje ustawienia', - 'settings.tabs.display': 'Wygląd', - 'settings.tabs.map': 'Mapa', - 'settings.tabs.notifications': 'Powiadomienia', - 'settings.tabs.integrations': 'Integracje', - 'settings.tabs.account': 'Konto', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'O aplikacji', - 'settings.map': 'Mapa', - 'settings.mapTemplate': 'Szablon mapy', - 'settings.mapTemplatePlaceholder.select': 'Wybierz szablon...', - 'settings.mapDefaultHint': 'Pozostaw puste dla OpenStreetMap (domyślnie)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'Szablon URL dla kafelków mapy', - 'settings.mapProvider': 'Dostawca mapy', - 'settings.mapProviderHint': 'Dotyczy map Trip Planner i Journey. Atlas zawsze używa Leaflet.', - 'settings.mapLeafletSubtitle': 'Klasyczne 2D, dowolne kafelki rastrowe', - 'settings.mapMapboxSubtitle': 'Kafelki wektorowe, budynki 3D i teren', - 'settings.mapExperimental': 'Eksperymentalne', - 'settings.mapMapboxToken': 'Token dostępu Mapbox', - 'settings.mapMapboxTokenHint': 'Token publiczny (pk.*) z', - 'settings.mapMapboxTokenLink': 'mapbox.com → Tokeny dostępu', - 'settings.mapStyle': 'Styl mapy', - 'settings.mapStylePlaceholder': 'Wybierz styl Mapbox', - 'settings.mapStyleHint': 'Preset lub własny URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': 'Budynki 3D i teren', - 'settings.map3dHint': 'Nachylenie + prawdziwe wytłaczanie budynków 3D — działa w każdym stylu, także satelitarnym.', - 'settings.mapHighQuality': 'Tryb wysokiej jakości', - 'settings.mapHighQualityHint': 'Antialiasing + projekcja globusa dla ostrzejszych krawędzi i realistycznego widoku świata.', - 'settings.mapHighQualityWarning': 'Może wpływać na wydajność na słabszych urządzeniach.', - 'settings.mapTipLabel': 'Wskazówka:', - 'settings.mapTip': 'Kliknij prawym przyciskiem i przeciągnij, aby obrócić/pochylić mapę. Środkowy przycisk dodaje miejsce (prawy jest zarezerwowany dla obrotu).', - 'settings.latitude': 'Szerokość', - 'settings.longitude': 'Długość', - 'settings.saveMap': 'Zapisz mapę', - 'settings.apiKeys': 'Klucze API', - 'settings.mapsKey': 'Klucz Google Maps API', - 'settings.mapsKeyHint': 'Do wyszukiwania miejsc. Wymaga Places API (New). Uzyskaj dostęp na console.cloud.google.com', - 'settings.weatherKey': 'Klucz OpenWeatherMap API', - 'settings.weatherKeyHint': 'Do danych pogodowych. Bezpłatnie na openweathermap.org/api', - 'settings.keyPlaceholder': 'Podaj klucz...', - 'settings.configured': 'Skonfigurowano', - 'settings.saveKeys': 'Zapisz klucze', - 'settings.display': 'Preferencje', - 'settings.colorMode': 'Motyw', - 'settings.light': 'Jasny', - 'settings.dark': 'Ciemny', - 'settings.auto': 'Automatyczny', - 'settings.language': 'Język', - 'settings.temperature': 'Jednostka temperatury', - 'settings.timeFormat': 'Format czasu', - 'settings.blurBookingCodes': 'Rozmyj kody rezerwacji', - 'settings.notifications': 'Powiadomienia', - 'settings.notifyTripInvite': 'Zaproszenia do podróży', - 'settings.notifyBookingChange': 'Zmiany w rezerwacjach', - 'settings.notifyTripReminder': 'Przypomnienia o podróżach', - 'settings.notifyTodoDue': 'Zadanie z terminem', - 'settings.notifyVacayInvite': 'Zaproszenia do połączenia kalendarzy', - 'settings.notifyPhotosShared': 'Udostępnione zdjęcia (Immich)', - 'settings.notifyCollabMessage': 'Wiadomości czatu (Collab)', - 'settings.notifyPackingTagged': 'Lista pakowania: przypisania', - 'settings.notifyWebhook': 'Powiadomienia Webhook', - 'settings.notifyVersionAvailable': 'Nowa wersja dostępna', - 'admin.smtp.title': 'E-maile i powiadomienia', - 'admin.smtp.hint': 'Konfiguracja SMTP dla powiadomień e-mail. Opcjonalnie: URL Webhooka dla Discorda, Slacka, itp.', - 'admin.smtp.testButton': 'Wyślij testowego e-maila', - 'admin.smtp.testSuccess': 'Testowy e-mail został wysłany pomyślnie', - 'admin.smtp.testFailed': 'Nie udało się wysłać testowego e-maila', - 'dayplan.icsTooltip': 'Eksportuj kalendarz (ICS)', - 'share.linkTitle': 'Publiczny link', - 'share.linkHint': 'Utwórz link umożliwiający przeglądanie tej podróży bez logowania. Tylko do odczytu — bez możliwości edycji.', - 'share.createLink': 'Utwórz link', - 'share.deleteLink': 'Usuń link', - 'share.createError': 'Nie udało się utworzyć linku', - 'common.copy': 'Kopiuj', - 'common.copied': 'Skopiowano', - 'share.permMap': 'Mapa i plan', - 'share.permBookings': 'Rezerwacje', - 'share.permPacking': 'Lista pakowania', - 'shared.expired': 'Link wygasł lub jest nieprawidłowy', - 'shared.expiredHint': 'Ten link do podróży jest już nieaktywny.', - 'shared.readOnly': 'Widok udostępniony tylko do odczytu', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Rezerwacje', - 'shared.tabPacking': 'Lista pakowania', - 'shared.tabBudget': 'Budżet', - 'shared.tabChat': 'Czat', - 'shared.days': 'dni', - 'shared.places': 'miejsca', - 'shared.other': 'Inne', - 'shared.totalBudget': 'Całkowity budżet', - 'shared.messages': 'wiadomości', - 'shared.sharedVia': 'Udostępnione przez', - 'shared.confirmed': 'Potwierdzone', - 'shared.pending': 'Oczekujące', - 'share.permBudget': 'Budżet', - 'share.permCollab': 'Czat', - 'settings.on': 'Włączone', - 'settings.off': 'Wyłączone', - 'settings.mcp.title': 'Konfiguracja MCP', - 'settings.mcp.endpoint': 'Endpoint MCP', - 'settings.mcp.clientConfig': 'Konfiguracja klienta', - 'settings.mcp.clientConfigHint': 'Zastąp tokenem API z listy poniżej. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Zastąp i danymi uwierzytelniającymi z klienta OAuth 2.1 utworzonego powyżej. mcp-remote otworzy przeglądarkę, aby dokończyć autoryzację przy pierwszym połączeniu. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).', - 'settings.mcp.copy': 'Kopiuj', - 'settings.mcp.copied': 'Skopiowano!', - 'settings.mcp.apiTokens': 'Tokeny API', - 'settings.mcp.createToken': 'Utwórz nowy token', - 'settings.mcp.noTokens': 'Brak tokenów. Utwórz go, aby połączyć klientów MCP.', - 'settings.mcp.tokenCreatedAt': 'Utworzono', - 'settings.mcp.tokenUsedAt': 'Użyto', - 'settings.mcp.deleteTokenTitle': 'Usuń token', - 'settings.mcp.deleteTokenMessage': 'Ten token przestanie działać natychmiastowo. Każdy klient MCP używający go straci dostęp.', - 'settings.mcp.modal.createTitle': 'Utwórz token API', - 'settings.mcp.modal.tokenName': 'Nazwa tokenu', - 'settings.mcp.modal.tokenNamePlaceholder': 'np. Claude Desktop, Laptop służbowy', - 'settings.mcp.modal.creating': 'Tworzenie...', - 'settings.mcp.modal.create': 'Utwórz token', - 'settings.mcp.modal.createdTitle': 'Token został utworzony', - 'settings.mcp.modal.createdWarning': 'Ten token zostanie wyświetlony tylko raz. Skopiuj i zapisz go — nie będzie można go zobaczyć ponownie.', - 'settings.mcp.modal.done': 'Gotowe', - 'settings.mcp.toast.created': 'Token został utworzony', - 'settings.mcp.toast.createError': 'Nie udało się utworzyć tokenu', - 'settings.mcp.toast.deleted': 'Token został usunięty', - 'settings.mcp.toast.deleteError': 'Nie udało się usunąć tokenu', - 'settings.mcp.apiTokensDeprecated': 'Tokeny API są przestarzałe i zostaną usunięte w przyszłej wersji. Użyj zamiast tego klientów OAuth 2.1.', - 'settings.oauth.clients': 'Klienci OAuth 2.1', - 'settings.oauth.clientsHint': 'Zarejestruj klientów OAuth 2.1, aby zewnętrzne aplikacje MCP (Claude Web, Cursor itp.) mogły się łączyć bez statycznych tokenów.', - 'settings.oauth.createClient': 'Nowy klient', - 'settings.oauth.noClients': 'Brak zarejestrowanych klientów OAuth.', - 'settings.oauth.clientId': 'ID klienta', - 'settings.oauth.clientSecret': 'Sekret klienta', - 'settings.oauth.deleteClient': 'Usuń klienta', - 'settings.oauth.deleteClientMessage': 'Ten klient i wszystkie aktywne sesje zostaną trwale usunięte. Każda aplikacja, która go używa, natychmiast utraci dostęp.', - 'settings.oauth.rotateSecret': 'Odnów sekret', - 'settings.oauth.rotateSecretMessage': 'Zostanie wygenerowany nowy sekret klienta, a wszystkie istniejące sesje zostaną natychmiast unieważnione. Zaktualizuj aplikację przed zamknięciem tego okna.', - 'settings.oauth.rotateSecretConfirm': 'Odnów', - 'settings.oauth.rotateSecretConfirming': 'Odnawianie…', - 'settings.oauth.rotateSecretDoneTitle': 'Wygenerowano nowy sekret', - 'settings.oauth.rotateSecretDoneWarning': 'Ten sekret jest wyświetlany tylko raz. Skopiuj go teraz i zaktualizuj aplikację — wszystkie poprzednie sesje zostały unieważnione.', - 'settings.oauth.activeSessions': 'Aktywne sesje OAuth', - 'settings.oauth.sessionScopes': 'Uprawnienia', - 'settings.oauth.sessionExpires': 'Wygasa', - 'settings.oauth.revoke': 'Unieważnij', - 'settings.oauth.revokeSession': 'Unieważnij sesję', - 'settings.oauth.revokeSessionMessage': 'Spowoduje to natychmiastowe unieważnienie dostępu dla tej sesji OAuth.', - 'settings.oauth.modal.createTitle': 'Zarejestruj klienta OAuth', - 'settings.oauth.modal.presets': 'Szybkie ustawienia', - 'settings.oauth.modal.clientName': 'Nazwa aplikacji', - 'settings.oauth.modal.clientNamePlaceholder': 'np. Claude Web, Moja aplikacja MCP', - 'settings.oauth.modal.redirectUris': 'URI przekierowania', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Jeden URI na linię. Wymagane HTTPS (localhost zwolniony). Wymagana dokładna zgodność.', - 'settings.oauth.modal.scopes': 'Dozwolone uprawnienia', - 'settings.oauth.modal.scopesHint': 'list_trips i get_trip_summary są zawsze dostępne — bez wymaganych uprawnień. Umożliwiają AI odkrycie potrzebnych ID podróży.', - 'settings.oauth.modal.selectAll': 'Zaznacz wszystko', - 'settings.oauth.modal.deselectAll': 'Odznacz wszystko', - 'settings.oauth.modal.creating': 'Rejestrowanie…', - 'settings.oauth.modal.create': 'Zarejestruj klienta', - 'settings.oauth.modal.createdTitle': 'Klient zarejestrowany', - 'settings.oauth.modal.createdWarning': 'Sekret klienta jest wyświetlany tylko raz. Skopiuj go teraz — nie można go odzyskać.', - 'settings.oauth.toast.createError': 'Nie udało się zarejestrować klienta OAuth', - 'settings.oauth.toast.deleted': 'Klient OAuth usunięty', - 'settings.oauth.toast.deleteError': 'Nie udało się usunąć klienta OAuth', - 'settings.oauth.toast.revoked': 'Sesja unieważniona', - 'settings.oauth.toast.revokeError': 'Nie udało się unieważnić sesji', - 'settings.oauth.toast.rotateError': 'Nie udało się odnowić sekretu klienta', - 'settings.oauth.modal.machineClient': 'Klient maszynowy (bez logowania przez przeglądarkę)', - 'settings.oauth.modal.machineClientHint': 'Używa grantu client_credentials — nie są potrzebne URI przekierowania. Token jest wystawiany bezpośrednio przez client_id + client_secret i działa w Twoim imieniu w ramach wybranych zakresów.', - 'settings.oauth.modal.machineClientUsage': 'Pobierz token: POST /oauth/token z grant_type=client_credentials, client_id i client_secret. Bez przeglądarki, bez tokenu odświeżania.', - 'settings.oauth.badge.machine': 'maszynowy', - 'settings.account': 'Konto', - 'settings.about': 'O aplikacji', - 'settings.about.reportBug': 'Zgłoś błąd', - 'settings.about.reportBugHint': 'Znalazłeś problem? Daj nam znać', - 'settings.about.featureRequest': 'Zaproponuj funkcję', - 'settings.about.featureRequestHint': 'Zaproponuj nową funkcję', - 'settings.about.wikiHint': 'Dokumentacja i poradniki', - 'settings.about.supporters.badge': 'Miesięczni Patroni', - 'settings.about.supporters.title': 'Towarzystwo podróży dla TREK', - 'settings.about.supporters.subtitle': 'Gdy planujesz kolejną trasę, te osoby planują razem ze mną przyszłość TREK. Ich comiesięczny wkład idzie bezpośrednio na rozwój i realnie przepracowane godziny — aby TREK pozostał Open Source.', - 'settings.about.supporters.since': 'patron od {date}', - 'settings.about.supporters.tierEmpty': 'Bądź pierwszy', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK to samodzielnie hostowany planer podróży, który pomaga organizować wyprawy od pierwszego pomysłu po ostatnie wspomnienie. Planowanie dzienne, budżet, listy pakowania, zdjęcia i wiele więcej — wszystko w jednym miejscu, na własnym serwerze.', - 'settings.about.madeWith': 'Stworzone z', - 'settings.about.madeBy': 'przez Maurice\'a i rosnącą społeczność open-source.', - 'settings.username': 'Nazwa użytkownika', - 'settings.email': 'E-mail', - 'settings.role': 'Rola', - 'settings.roleAdmin': 'Administrator', - 'settings.oidcLinked': 'Połączono z', - 'settings.changePassword': 'Zmień hasło', - 'settings.currentPassword': 'Aktualne hasło', - 'settings.currentPasswordRequired': 'Aktualne hasło jest wymagane', - 'settings.newPassword': 'Nowe hasło', - 'settings.confirmPassword': 'Potwierdź nowe hasło', - 'settings.updatePassword': 'Zaktualizuj hasło', - 'settings.passwordRequired': 'Proszę podać aktualne i nowe hasło', - 'settings.passwordTooShort': 'Hasło musi mieć co najmniej 8 znaków', - 'settings.passwordMismatch': 'Hasła nie są identyczne', - 'settings.passwordWeak': 'Hasło musi zawierać wielką literę, małą literę i cyfrę', - 'settings.passwordChanged': 'Hasło zostało zmienione pomyślnie', - 'settings.deleteAccount': 'Usuń konto', - 'settings.deleteAccountTitle': 'Usunąć twoje konto?', - 'settings.deleteAccountWarning': 'Twoje konto i wszystkie twoje podróże, miejsca i pliki zostaną trwale usunięte. Tej akcji nie można cofnąć.', - 'settings.deleteAccountConfirm': 'Usuń na zawsze', - 'settings.deleteBlockedTitle': 'Nie można usunąć konta', - 'settings.deleteBlockedMessage': 'Jesteś jedynym administratorem. Wyznacz innego użytkownika na administratora przed usunięciem konta.', - 'settings.roleUser': 'Użytkownik', - 'settings.saveProfile': 'Zapisz profil', - 'settings.toast.mapSaved': 'Ustawienia mapy zostały zapisane', - 'settings.toast.keysSaved': 'Klucze API zostały zapisane', - 'settings.toast.displaySaved': 'Preferencje zostały zapisane', - 'settings.toast.profileSaved': 'Profil został zapisany', - 'settings.uploadAvatar': 'Prześlij zdjęcie profilowe', - 'settings.removeAvatar': 'Usuń zdjęcie profilowe', - 'settings.avatarUploaded': 'Zdjęcie profilowe zostało zaktualizowane', - 'settings.avatarRemoved': 'Zdjęcie profilowe zostało usunięte', - 'settings.avatarError': 'Przesyłanie nie powiodło się', - 'settings.mfa.title': 'Uwierzytelnianie dwuskładnikowe (2FA)', - 'settings.mfa.description': 'Dodaje drugi krok, kiedy logujesz się e-mailem i hasłem. Użyj aplikacji uwierzytelniającej (Google Authenticator, Authy, itp.).', - 'settings.mfa.requiredByPolicy': 'Twój administrator wymaga uwierzytelniania dwuskładnikowego. Skonfiguruj aplikację uwierzytelniającą poniżej, zanim przejdziesz dalej.', - 'settings.mfa.backupTitle': 'Kody zapasowe', - 'settings.mfa.backupDescription': 'Użyj tych jednorazowych kodów zapasowych, jeżeli stracisz dostęp do swojej aplikacji uwierzytelniającej.', - 'settings.mfa.backupWarning': 'Zapisz te kody. Każdy z nich może być wykorzystany tylko raz.', - 'settings.mfa.backupCopy': 'Kopiuj kody', - 'settings.mfa.backupDownload': 'Pobierz TXT', - 'settings.mfa.backupPrint': 'Drukuj / PDF', - 'settings.mfa.backupCopied': 'Kody zapasowe zostały skopiowane', - 'settings.mfa.enabled': '2FA jest włączone dla Twojego konta.', - 'settings.mfa.disabled': '2FA jest wyłączone.', - 'settings.mfa.setup': 'Skonfiguruj aplikację uwierzytelniającą', - 'settings.mfa.scanQr': 'Zeskanuj ten kod QR za pomocą aplikacji lub wprowadź klucz ręcznie.', - 'settings.mfa.secretLabel': 'Tajny klucz (wprowadź ręcznie)', - 'settings.mfa.codePlaceholder': '6-cyfrowy kod', - 'settings.mfa.enable': 'Włącz 2FA', - 'settings.mfa.cancelSetup': 'Anuluj', - 'settings.mfa.disableTitle': 'Wyłącz 2FA', - 'settings.mfa.disableHint': 'Podaj hasło do konta i aktualny kod z aplikacji uwierzytelniającej.', - 'settings.mfa.disable': 'Wyłącz 2FA', - 'settings.mfa.toastEnabled': 'Uwierzytelnianie dwuskładnikowe zostało włączone', - 'settings.mfa.toastDisabled': 'Uwierzytelnianie dwuskładnikowe zostało wyłączone', - 'settings.mfa.demoBlocked': 'Niedostępne w trybie demonstracyjnym', - - // Login - 'login.error': 'Logowanie nie powiodło się. Sprawdź dane logowania.', - 'login.tagline': 'Twoje podróże.\nTwój plan.', - 'login.description': 'Planuj wspólnie podróże z interaktywnymi mapami, budżetami i synchronizacją w czasie rzeczywistym.', - 'login.features.maps': 'Interaktywne mapy', - 'login.features.mapsDesc': 'Google Places, trasy i grupowanie', - 'login.features.realtime': 'Synchronizacja w czasie rzeczywistym', - 'login.features.realtimeDesc': 'Planuj wspólnie przez WebSocket', - 'login.features.budget': 'Śledzenie budżetu', - 'login.features.budgetDesc': 'Kategorie, wykresy i koszty w przeliczeniu na osobę', - 'login.features.collab': 'Współpraca', - 'login.features.collabDesc': 'Wielu użytkowników i wspólne podróże', - 'login.features.packing': 'Listy pakowania', - 'login.features.packingDesc': 'Kategorie, postęp i sugestie', - 'login.features.bookings': 'Rezerwacje', - 'login.features.bookingsDesc': 'Loty, hotele, restauracje i więcej', - 'login.features.files': 'Dokumenty', - 'login.features.filesDesc': 'Przesyłaj i zarządzaj dokumentami', - 'login.features.routes': 'Inteligentne trasy', - 'login.features.routesDesc': 'Automatyczna optymalizacja i eksport do Google Maps', - 'login.selfHosted': 'Własny hosting \u00B7 Otwarty kod źródłowy \u00B7 Twoje dane pozostają Twoje', - 'login.title': 'Zaloguj się', - 'login.subtitle': 'Witaj ponownie', - 'login.signingIn': 'Logowanie...', - 'login.signIn': 'Zaloguj się', - 'login.createAdmin': 'Utwórz konto administratora', - 'login.createAdminHint': 'Skonfiguruj pierwsze konto administratora dla TREK.', - 'login.createAccount': 'Utwórz konto', - 'login.createAccountHint': 'Zarejestruj nowe konto.', - 'login.creating': 'Tworzenie...', - 'login.noAccount': "Nie masz konta?", - 'login.hasAccount': 'Masz już konto?', - 'login.register': 'Zarejestruj się', - 'login.emailPlaceholder': 'twoj@email.pl', - 'login.username': 'Nazwa użytkownika', - 'login.oidc.registrationDisabled': 'Rejestracja jest wyłączona. Skontaktuj się z administratorem.', - 'login.oidc.noEmail': 'Nie otrzymano e-maila od dostawcy.', - 'login.oidc.tokenFailed': 'Nie udało się uwierzytelnić.', - 'login.oidc.invalidState': 'Nieprawidłowa sesja. Spróbuj ponownie.', - 'login.demoFailed': 'Nie udało się zalogować do wersji demonstracyjnej', - 'login.oidcSignIn': 'Zaloguj się z {name}', - 'login.oidcOnly': 'Uwierzytelnianie hasłem jest wyłączone. Zaloguj się za pomocą swojego dostawcy SSO.', - 'login.oidcLoggedOut': 'Zostałeś wylogowany. Zaloguj się ponownie za pomocą swojego dostawcy SSO.', - 'login.demoHint': 'Wypróbuj demo — nie wymaga rejestracji', - 'login.mfaTitle': 'Uwierzytelnianie dwuskładnikowe', - 'login.mfaSubtitle': 'Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.', - 'login.mfaCodeLabel': 'Kod weryfikacyjny', - 'login.mfaCodeRequired': 'Wprowadź kod z aplikacji uwierzytelniającej.', - 'login.mfaHint': 'Otwórz Google Authenticator, Authy lub inną aplikację TOTP.', - 'login.mfaBack': '← Powrót do logowania', - 'login.mfaVerify': 'Weryfikuj', - 'login.invalidInviteLink': 'Nieprawidłowy lub wygasły link zaproszenia', - 'login.oidcFailed': 'Logowanie OIDC nie powiodło się', - 'login.usernameRequired': 'Nazwa użytkownika jest wymagana', - 'login.passwordMinLength': 'Hasło musi mieć co najmniej 8 znaków', - 'login.forgotPassword': 'Nie pamiętasz hasła?', - 'login.forgotPasswordTitle': 'Zresetuj hasło', - 'login.forgotPasswordBody': 'Wpisz adres e-mail użyty przy rejestracji. Jeśli konto istnieje, wyślemy link do resetu.', - 'login.forgotPasswordSubmit': 'Wyślij link', - 'login.forgotPasswordSentTitle': 'Sprawdź swoją pocztę', - 'login.forgotPasswordSentBody': 'Jeśli istnieje konto dla tego adresu, link jest już w drodze. Wygaśnie za 60 minut.', - 'login.forgotPasswordSmtpHintOff': 'Uwaga: administrator nie skonfigurował SMTP, więc link resetujący zostanie zapisany w konsoli serwera zamiast wysłania e-mailem.', - 'login.backToLogin': 'Wróć do logowania', - 'login.newPassword': 'Nowe hasło', - 'login.confirmPassword': 'Potwierdź nowe hasło', - 'login.passwordsDontMatch': 'Hasła nie są zgodne', - 'login.mfaCode': 'Kod 2FA', - 'login.resetPasswordTitle': 'Ustaw nowe hasło', - 'login.resetPasswordBody': 'Wybierz silne hasło, którego tu jeszcze nie używałeś. Minimum 8 znaków.', - 'login.resetPasswordMfaBody': 'Wpisz kod 2FA lub kod zapasowy, aby zakończyć reset.', - 'login.resetPasswordSubmit': 'Zresetuj hasło', - 'login.resetPasswordVerify': 'Zweryfikuj i zresetuj', - 'login.resetPasswordSuccessTitle': 'Hasło zaktualizowane', - 'login.resetPasswordSuccessBody': 'Możesz się teraz zalogować nowym hasłem.', - 'login.resetPasswordInvalidLink': 'Nieprawidłowy link', - 'login.resetPasswordInvalidLinkBody': 'Brakuje linku lub jest uszkodzony. Poproś o nowy, aby kontynuować.', - 'login.resetPasswordFailed': 'Reset nie powiódł się. Link mógł wygasnąć.', - - // Register - 'register.passwordMismatch': 'Hasła nie są identyczne', - 'register.passwordTooShort': 'Hasło musi mieć co najmniej 6 znaków', - 'register.failed': 'Rejestracja nie powiodła się', - 'register.getStarted': 'Rozpocznij', - 'register.subtitle': 'Utwórz konto i zacznij planować swoje wymarzone podróże.', - 'register.feature1': 'Nieograniczone plany podróży', - 'register.feature2': 'Interaktywna mapa', - 'register.feature3': 'Zarządzaj miejscami i kategoriami', - 'register.feature4': 'Śledź rezerwacje', - 'register.feature5': 'Twórz listy pakowania', - 'register.feature6': 'Przechowuj zdjęcia i pliki', - 'register.createAccount': 'Utwórz konto', - 'register.startPlanning': 'Zacznij planować podróż', - 'register.minChars': 'Min. 6 znaków', - 'register.confirmPassword': 'Potwierdź hasło', - 'register.repeatPassword': 'Powtórz hasło', - 'register.registering': 'Rejestrowanie...', - 'register.register': 'Zarejestruj się', - 'register.hasAccount': 'Masz już konto?', - 'register.signIn': 'Zaloguj się', - - // Admin - 'admin.title': 'Administracja', - 'admin.subtitle': 'Zarządzanie użytkownikami i ustawienia systemowe', - 'admin.tabs.users': 'Użytkownicy', - 'admin.tabs.categories': 'Kategorie', - 'admin.tabs.backup': 'Backupy', - 'admin.tabs.notifications': 'Powiadomienia', - 'admin.tabs.audit': 'Audit', - 'admin.stats.users': 'Użytkownicy', - 'admin.stats.trips': 'Podróże', - 'admin.stats.places': 'Miejsca', - 'admin.stats.photos': 'Zdjęcia', - 'admin.stats.files': 'Pliki', - 'admin.table.user': 'Użytkownik', - 'admin.table.email': 'E-mail', - 'admin.table.role': 'Rola', - 'admin.table.created': 'Utworzono', - 'admin.table.lastLogin': 'Ostatnie logowanie', - 'admin.table.actions': 'Akcje', - 'admin.you': '(Ty)', - 'admin.editUser': 'Edytuj użytkownika', - 'admin.newPassword': 'Nowe hasło', - 'admin.newPasswordHint': 'Pozostaw puste, aby zachować obecne hasło', - 'admin.deleteUser': 'Usunąć użytkownika "{name}"? Wszystkie jego podróże zostaną trwale usunięte.', - 'admin.deleteUserTitle': 'Usuń użytkownika', - 'admin.newPasswordPlaceholder': 'Podaj nowe hasło...', - 'admin.toast.loadError': 'Nie udało się załadować danych administratora', - 'admin.toast.userUpdated': 'Użytkownik został zaktualizowany', - 'admin.toast.updateError': 'Nie udało się zaktualizować użytkownika', - 'admin.toast.userDeleted': 'Użytkownik został usunięty', - 'admin.toast.deleteError': 'Nie udało się usunąć użytkownika', - 'admin.toast.cannotDeleteSelf': 'Nie można usunąć własnego konta', - 'admin.toast.userCreated': 'Użytkownik został utworzony', - 'admin.toast.createError': 'Nie udało się utworzyć użytkownika', - 'admin.toast.fieldsRequired': 'Nazwa użytkownika, e-mail i hasło są wymagane', - 'admin.createUser': 'Utwórz użytkownika', - 'admin.invite.title': 'Linki zaproszeń', - 'admin.invite.subtitle': 'Twórz jednorazowe linki do rejestracji', - 'admin.invite.create': 'Utwórz link', - 'admin.invite.createAndCopy': 'Utwórz i skopiuj', - 'admin.invite.empty': 'Nie utworzono jeszcze żadnych linków zaproszeń', - 'admin.invite.maxUses': 'Maksymalna liczba użyć', - 'admin.invite.expiry': 'Wygasa po', - 'admin.invite.uses': 'użycia', - 'admin.invite.expiresAt': 'wygasa', - 'admin.invite.createdBy': 'utworzone przez', - 'admin.invite.active': 'Aktywny', - 'admin.invite.expired': 'Wygasł', - 'admin.invite.usedUp': 'Wykorzystany', - 'admin.invite.copied': 'Link zaproszenia został skopiowany do schowka', - 'admin.invite.copyLink': 'Skopiuj link', - 'admin.invite.deleted': 'Link zaproszenia został usunięty', - 'admin.invite.createError': 'Nie udało się utworzyć linku zaproszenia', - 'admin.invite.deleteError': 'Nie udało się usunąć linku zaproszenia', - 'admin.tabs.settings': 'Ustawienia', - 'admin.allowRegistration': 'Zezwól na rejestrację', - 'admin.allowRegistrationHint': 'Nowi użytkownicy mogą się rejestrować samodzielnie', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Wymagaj uwierzytelniania dwuskładnikowego (2FA)', - 'admin.requireMfaHint': 'Użytkownicy bez 2FA muszą ukończyć konfigurację w Ustawieniach zanim zaczną korzystać z aplikacji.', - 'admin.apiKeys': 'Klucze API', - 'admin.apiKeysHint': 'Opcjonalne. Umożliwiają pobieranie większej ilości danych o miejscach, takich jak zdjęcia i pogoda.', - 'admin.mapsKey': 'Klucz Google Maps API', - 'admin.mapsKeyHint': 'Wymagany do wyszukiwania miejsc. Uzyskaj go na console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Bez klucza API, OpenStreetMap jest wykorzystywany do wyszukiwania miejsc. Z kluczem API Google, zdjęcia, oceny i godziny otwarcia również mogą być pobierane. Uzyskaj go na console.cloud.google.com.', - 'admin.recommended': 'Polecane', - 'admin.weatherKey': 'Klucz OpenWeatherMap API', - 'admin.weatherKeyHint': 'Do danych pogodowych. Uzyskaj go bezpłatnie na openweathermap.org', - 'admin.validateKey': 'Testuj', - 'admin.keyValid': 'Połączono', - 'admin.keyInvalid': 'Niepoprawny', - 'admin.keySaved': 'Klucze API zostały zapisane', - 'admin.oidcTitle': 'Logowanie jednokrotne (OIDC)', - 'admin.oidcSubtitle': 'Zezwól na logowanie za pomocą zewnętrznych dostawców, takich jak Google, Apple, Authentik lub Keycloak.', - 'admin.oidcDisplayName': 'Wyświetlana nazwa', - 'admin.oidcIssuer': 'URL wystawcy', - 'admin.oidcIssuerHint': 'Adres URL wystawcy OpenID Connect dostawcy, np. https://accounts.google.com', - 'admin.oidcSaved': 'Konfiguracja OIDC została zapisana', - 'admin.oidcOnlyMode': 'Wyłącz uwierzytelnianie hasłem', - 'admin.oidcOnlyModeHint': 'Po włączeniu dozwolone jest tylko logowanie jednokrotne. Logowanie i rejestracja za pomocą hasła są zablokowane.', - - // File Types - 'admin.fileTypes': 'Dozwolone typy plików', - 'admin.fileTypesHint': 'Ustaw, które typy plików mogą być przesyłane przez użytkowników.', - 'admin.fileTypesFormat': 'Rozszerzenia oddzielone przecinkami (np. jpg,png,pdf,doc). Użyj * aby zezwolić na wszystkie typy.', - 'admin.fileTypesSaved': 'Ustawienia typów plików zostały zapisane', - - // Packing Templates & Bag Tracking - 'admin.placesPhotos.title': 'Zdjęcia miejsc', - 'admin.placesPhotos.subtitle': 'Pobiera zdjęcia z Google Places API. Wyłącz, aby zaoszczędzić limit API. Zdjęcia z Wikimedia nie są objęte.', - 'admin.placesAutocomplete.title': 'Autouzupełnianie miejsc', - 'admin.placesAutocomplete.subtitle': 'Używa Google Places API do sugestii wyszukiwania. Wyłącz, aby zaoszczędzić limit API.', - 'admin.placesDetails.title': 'Szczegóły miejsca', - 'admin.placesDetails.subtitle': 'Pobiera szczegółowe informacje o miejscu (godziny, ocena, strona) z Google Places API. Wyłącz, aby zaoszczędzić limit API.', - 'admin.bagTracking.title': 'Kontrola bagażu', - 'admin.bagTracking.subtitle': 'Włącz wagę i przypisywanie do toreb dla przedmiotów do pakowania', - 'admin.collab.chat.title': 'Czat', - 'admin.collab.chat.subtitle': 'Wiadomości w czasie rzeczywistym', - 'admin.collab.notes.title': 'Notatki', - 'admin.collab.notes.subtitle': 'Wspólne notatki i dokumenty', - 'admin.collab.polls.title': 'Ankiety', - 'admin.collab.polls.subtitle': 'Ankiety grupowe i głosowania', - 'admin.collab.whatsnext.title': 'Co dalej', - 'admin.collab.whatsnext.subtitle': 'Sugestie aktywności i następne kroki', - 'admin.tabs.config': 'Personalizacja', - 'admin.tabs.defaults': 'Domyślne ustawienia', - 'admin.defaultSettings.title': 'Domyślne ustawienia użytkownika', - 'admin.defaultSettings.description': 'Ustaw domyślne wartości dla całej instancji. Użytkownicy, którzy nie zmienili ustawienia, zobaczą te wartości. Ich własne zmiany zawsze mają pierwszeństwo.', - 'admin.defaultSettings.saved': 'Domyślne zapisane', - 'admin.defaultSettings.reset': 'Przywróć wbudowaną wartość domyślną', - 'admin.defaultSettings.resetToBuiltIn': 'przywróć', - 'admin.tabs.templates': 'Szablony pakowania', - 'admin.packingTemplates.title': 'Szablony pakowania', - 'admin.packingTemplates.subtitle': 'Twórz szablony list pakowania do wielokrotnego użycia dla swoich podróży', - 'admin.packingTemplates.create': 'Nowy szablon', - 'admin.packingTemplates.namePlaceholder': 'Nazwa szablonu (np. Wakacje na plaży)', - 'admin.packingTemplates.empty': 'Nie utworzono jeszcze żadnych szablonów', - 'admin.packingTemplates.items': 'przedmiotów', - 'admin.packingTemplates.categories': 'kategorie', - 'admin.packingTemplates.itemName': 'Nazwa przedmiotu', - 'admin.packingTemplates.itemCategory': 'Kategoria', - 'admin.packingTemplates.categoryName': 'Nazwa kategorii (np. Ubrania)', - 'admin.packingTemplates.addCategory': 'Dodaj kategorię', - 'admin.packingTemplates.created': 'Szablon został utworzony', - 'admin.packingTemplates.deleted': 'Szablon został usunięty', - 'admin.packingTemplates.loadError': 'Nie udało się załadować szablonów', - 'admin.packingTemplates.createError': 'Nie udało się utworzyć szablonu', - 'admin.packingTemplates.deleteError': 'Nie udało się usunąć szablonu', - 'admin.packingTemplates.saveError': 'Nie udało się zapisać szablonu', - - // Addons - 'admin.tabs.addons': 'Dodatki', - 'admin.addons.title': 'Dodatki', - 'admin.addons.subtitle': 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w TREK.', - 'admin.addons.catalog.packing.name': 'Listy', - 'admin.addons.catalog.packing.description': 'Listy pakowania i zadania do wykonania dla Twoich podróży', - 'admin.addons.catalog.budget.name': 'Budżet', - 'admin.addons.catalog.budget.description': 'Śledź wydatki i planuj budżet podróży', - 'admin.addons.catalog.documents.name': 'Dokumenty', - 'admin.addons.catalog.documents.description': 'Przechowuj i zarządzaj dokumentami podróżnymi', - 'admin.addons.catalog.vacay.name': 'Urlopy', - 'admin.addons.catalog.vacay.description': 'Osobisty planer urlopu z widokiem kalendarza', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Mapa świata z odwiedzonymi krajami i statystykami podróży', - 'admin.addons.catalog.collab.name': 'Współpraca', - 'admin.addons.catalog.collab.description': 'Notatki w czasie rzeczywistym, ankiety i czat do planowania podróży', - 'admin.addons.catalog.memories.name': 'Zdjęcia (Immich)', - 'admin.addons.catalog.memories.description': 'Udostępniaj zdjęcia z podróży za pośrednictwem swojej instancji Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol dla integracji asystenta AI', - 'admin.addons.subtitleBefore': 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w ', - 'admin.addons.subtitleAfter': '.', - 'admin.addons.enabled': 'Włączone', - 'admin.addons.disabled': 'Wyłączone', - 'admin.addons.type.trip': 'Podróż', - 'admin.addons.type.global': 'Globalne', - 'admin.addons.type.integration': 'Integracja', - 'admin.addons.tripHint': 'Dostępne jako zakładka w każdej podróży', - 'admin.addons.globalHint': 'Dostępne jako osobna sekcja w menu głównym', - 'admin.addons.integrationHint': 'Usługi backendowe i integracje API bez dedykowanej strony', - 'admin.addons.toast.updated': 'Dodatek został zaktualizowany', - 'admin.addons.toast.error': 'Nie udało się zaktualizować dodatku', - 'admin.addons.noAddons': 'Brak dostępnych dodatków', - // Weather info - 'admin.weather.title': 'Dane pogodowe', - 'admin.weather.badge': 'Od 24 marca 2026', - 'admin.weather.description': 'TREK korzysta z Open-Meteo jako źródła danych pogodowych. Open-Meteo to darmowy, otwartoźródłowy serwis pogodowy — klucz API nie jest wymagany.', - 'admin.weather.forecast': '16-dniowa prognoza', - 'admin.weather.forecastDesc': 'Wcześniej 5 dni (OpenWeatherMap)', - 'admin.weather.climate': 'Historyczne dane klimatyczne', - 'admin.weather.climateDesc': 'Średnie z ostatnich 85 lat dla dni poza 16-dniową prognozą', - 'admin.weather.requests': '10,000 zapytań / dzień', - 'admin.weather.requestsDesc': 'Bezpłatnie, bez klucza API', - 'admin.weather.locationHint': 'Pogoda jest określana na podstawie pierwszego miejsca z przypisanymi współrzędnymi w danym dniu. Jeśli do dnia nie przypisano żadnego miejsca, jako punkt odniesienia używane jest dowolne miejsce z listy.', - - // GitHub - 'admin.tabs.mcpTokens': 'Dostęp MCP', - 'admin.mcpTokens.title': 'Dostęp MCP', - 'admin.mcpTokens.subtitle': 'Zarządzaj sesjami OAuth i tokenami API dla wszystkich użytkowników', - 'admin.mcpTokens.sectionTitle': 'Tokeny API', - 'admin.mcpTokens.owner': 'Właściciel', - 'admin.mcpTokens.tokenName': 'Nazwa tokenu', - 'admin.mcpTokens.created': 'Utworzono', - 'admin.mcpTokens.lastUsed': 'Ostatnio użyto', - 'admin.mcpTokens.never': 'Nigdy', - 'admin.mcpTokens.empty': 'Nie utworzono jeszcze żadnych tokenów MCP', - 'admin.mcpTokens.deleteTitle': 'Usuń token', - 'admin.mcpTokens.deleteMessage': 'Spowoduje to natychmiastowe unieważnienie tokenu. Użytkownik straci dostęp MCP przez ten token.', - 'admin.mcpTokens.deleteSuccess': 'Token został usunięty', - 'admin.mcpTokens.deleteError': 'Nie udało się usunąć tokenu', - 'admin.mcpTokens.loadError': 'Nie udało się załadować tokenów', - 'admin.oauthSessions.sectionTitle': 'Sesje OAuth', - 'admin.oauthSessions.clientName': 'Klient', - 'admin.oauthSessions.owner': 'Właściciel', - 'admin.oauthSessions.scopes': 'Uprawnienia', - 'admin.oauthSessions.created': 'Utworzono', - 'admin.oauthSessions.empty': 'Brak aktywnych sesji OAuth', - 'admin.oauthSessions.revokeTitle': 'Unieważnij sesję', - 'admin.oauthSessions.revokeMessage': 'Ta sesja OAuth zostanie natychmiast unieważniona. Klient straci dostęp do MCP.', - 'admin.oauthSessions.revokeSuccess': 'Sesja unieważniona', - 'admin.oauthSessions.revokeError': 'Nie udało się unieważnić sesji', - 'admin.oauthSessions.loadError': 'Nie udało się załadować sesji OAuth', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Zdarzenia związane z bezpieczeństwem i administracją (kopie zapasowe, użytkownicy, MFA, ustawienia).', - 'admin.audit.empty': 'Brak zapisów w historii aktywności.', - 'admin.audit.refresh': 'Odśwież', - 'admin.audit.loadMore': 'Załaduj więcej', - 'admin.audit.showing': '{count} załadowanych · {total} łącznie', - 'admin.audit.col.time': 'Czas', - 'admin.audit.col.user': 'Użytkownik', - 'admin.audit.col.action': 'Akcja', - 'admin.audit.col.resource': 'Zasób', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Szczegóły', - 'admin.github.title': 'Historia wydań', - 'admin.github.subtitle': 'Najnowsze aktualizacje z {repo}', - 'admin.github.latest': 'Najnowsze', - 'admin.github.prerelease': 'Wersja testowa', - 'admin.github.showDetails': 'Pokaż szczegóły', - 'admin.github.hideDetails': 'Ukryj szczegóły', - 'admin.github.loadMore': 'Załaduj więcej', - 'admin.github.loading': 'Ładowanie...', - 'admin.github.error': 'Nie udało się załadować wydań', - 'admin.github.by': 'przez', - 'admin.github.support': 'Pomóż mi rozwijać TREK', - - 'admin.update.available': 'Dostępna aktualizacja', - 'admin.update.text': 'Dostępna jest wersja TREK {version}. Używasz {current}.', - 'admin.update.button': 'Zobacz na GitHubie', - 'admin.update.install': 'Zainstaluj aktualizację', - 'admin.update.confirmTitle': 'Zainstalować aktualizację?', - 'admin.update.confirmText': 'TREK zostanie zaktualizowany z {current} do {version}. Serwer zostanie automatycznie zrestartowany po zakończeniu.', - 'admin.update.dataInfo': 'Wszystkie twoje dane (podróże, użytkownicy, klucze API, przesłane pliki, urlopy, Atlas, budżety) zostaną zachowane.', - 'admin.update.warning': 'Aplikacja będzie niedostępna przez krótki czas podczas restartu.', - 'admin.update.confirm': 'Zaktualizuj', - 'admin.update.installing': 'Aktualizowanie...', - 'admin.update.success': 'Aktualizacja zakończona! Serwer restartuje się...', - 'admin.update.failed': 'Aktualizacja nie powiodła się', - 'admin.update.backupHint': 'Zalecamy utworzenie kopii zapasowej przed aktualizacją.', - 'admin.update.backupLink': 'Zrób kopię zapasową', - 'admin.update.howTo': 'Jak zaktualizować', - 'admin.update.dockerText': 'Twoja instancja TREK działa w Dockerze. Aby zaktualizować do {version}, uruchom następujące polecenia na swoim serwerze:', - 'admin.update.reloadHint': 'Proszę odświeżyć stronę za kilka sekund.', - - // Vacay addon - 'vacay.subtitle': 'Planuj i zarządzaj dniami urlopu', - 'vacay.settings': 'Ustawienia', - 'vacay.year': 'Rok', - 'vacay.addYear': 'Dodaj następny rok', - 'vacay.addPrevYear': 'Dodaj poprzedni rok', - 'vacay.removeYear': 'Usuń rok', - 'vacay.removeYearConfirm': 'Usunąć {year}?', - 'vacay.removeYearHint': 'Wszystkie wpisy dotyczące urlopów oraz dni wolnych w tym roku zostaną trwale usunięte.', - 'vacay.remove': 'Usuń', - 'vacay.persons': 'Osoby', - 'vacay.noPersons': 'Nie dodano osób', - 'vacay.addPerson': 'Dodaj osobę', - 'vacay.editPerson': 'Edytuj osobę', - 'vacay.removePerson': 'Usuń osobę', - 'vacay.removePersonConfirm': 'Usunąć {name}?', - 'vacay.removePersonHint': 'Wszystkie wpisy dotyczące urlopów dla tej osoby zostaną trwale usunięte.', - 'vacay.personName': 'Imię', - 'vacay.personNamePlaceholder': 'Podaj imię', - 'vacay.color': 'Kolor', - 'vacay.add': 'Dodaj', - 'vacay.legend': 'Legenda', - 'vacay.publicHoliday': 'Święto państwowe', - 'vacay.companyHoliday': 'Urlop firmowy', - 'vacay.weekend': 'Weekendowy', - 'vacay.modeVacation': 'Urlop', - 'vacay.modeCompany': 'Urlop firmowy', - 'vacay.entitlement': 'Wymiar', - 'vacay.entitlementDays': 'Dni', - 'vacay.used': 'Wykorzystane', - 'vacay.remaining': 'Pozostało', - 'vacay.carriedOver': 'z {year}', - 'vacay.blockWeekends': 'Blokuj weekendy', - 'vacay.blockWeekendsHint': 'Zapobiegaj wpisywaniu urlopów w weekendy', - 'vacay.weekendDays': 'Dni weekendowe', - 'vacay.mon': 'Pon', - 'vacay.tue': 'Wt', - 'vacay.wed': 'Śr', - 'vacay.thu': 'Czw', - 'vacay.fri': 'Pt', - 'vacay.sat': 'Sob', - 'vacay.sun': 'Nd', - 'vacay.publicHolidays': 'Święta państwowe', - 'vacay.publicHolidaysHint': 'Oznacz święta państwowe w kalendarzu', - 'vacay.selectCountry': 'Wybierz kraj', - 'vacay.selectRegion': 'Wybierz region (opcjonalnie)', - 'vacay.addCalendar': 'Dodaj kalendarz', - 'vacay.calendarLabel': 'Etykieta (opcjonalnie)', - 'vacay.calendarColor': 'Kolor', - 'vacay.noCalendars': 'Nie dodano jeszcze kalendarzy świąt', - 'vacay.companyHolidays': 'Urlopy firmowe', - 'vacay.companyHolidaysHint': 'Pozwala oznaczać dni wolne od pracy w kalendarzu', - 'vacay.companyHolidaysNoDeduct': 'Urlopy firmowe nie są odejmowane od puli dni urlopowych.', - 'vacay.weekStart': 'Tydzień zaczyna się w', - 'vacay.weekStartHint': 'Wybierz czy tydzień zaczyna się w poniedziałek czy niedzielę', - 'vacay.carryOver': 'Przeniesienie na kolejny rok', - 'vacay.carryOverHint': 'Automatycznie przenosi pozostałe dni urlopowe na kolejny rok', - 'vacay.sharing': 'Udostępnianie', - 'vacay.sharingHint': 'Udostępnij swój plan urlopów innym użytkownikom TREK', - 'vacay.owner': 'Właściciel', - 'vacay.shareEmailPlaceholder': 'E-mail użytkownika TREK', - 'vacay.shareSuccess': 'Plan został udostępniony pomyślnie', - 'vacay.shareError': 'Nie udało się udostępnić planu', - 'vacay.dissolve': 'Rozłącz kalendarze', - 'vacay.dissolveHint': 'Rozłącz kalendarze ponownie. Twoje wpisy zostaną zachowane.', - 'vacay.dissolveAction': 'Rozłącz', - 'vacay.dissolved': 'Kalendarz został rozłączony', - 'vacay.fusedWith': 'Połączono z', - 'vacay.you': 'ty', - 'vacay.noData': 'Brak danych', - 'vacay.changeColor': 'Zmień kolor', - 'vacay.inviteUser': 'Zaproś użytkownika', - 'vacay.inviteHint': 'Zaproś innego użytkownika TREK do wspólnego kalendarza urlopów.', - 'vacay.selectUser': 'Wybierz użytkownika', - 'vacay.sendInvite': 'Wyślij zaproszenie', - 'vacay.inviteSent': 'Zaproszenie zostało wysłane', - 'vacay.inviteError': 'Nie udało się wysłać zaproszenia', - 'vacay.pending': 'oczekujące', - 'vacay.noUsersAvailable': 'Brak dostępnych użytkowników', - 'vacay.accept': 'Akceptuj', - 'vacay.decline': 'Odrzuć', - 'vacay.acceptFusion': 'Akceptuj i połącz', - 'vacay.inviteTitle': 'Zaproszenie do połączenia', - 'vacay.inviteWantsToFuse': 'chce udostępnić kalendarz urlopów.', - 'vacay.fuseInfo1': 'Obie strony będą widzieć wszystkie wpisy urlopowe w jednym wspólnym kalendarzu.', - 'vacay.fuseInfo2': 'Obie strony mogą tworzyć i edytować wpisy dla drugiej strony.', - 'vacay.fuseInfo3': 'Obie strony mogą usuwać wpisy i zmieniać pulę dni urlopowych.', - 'vacay.fuseInfo4': 'Ustawienia, takie jak święta państwowe i urlopy firmowe, są współdzielone.', - 'vacay.fuseInfo5': 'Połączenie może zostać rozwiązane w dowolnym momencie przez każdą ze stron. Twoje wpisy zostaną zachowane.', - 'nav.myTrips': 'Moje podróże', - - // Atlas addon - 'atlas.subtitle': 'Twój ślad podróżniczy po świecie', - 'atlas.countries': 'Kraje', - 'atlas.trips': 'Podróże', - 'atlas.places': 'Miejsca', - 'atlas.unmark': 'Usuń', - 'atlas.confirmMark': 'Oznaczyć ten kraj jako odwiedzony?', - 'atlas.confirmUnmark': 'Usunąć ten kraj z listy odwiedzonych?', - 'atlas.confirmUnmarkRegion': 'Usunąć ten region z listy odwiedzonych?', - 'atlas.markVisited': 'Oznacz jako odwiedzony', - 'atlas.markVisitedHint': 'Dodaj ten kraj do listy odwiedzonych', - 'atlas.markRegionVisitedHint': 'Dodaj ten region do listy odwiedzonych', - 'atlas.addToBucket': 'Dodaj do listy marzeń', - 'atlas.addPoi': 'Dodaj miejsce', - 'atlas.bucketNamePlaceholder': 'Nazwa (kraj, miasto, miejsce...)', - 'atlas.month': 'Miesiąc', - 'atlas.year': 'Rok', - 'atlas.addToBucketHint': 'Zapisz jako miejsce, które chcesz odwiedzić', - 'atlas.bucketWhen': 'Kiedy planujesz je odwiedzić?', - 'atlas.statsTab': 'Statystyki', - 'atlas.bucketTab': 'Lista marzeń', - 'atlas.addBucket': 'Dodaj do listy marzeń', - 'atlas.bucketNotesPlaceholder': 'Notatki (opcjonalnie)', - 'atlas.bucketEmpty': 'Twoja lista marzeń jest pusta', - 'atlas.bucketEmptyHint': 'Dodaj miejsca, które chcesz odwiedzić', - 'atlas.days': 'Dni', - 'atlas.visitedCountries': 'Odwiedzone kraje', - 'atlas.cities': 'Miasta', - 'atlas.noData': 'Brak danych o podróżach', - 'atlas.noDataHint': 'Utwórz podróż i dodaj miejsca, aby zobaczyć swoją mapę świata', - 'atlas.lastTrip': 'Ostatnia podróż', - 'atlas.nextTrip': 'Następna podróż', - 'atlas.daysLeft': 'dni do wyjazdu', - 'atlas.streak': 'Seria', - 'atlas.years': 'lata', - 'atlas.yearInRow': 'rok z rzędu', - 'atlas.yearsInRow': 'lat z rzędu', - 'atlas.tripIn': 'podróż w', - 'atlas.tripsIn': 'podróży w', - 'atlas.since': 'od', - 'atlas.europe': 'Europa', - 'atlas.asia': 'Azja', - 'atlas.northAmerica': 'Ameryka Pn.', - 'atlas.southAmerica': 'Ameryka Pd.', - 'atlas.africa': 'Afryka', - 'atlas.oceania': 'Oceania', - 'atlas.other': 'Inne', - 'atlas.firstVisit': 'Pierwsza podróż', - 'atlas.lastVisitLabel': 'Ostatnia podróż', - 'atlas.tripSingular': 'Podróż', - 'atlas.tripPlural': 'Podróże', - 'atlas.placeVisited': 'Odwiedzone miejsce', - 'atlas.placesVisited': 'Odwiedzone miejsca', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transport', - 'trip.tabs.reservations': 'Rezerwacje', - 'trip.tabs.reservationsShort': 'Rezerwacje', - 'trip.tabs.packing': 'Lista pakowania', - 'trip.tabs.packingShort': 'Pakowanie', - 'trip.tabs.lists': 'Listy', - 'trip.tabs.listsShort': 'Listy', - 'trip.tabs.budget': 'Budżet', - 'trip.tabs.files': 'Pliki', - 'trip.loading': 'Ładowanie podróży...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Miejsca', - 'trip.toast.placeUpdated': 'Miejsce zostało zaktualizowane', - 'trip.toast.placeAdded': 'Miejsce zostało dodane', - 'trip.toast.placeDeleted': 'Miejsce zostało usunięte', - 'trip.toast.selectDay': 'Proszę najpierw wybrać dzień', - 'trip.toast.assignedToDay': 'Miejsce przypisane do dnia', - 'trip.toast.reorderError': 'Nie udało się zmienić kolejności', - 'trip.toast.reservationUpdated': 'Rezerwacja została zaktualizowana', - 'trip.toast.reservationAdded': 'Rezerwacja została dodana', - 'trip.toast.deleted': 'Usunięto', - 'trip.confirm.deletePlace': 'Czy na pewno chcesz usunąć to miejsce?', - 'trip.confirm.deletePlaces': 'Usunąć {count} miejsc?', - 'trip.toast.placesDeleted': '{count} miejsc usunięto', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'Brak miejsc zaplanowanych na ten dzień', - 'dayplan.cannotReorderTransport': 'Nie można zmieniać kolejności dla rezerwacji z określoną godziną', - 'dayplan.confirmRemoveTimeTitle': 'Usunąć godzinę?', - 'dayplan.confirmRemoveTimeBody': 'To miejsce ma określoną godzinę ({time}). Przeniesienie go usunie godzinę i umożliwi swobodne sortowanie.', - 'dayplan.confirmRemoveTimeAction': 'Usuń godzinę i przenieś', - 'dayplan.cannotDropOnTimed': 'Nie można umieszczać elementów pomiędzy wpisami z określoną godziną', - 'dayplan.cannotBreakChronology': 'Spowodowałoby to naruszenie chronologicznej kolejności elementów i rezerwacji z określoną godziną', - 'dayplan.addNote': 'Dodaj notatkę', - 'dayplan.editNote': 'Edytuj notatkę', - 'dayplan.noteAdd': 'Dodaj notatkę', - 'dayplan.noteEdit': 'Edytuj notatkę', - 'dayplan.noteTitle': 'Notatka', - 'dayplan.noteSubtitle': 'Notatka dnia', - 'dayplan.totalCost': 'Łączny koszt', - 'dayplan.days': 'Dni', - 'dayplan.dayN': 'Dzień {n}', - 'dayplan.calculating': 'Obliczanie...', - 'dayplan.route': 'Trasa', - 'dayplan.optimize': 'Optymalizuj', - 'dayplan.optimized': 'Trasa została zoptymalizowana', - 'dayplan.routeError': 'Nie udało się obliczyć trasy', - 'dayplan.toast.needTwoPlaces': 'Potrzeba co najmniej dwóch miejsc, aby zoptymalizować trasę', - 'dayplan.toast.routeOptimized': 'Trasa została zoptymalizowana', - 'dayplan.toast.noGeoPlaces': 'Nie znaleziono miejsc ze współrzędnymi do obliczenia trasy', - 'dayplan.confirmed': 'Potwierdzono', - 'dayplan.pendingRes': 'Oczekujące', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Eksportuj plan dnia jako PDF', - 'dayplan.pdfError': 'Nie udało się wyeksportować pliku PDF', - - // Places Sidebar - 'places.addPlace': 'Dodaj miejsce/atrakcję', - 'places.importFile': 'Importuj plik', - 'places.sidebarDrop': 'Upuść, aby zaimportować', - 'places.importFileHint': 'Importuj pliki .gpx, .kml lub .kmz z narzędzi takich jak Google My Maps, Google Earth lub tracker GPS.', - 'places.importFileDropHere': 'Kliknij, aby wybrać plik lub przeciągnij i upuść tutaj', - 'places.importFileDropActive': 'Upuść plik, aby go wybrać', - 'places.importFileUnsupported': 'Nieobsługiwany typ pliku. Użyj .gpx, .kml lub .kmz.', - 'places.importFileTooLarge': 'Plik jest za duży. Maksymalny rozmiar przesyłania to {maxMb} MB.', - 'places.importFileError': 'Import nie powiódł się', - 'places.importAllSkipped': 'Wszystkie miejsca były już w podróży.', - 'places.gpxImported': '{count} miejsc zaimportowanych z GPX', - 'places.gpxImportTypes': 'Co chcesz zaimportować?', - 'places.gpxImportWaypoints': 'Punkty trasy', - 'places.gpxImportRoutes': 'Trasy', - 'places.gpxImportTracks': 'Trasy GPS (ze śladem)', - 'places.gpxImportNoneSelected': 'Wybierz co najmniej jeden typ do importu.', - 'places.kmlImportTypes': 'Co chcesz zaimportować?', - 'places.kmlImportPoints': 'Punkty (Placemarks)', - 'places.kmlImportPaths': 'Ścieżki (LineStrings)', - 'places.kmlImportNoneSelected': 'Wybierz co najmniej jeden typ.', - 'places.selectionCount': '{count} zaznaczono', - 'places.deleteSelected': 'Usuń wybrane', - 'places.kmlKmzImported': 'Zaimportowano {count} miejsc z KMZ/KML', - 'places.urlResolved': 'Miejsce zaimportowane z URL', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Zaimportowano: {created} • Pominięto: {skipped}', - 'places.importGoogleList': 'Lista Google', - 'places.assignToDay': 'Do którego dnia dodać?', - 'places.all': 'Wszystkie', - 'places.unplanned': 'Niezaplanowane', - 'places.filterTracks': 'Trasy', - 'places.search': 'Szukaj miejsc...', - 'places.allCategories': 'Wszystkie kategorie', - 'places.categoriesSelected': 'kategorii', - 'places.clearFilter': 'Wyczyść filtr', - 'places.count': '{count} miejsc', - 'places.countSingular': '1 miejsce', - 'places.allPlanned': 'Wszystkie miejsca są zaplanowane', - 'places.noneFound': 'Nie znaleziono miejsc', - 'places.editPlace': 'Edytuj miejsce', - 'places.formName': 'Nazwa', - 'places.formNamePlaceholder': 'np. Wieża Eiffla', - 'places.formDescription': 'Opis', - 'places.formDescriptionPlaceholder': 'Krótki opis...', - 'places.formAddress': 'Adres', - 'places.formAddressPlaceholder': 'Ulica, miasto, kraj', - 'places.formLat': 'Szerokość (np. 48.8566)', - 'places.formLng': 'Długość (np. 2.3522)', - 'places.formCategory': 'Kategoria', - 'places.noCategory': 'Brak kategorii', - 'places.categoryNamePlaceholder': 'Nazwa kategorii', - 'places.formTime': 'Godzina', - 'places.startTime': 'Początek', - 'places.endTime': 'Koniec', - 'places.endTimeBeforeStart': 'Godzina zakończenia jest przed godziną rozpoczęcia', - 'places.timeCollision': 'Nakładanie się godzin z:', - 'places.formWebsite': 'Strona internetowa', - 'places.formNotes': 'Notatki', - 'places.formNotesPlaceholder': 'Osobiste notatki...', - 'places.formReservation': 'Rezerwacja', - 'places.reservationNotesPlaceholder': 'Notatki z rezerwacji, numer potwierdzenia...', - 'places.mapsSearchPlaceholder': 'Szukaj miejsc...', - 'places.mapsSearchError': 'Nie udało się wyszukać miejsca.', - 'places.loadingDetails': 'Ładowanie szczegółów miejsca…', - 'places.osmHint': 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', - 'places.osmActive': 'Szukaj przez OpenStreetMap (brak zdjęć, ocen czy godzin otwarcia). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', - 'places.categoryCreateError': 'Nie udało się utworzyć kategorii', - 'places.nameRequired': 'Proszę podać nazwę', - 'places.saveError': 'Nie udało się zapisać', - // Place Inspector - 'inspector.opened': 'Otwarte', - 'inspector.closed': 'Zamknięte', - 'inspector.openingHours': 'Godziny otwarcia', - 'inspector.showHours': 'Pokaż godziny otwarcia', - 'inspector.files': 'Pliki', - 'inspector.filesCount': '{count} plików', - 'inspector.removeFromDay': 'Usuń z dnia', - 'inspector.remove': 'Usuń', - 'inspector.addToDay': 'Dodaj do dnia', - 'inspector.confirmedRes': 'Potwierdzona rezerwacja', - 'inspector.pendingRes': 'Oczekująca rezerwacja', - 'inspector.google': 'Otwórz w Mapach Google', - 'inspector.website': 'Otwórz stronę internetową', - 'inspector.addRes': 'Rezerwacja', - 'inspector.editRes': 'Edytuj rezerwację', - 'inspector.participants': 'Uczestnicy', - - // Reservations - 'reservations.title': 'Rezerwacje', - 'reservations.empty': 'Brak rezerwacji', - 'reservations.emptyHint': 'Dodaj rezerwacje lotów, hoteli i innych', - 'reservations.add': 'Dodaj rezerwację', - 'reservations.addManual': 'Rezerwacja ręczna', - 'reservations.placeHint': 'Wskazówka: Rezerwacje najlepiej tworzyć bezpośrednio z miejsca, aby powiązać je z planem dnia.', - 'reservations.confirmed': 'Potwierdzona', - 'reservations.pending': 'Oczekująca', - 'reservations.summary': '{confirmed} potwierdzonych, {pending} oczekujących', - 'reservations.fromPlan': 'Z planu', - 'reservations.showFiles': 'Pokaż pliki', - 'reservations.editTitle': 'Edytuj rezerwację', - 'reservations.status': 'Status', - 'reservations.datetime': 'Data i czas', - 'reservations.startTime': 'Godzina rozpoczęcia', - 'reservations.endTime': 'Godzina zakończenia', - 'reservations.date': 'Data', - 'reservations.time': 'Godzina', - 'reservations.timeAlt': 'Godzina (alternatywna, np. 19:30)', - 'reservations.notes': 'Notatki', - 'reservations.notesPlaceholder': 'Dodatkowe notatki...', - 'reservations.meta.airline': 'Linia lotnicza', - 'reservations.meta.flightNumber': 'Numer lotu', - 'reservations.meta.from': 'Skąd', - 'reservations.meta.to': 'Dokąd', - 'reservations.meta.trainNumber': 'Numer pociągu', - 'reservations.meta.platform': 'Peron', - 'reservations.meta.seat': 'Miejsce', - 'reservations.meta.checkIn': 'Zameldowanie', - 'reservations.meta.checkInUntil': 'Check-in do', - 'reservations.meta.checkOut': 'Wymeldowanie', - 'reservations.meta.linkAccommodation': 'Zakwaterowanie', - 'reservations.meta.pickAccommodation': 'Link do zakwaterowania', - 'reservations.meta.noAccommodation': 'Brak', - 'reservations.meta.hotelPlace': 'Zakwaterowanie', - 'reservations.meta.pickHotel': 'Wybierz zakwaterowanie', - 'reservations.meta.fromDay': 'Od', - 'reservations.meta.toDay': 'Do', - 'reservations.meta.selectDay': 'Wybierz dzień', - 'reservations.type.flight': 'Lot', - 'reservations.type.hotel': 'Zakwaterowanie', - 'reservations.type.restaurant': 'Restauracja', - 'reservations.type.train': 'Pociąg', - 'reservations.type.car': 'Samochód', - 'reservations.needsReview': 'Sprawdź', - 'reservations.needsReviewHint': 'Nie udało się automatycznie dopasować lotniska — potwierdź lokalizację.', - 'reservations.searchLocation': 'Szukaj stacji, portu, adresu...', - 'airport.searchPlaceholder': 'Kod lotniska lub miasto (np. FRA)', - 'map.connections': 'Połączenia', - 'map.showConnections': 'Pokaż trasy rezerwacji', - 'map.hideConnections': 'Ukryj trasy rezerwacji', - 'settings.bookingLabels': 'Etykiety tras rezerwacji', - 'settings.bookingLabelsHint': 'Pokazuje nazwy stacji / lotnisk na mapie. Gdy wyłączone, wyświetlana jest tylko ikona.', - 'reservations.type.cruise': 'Rejs', - 'reservations.type.event': 'Wydarzenie', - 'reservations.type.tour': 'Wycieczka', - 'reservations.type.other': 'Inne', - 'reservations.confirm.delete': 'Czy na pewno chcesz usunąć rezerwację "{name}"?', - 'reservations.confirm.deleteTitle': 'Usunąć rezerwację?', - 'reservations.confirm.deleteBody': 'Rezerwacja "{name}" zostanie trwale usunięta.', - 'reservations.toast.updated': 'Rezerwacja została zaktualizowana', - 'reservations.toast.removed': 'Rezerwacja została usunięta', - 'reservations.toast.fileUploaded': 'Plik został przesłany', - 'reservations.toast.uploadError': 'Nie udało się przesłać pliku', - 'reservations.newTitle': 'Nowa rezerwacja', - 'reservations.bookingType': 'Rodzaj rezerwacji', - 'reservations.titleLabel': 'Tytuł', - 'reservations.titlePlaceholder': 'np. Ryanair FR123, Hotel Dubaj, ...', - 'reservations.locationAddress': 'Lokalizacja / Adres', - 'reservations.locationPlaceholder': 'Adres, Lotnisko, Hotel...', - 'reservations.confirmationCode': 'Kod rezerwacji', - 'reservations.confirmationPlaceholder': 'np. ABC12345', - 'reservations.day': 'Dzień', - 'reservations.noDay': 'Brak dnia', - 'reservations.place': 'Miejsce', - 'reservations.noPlace': 'Brak miejsca', - 'reservations.pendingSave': 'zostanie zapisane...', - 'reservations.uploading': 'Przesyłanie...', - 'reservations.attachFile': 'Załącz plik', - 'reservations.linkExisting': 'Podlinkuj przesłany plik', - 'reservations.toast.saveError': 'Nie udało się zapisać', - 'reservations.toast.updateError': 'Nie udało się zaktualizować', - 'reservations.toast.deleteError': 'Nie udało się usunąć', - 'reservations.confirm.remove': 'Usunąć rezerwację "{name}"?', - 'reservations.linkAssignment': 'Przypisz do miejsca', - 'reservations.pickAssignment': 'Wybierz miejsce z planu...', - 'reservations.noAssignment': 'Brak przypisania (samodzielna)', - 'reservations.price': 'Cena', - 'reservations.budgetCategory': 'Kategoria budżetu', - 'reservations.budgetCategoryPlaceholder': 'np. Transport, Zakwaterowanie', - 'reservations.budgetCategoryAuto': 'Auto (na podstawie typu rezerwacji)', - 'reservations.budgetHint': 'Wpis budżetowy zostanie automatycznie utworzony podczas zapisywania.', - 'reservations.departureDate': 'Wylot', - 'reservations.arrivalDate': 'Przylot', - 'reservations.departureTime': 'Godz. wylotu', - 'reservations.arrivalTime': 'Godz. przylotu', - 'reservations.pickupDate': 'Odbiór', - 'reservations.returnDate': 'Zwrot', - 'reservations.pickupTime': 'Godz. odbioru', - 'reservations.returnTime': 'Godz. zwrotu', - 'reservations.endDate': 'Data końca', - 'reservations.meta.departureTimezone': 'TZ wylotu', - 'reservations.meta.arrivalTimezone': 'TZ przylotu', - 'reservations.span.departure': 'Wylot', - 'reservations.span.arrival': 'Przylot', - 'reservations.span.inTransit': 'W tranzycie', - 'reservations.span.pickup': 'Odbiór', - 'reservations.span.return': 'Zwrot', - 'reservations.span.active': 'Aktywny', - 'reservations.span.start': 'Start', - 'reservations.span.end': 'Koniec', - 'reservations.span.ongoing': 'W trakcie', - 'reservations.validation.endBeforeStart': 'Data/godzina zakończenia musi być późniejsza niż data/godzina rozpoczęcia', - 'reservations.addBooking': 'Dodaj rezerwację', - - // Budget - 'budget.title': 'Budżet', - 'budget.emptyTitle': 'Nie utworzono jeszcze budżetu', - 'budget.emptyText': 'Utwórz kategorie i wpisy, aby zaplanować budżet podróży', - 'budget.emptyPlaceholder': 'Podaj nazwę kategorii...', - 'budget.createCategory': 'Utwórz kategorię', - 'budget.category': 'Kategoria', - 'budget.categoryName': 'Nazwa kategorii', - 'budget.table.name': 'Nazwa', - 'budget.table.total': 'Łącznie', - 'budget.table.persons': 'Osoby', - 'budget.table.days': 'Dni', - 'budget.table.perPerson': 'Za osobę', - 'budget.table.perDay': 'Za dzień', - 'budget.table.perPersonDay': 'Za osobę/dzień', - 'budget.table.note': 'Notatka', - 'budget.newEntry': 'Nowy wpis', - 'budget.defaultEntry': 'Nowy wpis', - 'budget.defaultCategory': 'Nowa kategoria', - 'budget.total': 'Łącznie', - 'budget.totalBudget': 'Całkowity budżet', - 'budget.byCategory': 'Według kategorii', - 'budget.editTooltip': 'Kliknij, aby edytować', - 'budget.linkedToReservation': 'Powiązano z rezerwacją — edytuj nazwę tam', - 'budget.confirm.deleteCategory': 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} wpisami?', - 'budget.deleteCategory': 'Usuń kategorię', - 'budget.perPerson': 'Za osobę', - 'budget.paid': 'Zapłacone', - 'budget.open': 'Otwarte', - 'budget.noMembers': 'Brak przypisanych członków', - 'budget.settlement': 'Rozliczenie', - 'budget.settlementInfo': 'Kliknij avatar członka przy pozycji w budżecie, aby oznaczyć go na zielono — oznacza to, że zapłacił. Rozliczenie pokaże, kto komu i ile jest winien.', - 'budget.netBalances': 'Bilans', - - // Files - 'files.title': 'Pliki', - 'files.pageTitle': 'Pliki i dokumenty', - 'files.subtitle': '{count} plików dla {trip}', - 'files.download': 'Pobierz', - 'files.openError': 'Nie można otworzyć pliku', - 'files.downloadPdf': 'Pobierz PDF', - 'files.count': '{count} plików', - 'files.countSingular': '1 plik', - 'files.uploaded': '{count} przesłanych', - 'files.uploadError': 'Przesyłanie nie powiodło się', - 'files.dropzone': 'Przeciągnij pliki tutaj', - 'files.dropzoneHint': 'lub kliknij, aby przeglądać', - 'files.allowedTypes': 'Obrazki, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB', - 'files.uploading': 'Przesyłanie...', - 'files.filterAll': 'Wszystkie', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Obrazki', - 'files.filterDocs': 'Dokumenty', - 'files.filterCollab': 'Wspólne notatki', - 'files.sourceCollab': 'Z wspólnych notatek', - 'files.empty': 'Brak plików', - 'files.emptyHint': 'Prześlij pliki, aby dodać je do swojej podróży', - 'files.openTab': 'Otwórz w nowej karcie', - 'files.confirm.delete': 'Czy na pewno chcesz usunąć ten plik?', - 'files.toast.deleted': 'Plik został usunięty', - 'files.toast.deleteError': 'Nie udało się usunąć pliku', - 'files.sourcePlan': 'Plan dni', - 'files.sourceBooking': 'Rezerwacje', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Załącz', - 'files.pasteHint': 'Możesz również wkleić obrazki ze schowka (Ctrl+V)', - 'files.trash': 'Kosz', - 'files.trashEmpty': 'Kosz jest pusty', - 'files.emptyTrash': 'Opróżnij kosz', - 'files.restore': 'Przywróć', - 'files.star': 'Oznacz', - 'files.unstar': 'Usuń oznaczenie', - 'files.assign': 'Przypisz', - 'files.assignTitle': 'Przypisz plik', - 'files.assignPlace': 'Miejsce', - 'files.assignBooking': 'Rezerwacja', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Nieprzypisane', - 'files.unlink': 'Usuń link', - 'files.toast.trashed': 'Przeniesiono do kosza', - 'files.toast.restored': 'Plik został przywrócony', - 'files.toast.trashEmptied': 'Kosz został opróżniony', - 'files.toast.assigned': 'Plik został przypisany', - 'files.toast.assignError': 'Nie udało się przypisać', - 'files.toast.restoreError': 'Nie udało się przywrócić', - 'files.confirm.permanentDelete': 'Czy na pewno chcesz trwale usunąć ten plik? Tej operacji nie można cofnąć.', - 'files.confirm.emptyTrash': 'Czy na pewno chcesz trwale usunąć wszystkie pliki z kosza? Tej operacji nie można cofnąć.', - 'files.noteLabel': 'Notatka', - 'files.notePlaceholder': 'Dodaj notatkę...', - - // Packing - 'packing.title': 'Lista pakowania', - 'packing.empty': 'Lista pakowania jest pusta', - 'packing.import': 'Importuj', - 'packing.importTitle': 'Importuj listę pakowania', - 'packing.importHint': 'Jedna pozycja w wierszu. Format: kategoria, nazwa, waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie)', - 'packing.importPlaceholder': 'Higiena, Szczoteczka do zębów\nOdzież, Koszulki, 200\nDokumenty, Paszport, , Podręczny\nElektronika, Ładowarka, 50, Walizka, checked', - 'packing.importCsv': 'Załaduj CSV/TXT', - 'packing.importAction': 'Importuj {count}', - 'packing.importSuccess': '{count} pozycji zaimportowanych', - 'packing.importError': 'Import nie powiódł się', - 'packing.importEmpty': 'Brak pozycji do zaimportowania', - 'packing.progress': '{packed} z {total} spakowanych ({percent}%)', - 'packing.clearChecked': 'Usuń {count} spakowanych', - 'packing.clearCheckedShort': 'Usuń {count}', - 'packing.suggestions': 'Sugestie', - 'packing.suggestionsTitle': 'Dodaj sugestie', - 'packing.allSuggested': 'Dodano wszystkie sugestie', - 'packing.allPacked': 'Wszystko spakowane!', - 'packing.addPlaceholder': 'Dodaj nowy przedmiot...', - 'packing.categoryPlaceholder': 'Kategoria...', - 'packing.filterAll': 'Wszystkie', - 'packing.filterOpen': 'Do spakowania', - 'packing.filterDone': 'Spakowane', - 'packing.emptyTitle': 'Lista pakowania jest pusta', - 'packing.emptyHint': 'Dodaj przedmioty lub użyj sugestii', - 'packing.emptyFiltered': 'Brak przedmiotów pasujących do filtra', - 'packing.menuRename': 'Zmień nazwę', - 'packing.menuCheckAll': 'Zaznacz wszystko', - 'packing.menuUncheckAll': 'Odznacz wszystko', - 'packing.menuDeleteCat': 'Usuń kategorię', - 'packing.saveAsTemplate': 'Zapisz jako szablon', - 'packing.templateName': 'Nazwa szablonu', - 'packing.templateSaved': 'Lista pakowania zapisana jako szablon', - 'packing.noMembers': 'Brak członków podróży', - 'packing.addItem': 'Dodaj przedmiot', - 'packing.addItemPlaceholder': 'Nazwa przedmiotu...', - 'packing.addCategory': 'Dodaj kategorię', - 'packing.newCategoryPlaceholder': 'Nazwa kategorii (np. Odzież)', - 'packing.applyTemplate': 'Zastosuj szablon', - 'packing.template': 'Szablon', - 'packing.templateApplied': '{count} przedmiotów dodanych z szablonu', - 'packing.templateError': 'Nie udało się zastosować szablonu', - 'packing.bags': 'Torby', - 'packing.noBag': 'Nieprzypisane', - 'packing.totalWeight': 'Waga całkowita', - 'packing.bagName': 'Nazwa torby...', - 'packing.addBag': 'Dodaj torbę', - 'packing.changeCategory': 'Zmień kategorię', - 'packing.confirm.clearChecked': 'Czy na pewno chcesz usunąć {count} spakowanych przedmiotów?', - 'packing.confirm.deleteCat': 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} przedmiotami?', - 'packing.defaultCategory': 'Inne', - 'packing.toast.saveError': 'Nie udało się zapisać', - 'packing.toast.deleteError': 'Nie udało się usunąć', - 'packing.toast.renameError': 'Nie udało się zmienić nazwy', - 'packing.toast.addError': 'Nie udało się dodać', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Paszport', category: 'Dokumenty' }, - { name: 'Dowód osobisty', category: 'Dokumenty' }, - { name: 'Ubezpieczenie turystyczne', category: 'Dokumenty' }, - { name: 'Bilety lotnicze', category: 'Dokumenty' }, - { name: 'Karta kredytowa', category: 'Finanse' }, - { name: 'Gotówka', category: 'Finanse' }, - { name: 'Wiza', category: 'Dokumenty' }, - { name: 'Koszulki', category: 'Odzież' }, - { name: 'Spodnie', category: 'Odzież' }, - { name: 'Bielizna', category: 'Odzież' }, - { name: 'Skarpetki', category: 'Odzież' }, - { name: 'Kurtka', category: 'Odzież' }, - { name: 'Piżama', category: 'Odzież' }, - { name: 'Strój kąpielowy', category: 'Odzież' }, - { name: 'Kurtka przeciwdeszczowa', category: 'Odzież' }, - { name: 'Wygodne buty', category: 'Obuwie' }, - { name: 'Szczoteczka do zębów', category: 'Higiena' }, - { name: 'Pasta do zębów', category: 'Higiena' }, - { name: 'Szampon', category: 'Higiena' }, - { name: 'Dezodorant', category: 'Higiena' }, - { name: 'Krem z filtrem', category: 'Higiena' }, - { name: 'Maszynka do golenia', category: 'Higiena' }, - { name: 'Ładowarka', category: 'Elektronika' }, - { name: 'Powerbank', category: 'Elektronika' }, - { name: 'Słuchawki', category: 'Elektronika' }, - { name: 'Adapter podróżny', category: 'Elektronika' }, - { name: 'Aparat', category: 'Elektronika' }, - { name: 'Leki', category: 'Zdrowie' }, - { name: 'Plastry', category: 'Zdrowie' }, - { name: 'Środek dezynfekujący', category: 'Zdrowie' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Udostępnij podróż', - 'members.inviteUser': 'Zaproś użytkownika', - 'members.selectUser': 'Wybierz użytkownika...', - 'members.invite': 'Zaproś', - 'members.allHaveAccess': 'Wszyscy użytkownicy mają już dostęp.', - 'members.access': 'Dostęp', - 'members.person': 'osoba', - 'members.persons': 'osoby', - 'members.you': 'ty', - 'members.owner': 'Właściciel', - 'members.leaveTrip': 'Opuść podróż', - 'members.removeAccess': 'Usuń dostęp', - 'members.confirmLeave': 'Opuścić podróż? Stracisz dostęp.', - 'members.confirmRemove': 'Usunąć dostęp dla tego użytkownika?', - 'members.loadError': 'Nie udało się załadować członków', - 'members.added': 'dodano', - 'members.addError': 'Nie udało się dodać członka', - 'members.removed': 'Usunięto członka', - 'members.removeError': 'Nie udało się usunąć członka', - - // Categories (Admin) - 'categories.title': 'Kategorie', - 'categories.subtitle': 'Zarządzaj kategoriami miejsc', - 'categories.new': 'Nowa kategoria', - 'categories.empty': 'Brak kategorii', - 'categories.namePlaceholder': 'Nazwa kategorii', - 'categories.icon': 'Ikona', - 'categories.color': 'Kolor', - 'categories.customColor': 'Wybierz własny kolor', - 'categories.preview': 'Podgląd', - 'categories.defaultName': 'Kategoria', - 'categories.update': 'Aktualizuj', - 'categories.create': 'Utwórz', - 'categories.confirm.delete': 'Usunąć kategorię? Miejsca w tej kategorii nie zostaną usunięte.', - 'categories.toast.loadError': 'Nie udało się załadować kategorii', - 'categories.toast.nameRequired': 'Proszę podać nazwę', - 'categories.toast.updated': 'Kategoria została zaktualizowana', - 'categories.toast.created': 'Kategoria została utworzona', - 'categories.toast.saveError': 'Nie udało się zapisać kategorii', - 'categories.toast.deleted': 'Kategoria została usunięta', - 'categories.toast.deleteError': 'Nie udało się usunąć kategorii', - - // Backup (Admin) - 'backup.title': 'Kopia zapasowa danych', - 'backup.subtitle': 'Baza danych i wszystkie przesłane pliki', - 'backup.refresh': 'Odśwież', - 'backup.upload': 'Prześlij kopię zapasową', - 'backup.uploading': 'Przesyłanie...', - 'backup.create': 'Utwórz kopię zapasową', - 'backup.creating': 'Tworzenie...', - 'backup.empty': 'Brak kopii zapasowych', - 'backup.createFirst': 'Utwórz pierwszą kopię zapasową', - 'backup.download': 'Pobierz', - 'backup.restore': 'Przywróć', - 'backup.confirm.restore': 'Przywrócić kopię zapasową "{name}"?\n\nWszystkie obecne dane zostaną zastąpione danymi z kopii zapasowej.', - 'backup.confirm.uploadRestore': 'Przesłać i przywrócić plik kopii zapasowej "{name}"?\n\nWszystkie obecne dane zostaną nadpisane.', - 'backup.confirm.delete': 'Usunąć kopię zapasową "{name}"?', - 'backup.toast.loadError': 'Nie udało się załadować kopii zapasowych', - 'backup.toast.created': 'Kopia zapasowa została utworzona pomyślnie', - 'backup.toast.createError': 'Nie udało się utworzyć kopii zapasowej', - 'backup.toast.restored': 'Kopia zapasowa została przywrócona. Strona zostanie przeładowana...', - 'backup.toast.restoreError': 'Nie udało się przywrócić kopii zapasowej', - 'backup.toast.uploadError': 'Nie udało się przesłać kopii zapasowej', - 'backup.toast.deleted': 'Kopia zapasowa została usunięta', - 'backup.toast.deleteError': 'Nie udało się usunąć kopii zapasowej', - 'backup.toast.downloadError': 'Nie udało się pobrać kopii zapasowej', - 'backup.toast.settingsSaved': 'Ustawienia automatycznej kopii zapasowej zostały zapisane', - 'backup.toast.settingsError': 'Nie udało się zapisać ustawień', - 'backup.auto.title': 'Automatyczna kopia zapasowa', - 'backup.auto.subtitle': 'Tworzenie automatycznej kopii zapasowej według harmonogramu', - 'backup.auto.enable': 'Włącz automatyczną kopię zapasową', - 'backup.auto.enableHint': 'Kopie zapasowe będą tworzone automatycznie zgodnie z wybranym harmonogramem', - 'backup.auto.interval': 'Częstotliwość', - 'backup.auto.hour': 'Uruchom o godzinie', - 'backup.auto.hourHint': 'Czas lokalny serwera ({format} format)', - 'backup.auto.dayOfWeek': 'Dzień tygodnia', - 'backup.auto.dayOfMonth': 'Dzień miesiąca', - 'backup.auto.dayOfMonthHint': 'Ograniczone do 1–28 dla kompatybilności ze wszystkimi miesiącami', - 'backup.auto.scheduleSummary': 'Harmonogram', - 'backup.auto.summaryDaily': 'Każdego dnia o {hour}:00', - 'backup.auto.summaryWeekly': 'Co {day} o {hour}:00', - 'backup.auto.summaryMonthly': '{day}. dnia każdego miesiąca o {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Automatyczne kopie zapasowe są konfigurowane za pomocą zmiennych środowiskowych Dockera. Aby zmienić te ustawienia, zaktualizuj plik docker-compose.yml i uruchom ponownie kontener.', - 'backup.auto.copyEnv': 'Kopiuj zmienne środowiskowe Dockera', - 'backup.auto.envCopied': 'Zmienne środowiskowe Dockera zostały skopiowane do schowka', - 'backup.auto.keepLabel': 'Usuń stare kopie zapasowe po', - 'backup.dow.sunday': 'Nd', - 'backup.dow.monday': 'Pon', - 'backup.dow.tuesday': 'Wt', - 'backup.dow.wednesday': 'Śr', - 'backup.dow.thursday': 'Czw', - 'backup.dow.friday': 'Pt', - 'backup.dow.saturday': 'Sob', - 'backup.interval.hourly': 'Co godzinę', - 'backup.interval.daily': 'Co dzień', - 'backup.interval.weekly': 'Co tydzień', - 'backup.interval.monthly': 'Co miesiąc', - 'backup.keep.1day': '1 dzień', - 'backup.keep.3days': '3 dni', - 'backup.keep.7days': '7 dni', - 'backup.keep.14days': '14 dni', - 'backup.keep.30days': '30 dni', - 'backup.keep.forever': 'Przechowuj na zawsze', - - // Photos - 'photos.title': 'Zdjęcia', - 'photos.subtitle': '{count} zdjęć dla {trip}', - 'photos.dropHere': 'Przeciągnij zdjęcia tutaj...', - 'photos.dropHereActive': 'Przeciągnij zdjęcia tutaj', - 'photos.captionForAll': 'Podpis (dla wszystkich)', - 'photos.captionPlaceholder': 'Opcjonalny podpis...', - 'photos.addCaption': 'Dodaj podpis...', - 'photos.allDays': 'Wszystkie dni', - 'photos.noPhotos': 'Brak zdjęć', - 'photos.uploadHint': 'Prześlij zdjęcia z podróży', - 'photos.clickToSelect': 'lub kliknij, aby wybrać', - 'photos.linkPlace': 'Połącz z miejscem', - 'photos.noPlace': 'Brak miejsca', - 'photos.uploadN': 'Prześlij {n} zdjęć', - 'photos.linkDay': 'Połącz dzień', - 'photos.noDay': 'Brak dnia', - 'photos.dayLabel': 'Dzień {number}', - 'photos.photoSelected': 'Zdjęcie wybrane', - 'photos.photosSelected': 'Zdjęcia wybrane', - 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · do 30 zdjęć', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Przywrócić kopię zapasową?', - 'backup.restoreWarning': 'Wszystkie obecne dane (podróże, miejsca, użytkownicy, przesłane pliki) zostaną trwale zastąpione danymi z kopii zapasowej. Tej operacji nie można cofnąć.', - 'backup.restoreTip': 'Wskazówka: Przed przywróceniem utwórz kopię zapasową bieżącej instancji.', - 'backup.restoreConfirm': 'Tak, przywróć', - - // PDF - 'pdf.travelPlan': 'Plan podróży', - 'pdf.planned': 'Zaplanowane', - 'pdf.costLabel': 'Koszt w EUR', - 'pdf.preview': 'Podgląd PDF', - 'pdf.saveAsPdf': 'Zapisz jako PDF', - - // Planner - 'planner.places': 'Miejsca', - 'planner.bookings': 'Rezerwacje', - 'planner.packingList': 'Lista pakowania', - 'planner.documents': 'Dokumenty', - 'planner.dayPlan': 'Plan', - 'planner.reservations': 'Rezerwacje', - 'planner.minTwoPlaces': 'Wymagane są przynajmniej dwa miejsca ze współrzędnymi', - 'planner.noGeoPlaces': 'Brak miejsc ze współrzędnymi', - 'planner.routeCalculated': 'Trasa została obliczona', - 'planner.routeCalcFailed': 'Nie udało się obliczyć trasy', - 'planner.routeError': 'Błąd obliczania trasy', - 'planner.icsExportFailed': 'Eksport ICS nie powiódł się', - 'planner.routeOptimized': 'Trasa została zoptymalizowana', - 'planner.reservationUpdated': 'Rezerwacja została zaktualizowana', - 'planner.reservationAdded': 'Rezerwacja została dodana', - 'planner.confirmDeleteReservation': 'Usunąć rezerwację?', - 'planner.reservationDeleted': 'Rezerwacja została usunięta', - 'planner.days': 'Dni', - 'planner.allPlaces': 'Wszystkie miejsca', - 'planner.totalPlaces': '{n} miejsc ogółem', - 'planner.noDaysPlanned': 'Nie zaplanowano jeszcze dni', - 'planner.editTrip': 'Edytuj podróż \u2192', - 'planner.placeOne': '1 miejsce', - 'planner.placeN': '{n} miejsc', - 'planner.addNote': 'Dodaj notatkę', - 'planner.noEntries': 'Brak wpisów dla tego dnia', - 'planner.addPlace': 'Dodaj miejsce/atrakcję', - 'planner.addPlaceShort': '+ Dodaj miejsce/atrakcję', - 'planner.resPending': 'Rezerwacja oczekująca · ', - 'planner.resConfirmed': 'Rezerwacja potwierdzona · ', - 'planner.notePlaceholder': 'Notatka\u2026', - 'planner.noteTimePlaceholder': 'Godzina (opcjonalnie)', - 'planner.noteExamplePlaceholder': 'np. S3 o 14:30 z dworca centralnego, prom z molo 7, przerwa na lunch\u2026', - 'planner.totalCost': 'Całkowity koszt', - 'planner.searchPlaces': 'Szukaj miejsc\u2026', - 'planner.allCategories': 'Wszystkie kategorie', - 'planner.noPlacesFound': 'Nie znaleziono miejsc', - 'planner.addFirstPlace': 'Dodaj pierwsze miejsce', - 'planner.noReservations': 'Brak rezerwacji', - 'planner.addFirstReservation': 'Dodaj pierwszą rezerwację', - 'planner.new': 'Nowy', - 'planner.addToDay': '+ Dzień', - 'planner.calculating': 'Obliczanie\u2026', - 'planner.route': 'Trasa', - 'planner.optimize': 'Optymalizuj', - 'planner.openGoogleMaps': 'Otwórz w Google Maps', - 'planner.selectDayHint': 'Wybierz dzień z listy po lewej, aby zobaczyć jego plan', - 'planner.noPlacesForDay': 'Brak miejsc dla tego dnia', - 'planner.addPlacesLink': 'Dodaj miejsca \u2192', - 'planner.minTotal': 'min. łącznie', - 'planner.noReservation': 'Brak rezerwacji', - 'planner.removeFromDay': 'Usuń z dnia', - 'planner.addToThisDay': 'Dodaj do dnia', - 'planner.overview': 'Przegląd', - 'planner.noDays': 'Brak dni', - 'planner.editTripToAddDays': 'Edytuj podróż, aby dodać dni', - 'planner.dayCount': '{n} dni', - 'planner.clickToUnlock': 'Kliknij, aby odblokować', - 'planner.keepPosition': 'Zachowaj pozycję podczas optymalizacji trasy', - 'planner.dayDetails': 'Szczegóły dnia', - 'planner.dayN': 'Dzień {n}', - - // Dashboard Stats - 'stats.countries': 'Kraje', - 'stats.cities': 'Miasta', - 'stats.trips': 'Podróże', - 'stats.places': 'Miejsca', - 'stats.worldProgress': 'Postęp', - 'stats.visited': 'odwiedzone', - 'stats.remaining': 'pozostałe', - 'stats.visitedCountries': 'Odwiedzone kraje', - - // Day Detail Panel - 'day.precipProb': 'Prawdopodobieństwo opadów', - 'day.precipitation': 'Opady', - 'day.wind': 'Wiatr', - 'day.sunrise': 'Wschód słońca', - 'day.sunset': 'Zachód słońca', - 'day.hourlyForecast': 'Prognoza godzinowa', - 'day.climateHint': 'Historyczne średnie — rzeczywista prognoza dostępna na następne 16 dni od tej daty.', - 'day.noWeather': 'Brak danych pogodowych. Dodaj miejsce ze współrzędnymi.', - 'day.overview': 'Przegląd dnia', - 'day.accommodation': 'Zakwaterowanie', - 'day.addAccommodation': 'Dodaj zakwaterowanie', - 'day.hotelDayRange': 'Zastosuj do dni', - 'day.noPlacesForHotel': 'Najpierw dodaj miejsca do swojej podróży', - 'day.allDays': 'Wszystkie', - 'day.checkIn': 'Zameldowanie', - 'day.checkInUntil': 'Do', - 'day.checkOut': 'Wymeldowanie', - 'day.confirmation': 'Potwierdzenie', - 'day.editAccommodation': 'Edytuj zakwaterowanie', - 'day.reservations': 'Rezerwacje', - - // Photos / Immich - 'memories.title': 'Zdjęcia', - 'memories.notConnected': 'Immich nie jest połączony', - 'memories.notConnectedHint': 'Połącz swoją instancję Immich w ustawieniach, aby przeglądać tutaj swoje zdjęcia z podróży.', - 'memories.notConnectedMultipleHint': 'Połącz jednego z tych dostawców zdjęć: {provider_names} w Ustawieniach, aby móc dodawać zdjęcia do tej podróży.', - 'memories.noDates': 'Dodaj daty do swojej podróży, aby załadować zdjęcia.', - 'memories.noPhotos': 'Nie znaleziono zdjęć', - 'memories.noPhotosHint': 'Nie znaleziono zdjęć w Immich dla tego zakresu dat podróży.', - 'memories.photosFound': 'zdjęć', - 'memories.fromOthers': 'od innych', - 'memories.sharePhotos': 'Udostępnij zdjęcia', - 'memories.sharing': 'Udostępnianie', - 'memories.reviewTitle': 'Przejrzyj swoje zdjęcia', - 'memories.reviewHint': 'Kliknij w zdjęcia, aby wykluczyć je z udostępnienia.', - 'memories.shareCount': 'Udostępnij {count} zdjęć', - 'memories.providerUrl': 'URL serwera', - 'memories.providerApiKey': 'Klucz API', - 'memories.providerUsername': 'Nazwa użytkownika', - 'memories.providerPassword': 'Hasło', - 'memories.providerOTP': 'Kod MFA (jeśli włączony)', - 'memories.skipSSLVerification': 'Pomiń weryfikację certyfikatu SSL', - 'memories.immichAutoUpload': 'Przy przesyłaniu kopiuj zdjęcia journey także do Immich', - 'memories.providerUrlHintSynology': 'Uwzględnij ścieżkę aplikacji Photos w URL, np. https://nas:5001/photo', - 'memories.testConnection': 'Test', - 'memories.testShort': 'Test', - 'memories.connected': 'Połączono', - 'memories.disconnected': 'Nie połączono', - 'memories.connectionSuccess': 'Połączono z Immich', - 'memories.connectionError': 'Nie udało się połączyć z Immich', - 'memories.saved': 'Ustawienia {provider_name} zostały zapisane', - 'memories.providerDisconnectedBanner': 'Połączenie z {provider_name} zostało utracone. Połącz ponownie w Ustawieniach, aby wyświetlać zdjęcia.', - 'memories.saveError': 'Nie można zapisać ustawień {provider_name}', - 'memories.addPhotos': 'Dodaj zdjęcia', - 'memories.selectPhotos': 'Wybierz zdjęcia z Immich', - 'memories.selectPhotosMultiple': 'Wybierz zdjęcia', - 'memories.selectHint': 'Dotknij zdjęć, aby je zaznaczyć.', - 'memories.selected': 'wybranych', - 'memories.addSelected': 'Dodaj {count} zdjęć', - 'memories.alreadyAdded': 'Dodano', - 'memories.private': 'Prywatne', - 'memories.stopSharing': 'Przestań udostępniać', - 'memories.oldest': 'Od najstarszych', - 'memories.newest': 'Od najnowszych', - 'memories.allLocations': 'Wszystkie lokalizacje', - 'memories.tripDates': 'Daty podróży', - 'memories.allPhotos': 'Wszystkie zdjęcia', - 'memories.confirmShareTitle': 'Udostępnić członkom podróży?', - 'memories.confirmShareHint': '{count} zdjęć będzie widocznych dla wszystkich członków tej podróży. Możesz później ustawić poszczególne zdjęcia jako prywatne.', - 'memories.confirmShareButton': 'Udostępnij zdjęcia', - - // Collab Addon - 'collab.tabs.chat': 'Czat', - 'collab.tabs.notes': 'Notatki', - 'collab.tabs.polls': 'Ankiety', - 'collab.whatsNext.title': 'Co dalej', - 'collab.whatsNext.today': 'Dzisiaj', - 'collab.whatsNext.tomorrow': 'Jutro', - 'collab.whatsNext.empty': 'Brak nadchodzących aktywności', - 'collab.whatsNext.until': 'do', - 'collab.whatsNext.emptyHint': 'Aktywności z godzinami pojawią się tutaj', - 'collab.chat.send': 'Wyślij', - 'collab.chat.placeholder': 'Napisz wiadomość...', - 'collab.chat.empty': 'Rozpocznij konwersację', - 'collab.chat.emptyHint': 'Wiadomości są widoczne dla wszystkich uczestników podróży', - 'collab.chat.emptyDesc': 'Dziel się pomysłami, planami i aktualizacjami z uczestnikami podróży', - 'collab.chat.today': 'Dzisiaj', - 'collab.chat.yesterday': 'Wczoraj', - 'collab.chat.deletedMessage': 'usunięto wiadomość', - 'collab.chat.loadMore': 'Załaduj starsze wiadomości', - 'collab.chat.justNow': 'teraz', - 'collab.chat.minutesAgo': '{n}m temu', - 'collab.chat.hoursAgo': '{n}h temu', - 'collab.notes.title': 'Notatki', - 'collab.notes.new': 'Nowa notatka', - 'collab.notes.empty': 'Brak notatek', - 'collab.notes.emptyHint': 'Zapisuj pomysły i plany', - 'collab.notes.all': 'Wszystkie', - 'collab.notes.titlePlaceholder': 'Tytuł notatki', - 'collab.notes.contentPlaceholder': 'Napisz coś...', - 'collab.notes.categoryPlaceholder': 'Kategoria', - 'collab.notes.newCategory': 'Nowa kategoria...', - 'collab.notes.category': 'Kategoria', - 'collab.notes.noCategory': 'Brak kategorii', - 'collab.notes.color': 'Kolor', - 'collab.notes.save': 'Zapisz', - 'collab.notes.cancel': 'Anuluj', - 'collab.notes.edit': 'Edytuj', - 'collab.notes.delete': 'Usuń', - 'collab.notes.pin': 'Przypnij', - 'collab.notes.unpin': 'Odepnij', - 'collab.notes.daysAgo': '{n}d temu', - 'collab.notes.categorySettings': 'Zarządzaj kategoriami', - 'collab.notes.create': 'Utwórz', - 'collab.notes.website': 'Strona internetowa', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Załącz pliki', - 'collab.notes.noCategoriesYet': 'Brak kategorii', - 'collab.notes.emptyDesc': 'Utwórz notatkę, aby rozpocząć', - 'collab.polls.title': 'Ankiety', - 'collab.polls.new': 'Nowa ankieta', - 'collab.polls.empty': 'Brak ankiet', - 'collab.polls.emptyHint': 'Zapytaj grupę i głosujcie razem', - 'collab.polls.question': 'Pytanie', - 'collab.polls.questionPlaceholder': 'Co powinniśmy zrobić?', - 'collab.polls.addOption': '+ Dodaj opcję', - 'collab.polls.optionPlaceholder': 'Opcja {n}', - 'collab.polls.create': 'Utwórz ankietę', - 'collab.polls.close': 'Zamknij', - 'collab.polls.closed': 'Zamknięta', - 'collab.polls.votes': '{n} głosów', - 'collab.polls.vote': '{n} głos', - 'collab.polls.multipleChoice': 'Wielokrotny wybór', - 'collab.polls.multiChoice': 'Wielokrotny wybór', - 'collab.polls.deadline': 'Koniec', - 'collab.polls.option': 'Opcja', - 'collab.polls.options': 'Opcje', - 'collab.polls.delete': 'Usuń', - 'collab.polls.closedSection': 'Zamknięte', - 'common.import': 'Importuj', - 'common.select': 'Wybierz', - 'common.selectAll': 'Zaznacz wszystko', - 'common.deselectAll': 'Odznacz wszystko', - 'common.saved': 'Zapisano', - 'trips.reminder': 'Przypomnienie', - 'trips.reminderNone': 'Brak', - 'trips.reminderDay': 'dzień', - 'trips.reminderDays': 'dni', - 'trips.reminderCustom': 'Niestandardowe', - 'trips.reminderDaysBefore': 'dni przed wyjazdem', - 'trips.reminderDisabledHint': 'Przypomnienia o podróżach są wyłączone.', - 'dashboard.members': 'Towarzysze', - 'dashboard.copyTrip': 'Kopiuj', - 'dashboard.copySuffix': 'kopia', - 'dashboard.toast.copied': 'Podróż skopiowana!', - 'dashboard.toast.copyError': 'Nie udało się skopiować podróży', - 'admin.notifications.title': 'Powiadomienia', - 'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.', - 'admin.notifications.none': 'Wyłączone', - 'admin.notifications.email': 'E-mail (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Zapisz ustawienia powiadomień', - 'admin.notifications.saved': 'Ustawienia powiadomień zapisane', - 'admin.notifications.testWebhook': 'Wyślij testowy webhook', - 'admin.notifications.testWebhookSuccess': 'Testowy webhook wysłany pomyślnie', - 'admin.notifications.testWebhookFailed': 'Testowy webhook nie powiódł się', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Powiadomienia w aplikacji są zawsze aktywne i nie można ich globalnie wyłączyć.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook admina', - 'admin.notifications.adminWebhookPanel.hint': 'Ten webhook służy wyłącznie do powiadomień admina (np. alertów o nowych wersjach). Jest niezależny od webhooków użytkowników i wysyła automatycznie, gdy URL jest skonfigurowany.', - 'admin.notifications.adminWebhookPanel.saved': 'URL webhooka admina zapisany', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Testowy webhook wysłany pomyślnie', - 'admin.notifications.adminWebhookPanel.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Pozwala użytkownikom skonfigurować własne tematy ntfy dla powiadomień push. Ustaw domyślny serwer poniżej, aby wstępnie wypełnić ustawienia użytkownika.', - 'admin.notifications.testNtfy': 'Wyślij testowe Ntfy', - 'admin.notifications.testNtfySuccess': 'Testowe Ntfy wysłane pomyślnie', - 'admin.notifications.testNtfyFailed': 'Wysyłanie testowego Ntfy nie powiodło się', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'Ten temat Ntfy jest używany wyłącznie do powiadomień admina (np. alertów o wersjach). Jest niezależny od tematów użytkowników i zawsze wysyła po skonfigurowaniu.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serwera Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Używany również jako domyślny serwer dla powiadomień ntfy użytkowników. Pozostaw puste, aby użyć ntfy.sh. Użytkownicy mogą to nadpisać w swoich ustawieniach.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Temat admina', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token dostępu (opcjonalne)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Token dostępu admina wyczyszczony', - 'admin.notifications.adminNtfyPanel.saved': 'Ustawienia admin Ntfy zapisane', - 'admin.notifications.adminNtfyPanel.test': 'Wyślij testowe Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Testowe Ntfy wysłane pomyślnie', - 'admin.notifications.adminNtfyPanel.testFailed': 'Wysyłanie testowego Ntfy nie powiodło się', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin Ntfy zawsze wysyła po skonfigurowaniu tematu', - 'admin.notifications.adminNotificationsHint': 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.', - 'admin.notifications.tripReminders.title': 'Przypomnienia o podróżach', - 'admin.notifications.tripReminders.hint': 'Wysyła powiadomienie z przypomnieniem przed rozpoczęciem podróży (wymaga ustawienia dni przypomnienia dla podróży).', - 'admin.notifications.tripReminders.enabled': 'Przypomnienia o podróżach włączone', - 'admin.notifications.tripReminders.disabled': 'Przypomnienia o podróżach wyłączone', - 'admin.webhook.hint': 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).', - 'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.', - 'settings.notificationPreferences.noChannels': 'Brak skonfigurowanych kanałów powiadomień. Poproś administratora o skonfigurowanie powiadomień e-mail lub webhook.', - 'settings.webhookUrl.label': 'URL webhooka', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.', - 'settings.webhookUrl.saved': 'URL webhooka zapisany', - 'settings.webhookUrl.test': 'Testuj', - 'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie', - 'settings.webhookUrl.testFailed': 'Wysyłanie testowego webhooka nie powiodło się', - 'settings.ntfyUrl.topicLabel': 'Temat Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL serwera Ntfy (opcjonalne)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Wprowadź swój temat Ntfy, aby otrzymywać powiadomienia push. Pozostaw pole serwera puste, aby użyć domyślnego ustawienia skonfigurowanego przez administratora.', - 'settings.ntfyUrl.tokenLabel': 'Token dostępu (opcjonalne)', - 'settings.ntfyUrl.tokenHint': 'Wymagane dla tematów chronionych hasłem.', - 'settings.ntfyUrl.saved': 'Ustawienia Ntfy zapisane', - 'settings.ntfyUrl.test': 'Testuj', - 'settings.ntfyUrl.testSuccess': 'Testowe powiadomienie Ntfy wysłane pomyślnie', - 'settings.ntfyUrl.testFailed': 'Testowe powiadomienie Ntfy nie powiodło się', - 'settings.ntfyUrl.tokenCleared': 'Token dostępu wyczyszczony', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationsActive': 'Aktywny kanał', - 'settings.notificationsManagedByAdmin': 'Zdarzenia konfigurowane przez administratora.', - 'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.', - 'login.setNewPassword': 'Ustaw nowe hasło', - 'login.setNewPasswordHint': 'Musisz zmienić hasło.', - 'atlas.searchCountry': 'Szukaj kraju...', - 'trip.loadingPhotos': 'Ładowanie zdjęć...', - 'places.importNaverList': 'Lista Naver', - 'places.importList': 'Import listy', - 'places.googleListHint': 'Wklej link do listy Google Maps.', - 'places.googleListImported': 'Zaimportowano {count} miejsc', - 'places.googleListError': 'Nie udało się zaimportować listy', - 'places.naverListHint': 'Wklej link do udostępnionej listy Naver Maps, aby zaimportować wszystkie miejsca.', - 'places.naverListImported': 'Zaimportowano {count} miejsc z "{list}"', - 'places.naverListError': 'Nie udało się zaimportować listy Naver Maps', - 'places.viewDetails': 'Zobacz szczegóły', - 'inspector.trackStats': 'Statystyki trasy', - 'budget.exportCsv': 'Eksportuj CSV', - 'budget.table.date': 'Data', - 'memories.testFirst': 'Najpierw przetestuj połączenie', - 'memories.linkAlbum': 'Połącz album', - 'memories.selectAlbum': 'Wybierz album Immich', - 'memories.selectAlbumMultiple': 'Wybierz album', - 'memories.noAlbums': 'Nie znaleziono albumów', - 'memories.syncAlbum': 'Synchronizuj album', - 'memories.unlinkAlbum': 'Odłącz album', - 'memories.photos': 'zdjęcia', - 'memories.error.loadAlbums': 'Nie udało się załadować albumów', - 'memories.error.linkAlbum': 'Nie udało się połączyć albumu', - 'memories.error.unlinkAlbum': 'Nie udało się odłączyć albumu', - 'memories.error.syncAlbum': 'Nie udało się zsynchronizować albumu', - 'memories.error.loadPhotos': 'Nie udało się załadować zdjęć', - 'memories.error.addPhotos': 'Nie udało się dodać zdjęć', - 'memories.error.removePhoto': 'Nie udało się usunąć zdjęcia', - 'memories.error.toggleSharing': 'Nie udało się zaktualizować udostępniania', - 'collab.chat.reply': 'Odpowiedz', - 'admin.tabs.permissions': 'Uprawnienia', - 'perm.title': 'Ustawienia uprawnień', - 'perm.subtitle': 'Kontroluj uprawnienia w aplikacji', - 'perm.saved': 'Ustawienia uprawnień zapisane', - 'perm.resetDefaults': 'Przywróć domyślne', - 'perm.customized': 'dostosowane', - 'perm.level.admin': 'Tylko admin', - 'perm.level.tripOwner': 'Właściciel podróży', - 'perm.level.tripMember': 'Członkowie podróży', - 'perm.level.everybody': 'Wszyscy', - 'perm.cat.trip': 'Zarządzanie podróżami', - 'perm.cat.members': 'Zarządzanie członkami', - 'perm.cat.files': 'Pliki', - 'perm.cat.content': 'Treść i harmonogram', - 'perm.cat.extras': 'Budżet, pakowanie i współpraca', - 'perm.action.trip_create': 'Tworzenie podróży', - 'perm.action.trip_edit': 'Edytowanie podróży', - 'perm.action.trip_delete': 'Usuwanie podróży', - 'perm.action.trip_archive': 'Archiwizacja podróży', - 'perm.action.trip_cover_upload': 'Przesyłanie okładki', - 'perm.action.member_manage': 'Zarządzanie członkami', - 'perm.action.file_upload': 'Przesyłanie plików', - 'perm.action.file_edit': 'Edytowanie plików', - 'perm.action.file_delete': 'Usuwanie plików', - 'perm.action.place_edit': 'Zarządzanie miejscami', - 'perm.action.day_edit': 'Edytowanie dni', - 'perm.action.reservation_edit': 'Zarządzanie rezerwacjami', - 'perm.action.budget_edit': 'Zarządzanie budżetem', - 'perm.action.packing_edit': 'Zarządzanie pakowaniem', - 'perm.action.collab_edit': 'Współpraca', - 'perm.action.share_manage': 'Zarządzanie udostępnianiem', - 'perm.actionHint.trip_create': 'Kto może tworzyć nowe podróże', - 'perm.actionHint.trip_edit': 'Kto może edytować szczegóły podróży', - 'perm.actionHint.trip_delete': 'Kto może usunąć podróż', - 'perm.actionHint.trip_archive': 'Kto może archiwizować podróż', - 'perm.actionHint.trip_cover_upload': 'Kto może zmieniać okładkę', - 'perm.actionHint.member_manage': 'Kto może zapraszać lub usuwać członków', - 'perm.actionHint.file_upload': 'Kto może przesyłać pliki', - 'perm.actionHint.file_edit': 'Kto może edytować pliki', - 'perm.actionHint.file_delete': 'Kto może usuwać pliki', - 'perm.actionHint.place_edit': 'Kto może zarządzać miejscami', - 'perm.actionHint.day_edit': 'Kto może edytować dni i przypisania', - 'perm.actionHint.reservation_edit': 'Kto może zarządzać rezerwacjami', - 'perm.actionHint.budget_edit': 'Kto może zarządzać budżetem', - 'perm.actionHint.packing_edit': 'Kto może zarządzać pakowaniem', - 'perm.actionHint.collab_edit': 'Kto może korzystać ze współpracy', - 'perm.actionHint.share_manage': 'Kto może zarządzać linkami', - 'undo.button': 'Cofnij', - 'undo.tooltip': 'Cofnij: {action}', - 'undo.assignPlace': 'Miejsce przypisane do dnia', - 'undo.removeAssignment': 'Miejsce usunięte z dnia', - 'undo.reorder': 'Kolejność zmieniona', - 'undo.optimize': 'Trasa zoptymalizowana', - 'undo.deletePlace': 'Miejsce usunięte', - 'undo.deletePlaces': 'Miejsca usunięte', - 'undo.moveDay': 'Miejsce przeniesione', - 'undo.lock': 'Blokada przełączona', - 'undo.importGpx': 'Import GPX', - 'undo.importKeyholeMarkup': 'Import KMZ/KML', - 'undo.importGoogleList': 'Import Google Maps', - 'undo.importNaverList': 'Import Naver Maps', - 'undo.addPlace': 'Miejsce dodane', - 'undo.done': 'Cofnięto: {action}', - 'notifications.title': 'Powiadomienia', - 'notifications.markAllRead': 'Oznacz wszystkie jako przeczytane', - 'notifications.deleteAll': 'Usuń wszystkie', - 'notifications.showAll': 'Pokaż wszystkie', - 'notifications.empty': 'Brak powiadomień', - 'notifications.emptyDescription': 'Jesteś na bieżąco!', - 'notifications.all': 'Wszystkie', - 'notifications.unreadOnly': 'Nieprzeczytane', - 'notifications.markRead': 'Oznacz jako przeczytane', - 'notifications.markUnread': 'Oznacz jako nieprzeczytane', - 'notifications.delete': 'Usuń', - 'notifications.system': 'System', - 'notifications.synologySessionCleared.title': 'Synology Photos rozłączone', - 'notifications.synologySessionCleared.text': 'Twój serwer lub konto zostało zmienione — przejdź do Ustawień, aby ponownie przetestować połączenie.', - 'notifications.versionAvailable.title': 'Dostępna aktualizacja', - 'notifications.versionAvailable.text': 'TREK {version} jest już dostępny.', - 'notifications.versionAvailable.button': 'Zobacz szczegóły', - 'notifications.test.title': 'Testowe powiadomienie od {actor}', - 'notifications.test.text': 'To jest powiadomienie testowe.', - 'notifications.test.booleanTitle': '{actor} prosi o akceptację', - 'notifications.test.booleanText': 'Testowe powiadomienie z wyborem.', - 'notifications.test.accept': 'Zatwierdź', - 'notifications.test.decline': 'Odrzuć', - 'notifications.test.navigateTitle': 'Sprawdź coś', - 'notifications.test.navigateText': 'Testowe powiadomienie nawigacyjne.', - 'notifications.test.goThere': 'Przejdź tam', - 'notifications.test.adminTitle': 'Komunikat administracyjny', - 'notifications.test.adminText': '{actor} wysłał testowe powiadomienie.', - 'notifications.test.tripTitle': '{actor} opublikował w Twojej podróży', - 'notifications.test.tripText': 'Testowe powiadomienie dla podróży "{trip}".', - - // Todo - 'todo.subtab.packing': 'Lista pakowania', - 'todo.subtab.todo': 'Do zrobienia', - 'todo.completed': 'ukończono', - 'todo.filter.all': 'Wszystkie', - 'todo.filter.open': 'Otwarte', - 'todo.filter.done': 'Gotowe', - 'todo.uncategorized': 'Bez kategorii', - 'todo.namePlaceholder': 'Nazwa zadania', - 'todo.descriptionPlaceholder': 'Opis (opcjonalnie)', - 'todo.unassigned': 'Nieprzypisane', - 'todo.noCategory': 'Brak kategorii', - 'todo.hasDescription': 'Ma opis', - 'todo.addItem': 'Nowe zadanie', - 'todo.sidebar.sortBy': 'Sortuj wg', - 'todo.priority': 'Priorytet', - 'todo.newCategoryLabel': 'nowa', - 'budget.categoriesLabel': 'kategorie', - 'todo.newCategory': 'Nazwa kategorii', - 'todo.addCategory': 'Dodaj kategorię', - 'todo.newItem': 'Nowe zadanie', - 'todo.empty': 'Brak zadań. Dodaj zadanie, aby zacząć!', - 'todo.filter.my': 'Moje zadania', - 'todo.filter.overdue': 'Przeterminowane', - 'todo.sidebar.tasks': 'Zadania', - 'todo.sidebar.categories': 'Kategorie', - 'todo.detail.title': 'Zadanie', - 'todo.detail.description': 'Opis', - 'todo.detail.category': 'Kategoria', - 'todo.detail.dueDate': 'Termin', - 'todo.detail.assignedTo': 'Przypisano do', - 'todo.detail.delete': 'Usuń', - 'todo.detail.save': 'Zapisz zmiany', - 'todo.detail.create': 'Utwórz zadanie', - 'todo.detail.priority': 'Priorytet', - 'todo.detail.noPriority': 'Brak', - 'todo.sortByPrio': 'Priorytet', - - // Notifications — dev test events - 'notif.test.title': '[Test] Powiadomienie', - 'notif.test.simple.text': 'To jest proste powiadomienie testowe.', - 'notif.test.boolean.text': 'Czy akceptujesz to powiadomienie testowe?', - 'notif.test.navigate.text': 'Kliknij poniżej, aby przejść do pulpitu.', - - // Notifications - 'notif.trip_invite.title': 'Zaproszenie do podróży', - 'notif.trip_invite.text': '{actor} zaprosił Cię do {trip}', - 'notif.booking_change.title': 'Rezerwacja zaktualizowana', - 'notif.booking_change.text': '{actor} zaktualizował rezerwację w {trip}', - 'notif.trip_reminder.title': 'Przypomnienie o podróży', - 'notif.trip_reminder.text': 'Twoja podróż {trip} zbliża się!', - 'notif.todo_due.title': 'Zadanie z terminem', - 'notif.todo_due.text': '{todo} w {trip} — termin {due}', - 'notif.vacay_invite.title': 'Zaproszenie Vacay Fusion', - 'notif.vacay_invite.text': '{actor} zaprosił Cię do połączenia planów urlopowych', - 'notif.photos_shared.title': 'Zdjęcia udostępnione', - 'notif.photos_shared.text': '{actor} udostępnił {count} zdjęcie/zdjęcia w {trip}', - 'notif.collab_message.title': 'Nowa wiadomość', - 'notif.collab_message.text': '{actor} wysłał wiadomość w {trip}', - 'notif.packing_tagged.title': 'Zadanie pakowania', - 'notif.packing_tagged.text': '{actor} przypisał Cię do {category} w {trip}', - 'notif.version_available.title': 'Nowa wersja dostępna', - 'notif.version_available.text': 'TREK {version} jest teraz dostępny', - 'notif.action.view_trip': 'Zobacz podróż', - 'notif.action.view_collab': 'Zobacz wiadomości', - 'notif.action.view_packing': 'Zobacz pakowanie', - 'notif.action.view_photos': 'Zobacz zdjęcia', - 'notif.action.view_vacay': 'Zobacz Vacay', - 'notif.action.view_admin': 'Przejdź do admina', - 'notif.action.view': 'Zobacz', - 'notif.action.accept': 'Akceptuj', - 'notif.action.decline': 'Odrzuć', - 'notif.generic.title': 'Powiadomienie', - 'notif.generic.text': 'Masz nowe powiadomienie', - 'notif.dev.unknown_event.title': '[DEV] Nieznane zdarzenie', - 'notif.dev.unknown_event.text': 'Typ zdarzenia "{event}" nie jest zarejestrowany w EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'przed chwilą', - 'common.hoursAgo': '{count} godz. temu', - 'common.daysAgo': '{count} dn. temu', - 'memories.saveRouteNotConfigured': 'Trasa zapisu nie jest skonfigurowana dla tego dostawcy', - 'memories.testRouteNotConfigured': 'Trasa testowa nie jest skonfigurowana dla tego dostawcy', - 'memories.fillRequiredFields': 'Proszę wypełnić wszystkie wymagane pola', - 'journey.search.placeholder': 'Szukaj podróży…', - 'journey.search.noResults': 'Brak podróży pasujących do „{query}"', - 'journey.title': 'Dziennik podróży', - 'journey.subtitle': 'Dokumentuj swoje podróże na bieżąco', - 'journey.new': 'Nowy dziennik podróży', - 'journey.create': 'Utwórz', - 'journey.titlePlaceholder': 'Dokąd jedziesz?', - 'journey.empty': 'Brak dzienników podróży', - 'journey.emptyHint': 'Zacznij dokumentować swoją następną podróż', - 'journey.deleted': 'Dziennik podróży usunięty', - 'journey.createError': 'Nie udało się utworzyć dziennika podróży', - 'journey.deleteError': 'Nie udało się usunąć dziennika podróży', - 'journey.deleteConfirmTitle': 'Usuń', - 'journey.deleteConfirmMessage': 'Usunąć „{title}"? Tej operacji nie można cofnąć.', - 'journey.deleteConfirmGeneric': 'Czy na pewno chcesz to usunąć?', - 'journey.notFound': 'Nie znaleziono dziennika podróży', - 'journey.photos': 'Zdjęcia', - 'journey.timelineEmpty': 'Brak przystanków', - 'journey.timelineEmptyHint': 'Dodaj zameldowanie lub napisz wpis w dzienniku, aby rozpocząć', - 'journey.status.draft': 'Szkic', - 'journey.status.active': 'Aktywny', - 'journey.status.completed': 'Zakończony', - 'journey.status.upcoming': 'Nadchodzący', - 'journey.status.archived': 'Zarchiwizowano', - 'journey.checkin.add': 'Zamelduj się', - 'journey.checkin.namePlaceholder': 'Nazwa miejsca', - 'journey.checkin.notesPlaceholder': 'Notatki (opcjonalnie)', - 'journey.checkin.save': 'Zapisz', - 'journey.checkin.error': 'Nie udało się zapisać zameldowania', - 'journey.entry.add': 'Dziennik', - 'journey.entry.edit': 'Edytuj wpis', - 'journey.entry.titlePlaceholder': 'Tytuł (opcjonalnie)', - 'journey.entry.bodyPlaceholder': 'Co się dziś wydarzyło?', - 'journey.entry.save': 'Zapisz', - 'journey.entry.error': 'Nie udało się zapisać wpisu', - 'journey.photo.add': 'Zdjęcie', - 'journey.photo.uploadError': 'Przesyłanie nie powiodło się', - 'journey.share.share': 'Udostępnij', - 'journey.share.public': 'Publiczny', - 'journey.share.linkCopied': 'Publiczny link skopiowany', - 'journey.share.disabled': 'Udostępnianie publiczne wyłączone', - 'journey.editor.titlePlaceholder': 'Nadaj temu momentowi nazwę...', - 'journey.editor.bodyPlaceholder': 'Opowiedz historię tego dnia...', - 'journey.editor.placePlaceholder': 'Lokalizacja (opcjonalnie)', - 'journey.editor.tagsPlaceholder': 'Tagi: ukryty skarb, najlepszy posiłek, warto wrócić...', - 'journey.visibility.private': 'Prywatny', - 'journey.visibility.shared': 'Udostępniony', - 'journey.visibility.public': 'Publiczny', - 'journey.emptyState.title': 'Twoja historia zaczyna się tutaj', - 'journey.emptyState.subtitle': 'Zamelduj się w miejscu lub napisz swój pierwszy wpis w dzienniku', - 'journey.frontpage.subtitle': 'Zamień swoje podróże w historie, których nigdy nie zapomnisz', - 'journey.frontpage.createJourney': 'Utwórz dziennik podróży', - 'journey.frontpage.activeJourney': 'Aktywny dziennik podróży', - 'journey.frontpage.allJourneys': 'Wszystkie dzienniki podróży', - 'journey.frontpage.journeys': 'dzienniki podróży', - 'journey.frontpage.createNew': 'Utwórz nowy dziennik podróży', - 'journey.frontpage.createNewSub': 'Wybierz podróże, pisz historie, dziel się przygodami', - 'journey.frontpage.live': 'Na żywo', - 'journey.frontpage.synced': 'Zsynchronizowany', - 'journey.frontpage.continueWriting': 'Kontynuuj pisanie', - 'journey.frontpage.updated': 'Zaktualizowano {time}', - 'journey.frontpage.suggestionLabel': 'Podróż właśnie się zakończyła', - 'journey.frontpage.suggestionText': 'Zamień {title} w dziennik podróży', - 'journey.frontpage.dismiss': 'Odrzuć', - 'journey.frontpage.journeyName': 'Nazwa dziennika podróży', - 'journey.frontpage.namePlaceholder': 'np. Azja Południowo-Wschodnia 2026', - 'journey.frontpage.selectTrips': 'Wybierz podróże', - 'journey.frontpage.tripsSelected': 'podróży wybranych', - 'journey.frontpage.trips': 'podróże', - 'journey.frontpage.placesImported': 'miejsc zostanie zaimportowanych', - 'journey.frontpage.places': 'miejsca', - 'journey.detail.backToJourney': 'Powrót do dziennika podróży', - 'journey.detail.syncedWithTrips': 'Zsynchronizowany z podróżami', - 'journey.detail.addEntry': 'Dodaj wpis', - 'journey.detail.newEntry': 'Nowy wpis', - 'journey.detail.editEntry': 'Edytuj wpis', - 'journey.detail.noEntries': 'Brak wpisów', - 'journey.detail.noEntriesHint': 'Dodaj podróż, aby rozpocząć ze szkieletowymi wpisami', - 'journey.detail.noPhotos': 'Brak zdjęć', - 'journey.detail.noPhotosHint': 'Prześlij zdjęcia do wpisów lub przeglądaj bibliotekę Immich/Synology', - 'journey.detail.journeyStats': 'Statystyki podróży', - 'journey.detail.syncedTrips': 'Zsynchronizowane podróże', - 'journey.detail.noTripsLinked': 'Brak powiązanych podróży', - 'journey.detail.contributors': 'Współtwórcy', - 'journey.detail.readMore': 'Czytaj dalej', - 'journey.detail.prosCons': 'Zalety i wady', - 'journey.detail.photos': 'zdjęć', - 'journey.detail.day': 'Dzień {number}', - 'journey.detail.places': 'miejsc', - 'journey.stats.days': 'Dni', - 'journey.stats.cities': 'Miasta', - 'journey.stats.entries': 'Wpisy', - 'journey.stats.photos': 'Zdjęcia', - 'journey.stats.places': 'Miejsca', - 'journey.skeletons.show': 'Pokaż sugestie', - 'journey.skeletons.hide': 'Ukryj sugestie', - 'journey.verdict.lovedIt': 'Świetne', - 'journey.verdict.couldBeBetter': 'Mogłoby być lepiej', - 'journey.synced.places': 'miejsca', - 'journey.synced.synced': 'zsynchronizowane', - 'journey.editor.discardChangesConfirm': 'Masz niezapisane zmiany. Odrzucić?', - 'journey.editor.uploadFailed': 'Przesyłanie zdjęć nie powiodło się', - 'journey.editor.uploadPhotos': 'Prześlij zdjęcia', - 'journey.editor.uploading': 'Przesyłanie...', - 'journey.editor.uploadingProgress': 'Przesyłanie {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} z {total} zdjęć nie powiodło się — zapisz ponownie, aby spróbować', - 'journey.editor.fromGallery': 'Z galerii', - 'journey.editor.allPhotosAdded': 'Wszystkie zdjęcia już dodane', - 'journey.editor.writeStory': 'Napisz swoją historię...', - 'journey.editor.prosCons': 'Zalety i wady', - 'journey.editor.pros': 'Zalety', - 'journey.editor.cons': 'Wady', - 'journey.editor.proPlaceholder': 'Coś świetnego...', - 'journey.editor.conPlaceholder': 'Nie tak świetne...', - 'journey.editor.addAnother': 'Dodaj kolejny', - 'journey.editor.date': 'Data', - 'journey.editor.location': 'Lokalizacja', - 'journey.editor.searchLocation': 'Szukaj lokalizacji...', - 'journey.editor.mood': 'Nastrój', - 'journey.editor.weather': 'Pogoda', - 'journey.editor.photoFirst': '1.', - 'journey.editor.makeFirst': 'Ustaw jako 1.', - 'journey.editor.searching': 'Szukanie...', - 'journey.mood.amazing': 'Niesamowity', - 'journey.mood.good': 'Dobry', - 'journey.mood.neutral': 'Neutralny', - 'journey.mood.rough': 'Ciężki', - 'journey.weather.sunny': 'Słonecznie', - 'journey.weather.partly': 'Częściowe zachmurzenie', - 'journey.weather.cloudy': 'Pochmurno', - 'journey.weather.rainy': 'Deszczowo', - 'journey.weather.stormy': 'Burzowo', - 'journey.weather.cold': 'Śnieżnie', - 'journey.trips.linkTrip': 'Powiąż podróż', - 'journey.trips.searchTrip': 'Szukaj podróży', - 'journey.trips.searchPlaceholder': 'Nazwa podróży lub cel...', - 'journey.trips.noTripsAvailable': 'Brak dostępnych podróży', - 'journey.trips.link': 'Powiąż', - 'journey.trips.tripLinked': 'Podróż powiązana', - 'journey.trips.linkFailed': 'Powiązanie podróży nie powiodło się', - 'journey.trips.addTrip': 'Dodaj podróż', - 'journey.trips.unlinkTrip': 'Odłącz podróż', - 'journey.trips.unlinkMessage': 'Odłączyć „{title}"? Wszystkie zsynchronizowane wpisy i zdjęcia z tej podróży zostaną trwale usunięte. Tej operacji nie można cofnąć.', - 'journey.trips.unlink': 'Odłącz', - 'journey.trips.tripUnlinked': 'Podróż odłączona', - 'journey.trips.unlinkFailed': 'Odłączenie podróży nie powiodło się', - 'journey.trips.noTripsLinkedSettings': 'Brak powiązanych podróży', - 'journey.contributors.invite': 'Zaproś współtwórcę', - 'journey.contributors.searchUser': 'Szukaj użytkownika', - 'journey.contributors.searchPlaceholder': 'Nazwa użytkownika lub e-mail...', - 'journey.contributors.noUsers': 'Nie znaleziono użytkowników', - 'journey.contributors.role': 'Rola', - 'journey.contributors.added': 'Współtwórca dodany', - 'journey.contributors.addFailed': 'Dodawanie współtwórcy nie powiodło się', - 'journey.share.publicShare': 'Udostępnianie publiczne', - 'journey.share.createLink': 'Utwórz link udostępniania', - 'journey.share.linkCreated': 'Link udostępniania utworzony', - 'journey.share.createFailed': 'Tworzenie linku nie powiodło się', - 'journey.share.copy': 'Kopiuj', - 'journey.share.copied': 'Skopiowano!', - 'journey.share.timeline': 'Oś czasu', - 'journey.share.gallery': 'Galeria', - 'journey.share.map': 'Mapa', - 'journey.share.removeLink': 'Usuń link udostępniania', - 'journey.share.linkDeleted': 'Link udostępniania usunięty', - 'journey.share.deleteFailed': 'Usunięcie nie powiodło się', - 'journey.share.updateFailed': 'Aktualizacja nie powiodła się', - - // Journey — Invite - 'journey.invite.role': 'Rola', - 'journey.invite.viewer': 'Obserwator', - 'journey.invite.editor': 'Redaktor', - 'journey.invite.invite': 'Zaproś', - 'journey.invite.inviting': 'Zapraszanie...', - 'journey.settings.title': 'Ustawienia dziennika podróży', - 'journey.settings.coverImage': 'Zdjęcie okładkowe', - 'journey.settings.changeCover': 'Zmień okładkę', - 'journey.settings.addCover': 'Dodaj zdjęcie okładkowe', - 'journey.settings.name': 'Nazwa', - 'journey.settings.subtitle': 'Podtytuł', - 'journey.settings.subtitlePlaceholder': 'np. Tajlandia, Wietnam i Kambodża', - 'journey.settings.endJourney': 'Archiwizuj podróż', - 'journey.settings.reopenJourney': 'Przywróć podróż', - 'journey.settings.archived': 'Podróż zarchiwizowana', - 'journey.settings.reopened': 'Podróż wznowiona', - 'journey.settings.endDescription': 'Ukrywa odznakę Na żywo. Możesz wznowić w dowolnym momencie.', - 'journey.settings.delete': 'Usuń', - 'journey.settings.deleteJourney': 'Usuń dziennik podróży', - 'journey.settings.deleteMessage': 'Usunąć „{title}"? Wszystkie wpisy i zdjęcia zostaną utracone.', - 'journey.settings.saved': 'Ustawienia zapisane', - 'journey.settings.saveFailed': 'Zapisywanie nie powiodło się', - 'journey.settings.coverUpdated': 'Okładka zaktualizowana', - 'journey.settings.coverFailed': 'Przesyłanie nie powiodło się', - 'journey.settings.failedToDelete': 'Nie udało się usunąć', - 'journey.entries.deleteTitle': 'Usuń wpis', - 'journey.photosUploaded': '{count} zdjęć przesłanych', - 'journey.photosUploadFailed': 'Nie udało się przesłać niektórych zdjęć', - 'journey.photosAdded': '{count} zdjęć dodanych', - 'journey.public.notFound': 'Nie znaleziono', - 'journey.public.notFoundMessage': 'Ten dziennik podróży nie istnieje lub link wygasł.', - 'journey.public.readOnly': 'Tylko do odczytu · Publiczny dziennik podróży', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Udostępnione przez', - 'journey.public.madeWith': 'Stworzone z', - 'journey.pdf.journeyBook': 'Książka podróży', - 'journey.pdf.madeWith': 'Stworzone z TREK', - 'journey.pdf.day': 'Dzień', - 'journey.pdf.theEnd': 'Koniec', - 'journey.pdf.saveAsPdf': 'Zapisz jako PDF', - 'journey.pdf.pages': 'stron', - 'journey.picker.tripPeriod': 'Okres podróży', - 'journey.picker.dateRange': 'Zakres dat', - 'journey.picker.allPhotos': 'Wszystkie zdjęcia', - 'journey.picker.albums': 'Albumy', - 'journey.picker.selected': 'wybranych', - 'journey.picker.addTo': 'Dodaj do', - 'journey.picker.newGallery': 'Nowa galeria', - 'journey.picker.selectAll': 'Zaznacz wszystko', - 'journey.picker.deselectAll': 'Odznacz wszystko', - 'journey.picker.noAlbums': 'Nie znaleziono albumów', - 'journey.picker.selectDate': 'Wybierz datę', - 'journey.picker.search': 'Szukaj', - 'dashboard.greeting.morning': 'Dzień dobry,', - 'dashboard.greeting.afternoon': 'Dzień dobry,', - 'dashboard.greeting.evening': 'Dobry wieczór,', - 'dashboard.mobile.liveNow': 'Na żywo', - 'dashboard.mobile.tripProgress': 'Postęp podróży', - 'dashboard.mobile.daysLeft': 'Pozostało {count} dni', - 'dashboard.mobile.places': 'Miejsca', - 'dashboard.mobile.buddies': 'Towarzysze', - 'dashboard.mobile.newTrip': 'Nowa podróż', - 'dashboard.mobile.currency': 'Waluta', - 'dashboard.mobile.timezone': 'Strefa czasowa', - 'dashboard.mobile.upcomingTrips': 'Nadchodzące podróże', - 'dashboard.mobile.yourTrips': 'Twoje podróże', - 'dashboard.mobile.trips': 'podróże', - 'dashboard.mobile.starts': 'Początek', - 'dashboard.mobile.duration': 'Czas trwania', - 'dashboard.mobile.day': 'dzień', - 'dashboard.mobile.days': 'dni', - 'dashboard.mobile.ongoing': 'W trakcie', - 'dashboard.mobile.startsToday': 'Zaczyna się dziś', - 'dashboard.mobile.tomorrow': 'Jutro', - 'dashboard.mobile.inDays': 'Za {count} dni', - 'dashboard.mobile.inMonths': 'Za {count} miesięcy', - 'dashboard.mobile.completed': 'Zakończone', - 'dashboard.mobile.currencyConverter': 'Przelicznik walut', - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Ustawienia', - 'nav.bottomAdmin': 'Ustawienia administratora', - 'nav.bottomLogout': 'Wyloguj się', - 'nav.bottomAdminBadge': 'Administrator', - 'dayplan.mobile.addPlace': 'Dodaj miejsce', - 'dayplan.mobile.searchPlaces': 'Szukaj miejsc...', - 'dayplan.mobile.allAssigned': 'Wszystkie miejsca przypisane', - 'dayplan.mobile.noMatch': 'Brak wyników', - 'dayplan.mobile.createNew': 'Utwórz nowe miejsce', - 'admin.addons.catalog.journey.name': 'Dziennik podróży', - 'admin.addons.catalog.journey.description': 'Śledzenie podróży i dziennik z zameldowaniami, zdjęciami i codziennymi historiami', - // OAuth scope groups - 'oauth.scope.group.trips': 'Podróże', - 'oauth.scope.group.places': 'Miejsca', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Pakowanie', - 'oauth.scope.group.todos': 'Zadania', - 'oauth.scope.group.budget': 'Budżet', - 'oauth.scope.group.reservations': 'Rezerwacje', - 'oauth.scope.group.collab': 'Współpraca', - 'oauth.scope.group.notifications': 'Powiadomienia', - 'oauth.scope.group.vacay': 'Urlop', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Pogoda', - 'oauth.scope.group.journey': 'Dziennik podróży', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Przeglądaj podróże i itineraria', - 'oauth.scope.trips:read.description': 'Odczytuj podróże, dni, notatki i członków', - 'oauth.scope.trips:write.label': 'Edytuj podróże i itineraria', - 'oauth.scope.trips:write.description': 'Twórz i aktualizuj podróże, dni, notatki oraz zarządzaj członkami', - 'oauth.scope.trips:delete.label': 'Usuń podróże', - 'oauth.scope.trips:delete.description': 'Trwale usuń całe podróże — ta akcja jest nieodwracalna', - 'oauth.scope.trips:share.label': 'Zarządzaj linkami udostępniania', - 'oauth.scope.trips:share.description': 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania', - 'oauth.scope.places:read.label': 'Przeglądaj miejsca i dane mapy', - 'oauth.scope.places:read.description': 'Odczytuj miejsca, przypisania dni, tagi i kategorie', - 'oauth.scope.places:write.label': 'Zarządzaj miejscami', - 'oauth.scope.places:write.description': 'Twórz, aktualizuj i usuń miejsca, przypisania i tagi', - 'oauth.scope.atlas:read.label': 'Przeglądaj Atlas', - 'oauth.scope.atlas:read.description': 'Odczytuj odwiedzone kraje, regiony i listę marzeń', - 'oauth.scope.atlas:write.label': 'Zarządzaj Atlasem', - 'oauth.scope.atlas:write.description': 'Oznaczaj kraje i regiony jako odwiedzone, zarządzaj listą marzeń', - 'oauth.scope.packing:read.label': 'Przeglądaj listy pakowania', - 'oauth.scope.packing:read.description': 'Odczytuj przedmioty, torby i przypisania kategorii', - 'oauth.scope.packing:write.label': 'Zarządzaj listami pakowania', - 'oauth.scope.packing:write.description': 'Dodawaj, aktualizuj, usuwaj, zaznaczaj i porządkuj przedmioty i torby', - 'oauth.scope.todos:read.label': 'Przeglądaj listy zadań', - 'oauth.scope.todos:read.description': 'Odczytuj zadania podróży i przypisania kategorii', - 'oauth.scope.todos:write.label': 'Zarządzaj listami zadań', - 'oauth.scope.todos:write.description': 'Twórz, aktualizuj, zaznaczaj, usuwaj i porządkuj zadania', - 'oauth.scope.budget:read.label': 'Przeglądaj budżet', - 'oauth.scope.budget:read.description': 'Odczytuj pozycje budżetu i zestawienie wydatków', - 'oauth.scope.budget:write.label': 'Zarządzaj budżetem', - 'oauth.scope.budget:write.description': 'Twórz, aktualizuj i usuń pozycje budżetu', - 'oauth.scope.reservations:read.label': 'Przeglądaj rezerwacje', - 'oauth.scope.reservations:read.description': 'Odczytuj rezerwacje i szczegóły zakwaterowania', - 'oauth.scope.reservations:write.label': 'Zarządzaj rezerwacjami', - 'oauth.scope.reservations:write.description': 'Twórz, aktualizuj, usuwaj i porządkuj rezerwacje', - 'oauth.scope.collab:read.label': 'Przeglądaj współpracę', - 'oauth.scope.collab:read.description': 'Odczytuj notatki, ankiety i wiadomości', - 'oauth.scope.collab:write.label': 'Zarządzaj współpracą', - 'oauth.scope.collab:write.description': 'Twórz, aktualizuj i usuń notatki, ankiety i wiadomości', - 'oauth.scope.notifications:read.label': 'Przeglądaj powiadomienia', - 'oauth.scope.notifications:read.description': 'Odczytuj powiadomienia i liczby nieprzeczytanych', - 'oauth.scope.notifications:write.label': 'Zarządzaj powiadomieniami', - 'oauth.scope.notifications:write.description': 'Oznaczaj powiadomienia jako przeczytane i odpowiadaj na nie', - 'oauth.scope.vacay:read.label': 'Przeglądaj plany urlopowe', - 'oauth.scope.vacay:read.description': 'Odczytuj dane planowania urlopu, wpisy i statystyki', - 'oauth.scope.vacay:write.label': 'Zarządzaj planami urlopowymi', - 'oauth.scope.vacay:write.description': 'Twórz i zarządzaj wpisami urlopowymi, świętami i planami zespołu', - 'oauth.scope.geo:read.label': 'Mapy i geokodowanie', - 'oauth.scope.geo:read.description': 'Wyszukuj miejsca, rozwiązuj adresy URL map i odwrotnie geokoduj współrzędne', - 'oauth.scope.weather:read.label': 'Prognozy pogody', - 'oauth.scope.weather:read.description': 'Pobieraj prognozy pogody dla miejsc i dat podróży', - 'oauth.scope.journey:read.label': 'Przeglądaj dzienniki podróży', - 'oauth.scope.journey:read.description': 'Odczytuj dzienniki podróży, wpisy i listę współautorów', - 'oauth.scope.journey:write.label': 'Zarządzaj dziennikami podróży', - 'oauth.scope.journey:write.description': 'Twórz, aktualizuj i usuwaj dzienniki podróży oraz ich wpisy', - 'oauth.scope.journey:share.label': 'Zarządzaj linkami dzienników podróży', - 'oauth.scope.journey:share.description': 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania dzienników podróży', - - // System notices - 'system_notice.welcome_v1.title': 'Witaj w TREK', - 'system_notice.welcome_v1.body': 'Twój kompleksowy planer podróży. Twórz trasy, dziel się wycieczkami ze znajomymi i bądź zorganizowany — online i offline.', - 'system_notice.welcome_v1.cta_label': 'Zaplanuj podróż', - 'system_notice.welcome_v1.hero_alt': 'Malownicze miejsce z interfejsem planowania TREK', - 'system_notice.welcome_v1.highlight_plan': 'Trasy dzień po dniu', - 'system_notice.welcome_v1.highlight_share': 'Współpraca z partnerami podróży', - 'system_notice.welcome_v1.highlight_offline': 'Działa offline na telefonie', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Poprzednie powiadomienie', - 'system_notice.pager.next': 'Następne powiadomienie', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Przejdź do powiadomienia {n}', - 'system_notice.pager.position': 'Powiadomienie {current} z {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Zdjęcia zostały przeniesione w 3.0', - 'system_notice.v3_photos.body': '**Zdjęcia** w Planerze Podróży zostały usunięte. Twoje zdjęcia są bezpieczne — TREK nigdy nie modyfikował Twojej biblioteki Immich lub Synology.\n\nZdjęcia są teraz dostępne w dodatku **Journey**. Journey jest opcjonalny — jeśli jeszcze nie jest dostępny, poproś administratora o jego włączenie w Admin → Dodatki.', - 'system_notice.v3_journey.title': 'Poznaj Journey — dziennik podróży', - 'system_notice.v3_journey.body': 'Dokumentuj swoje podróże jako bogatrze opowieści z osami czasu, galeriami i mapami interaktywnymi.', - 'system_notice.v3_journey.cta_label': 'Otwórz Journey', - 'system_notice.v3_journey.highlight_timeline': 'Dzienna oś czasu i galeria', - 'system_notice.v3_journey.highlight_photos': 'Import z Immich lub Synology', - 'system_notice.v3_journey.highlight_share': 'Udostępnij publicznie — bez logowania', - 'system_notice.v3_journey.highlight_export': 'Eksportuj jako książkę fotograficzną PDF', - 'system_notice.v3_features.title': 'Więcej nowości w 3.0', - 'system_notice.v3_features.body': 'Kilka innych rzeczy wartych uwagi w tym wydaniu.', - 'system_notice.v3_features.highlight_dashboard': 'Przeprojektowany pulpit mobile-first', - 'system_notice.v3_features.highlight_offline': 'Pełny tryb offline jako PWA', - 'system_notice.v3_features.highlight_search': 'Autouzupełnianie wyszukiwania miejsc', - 'system_notice.v3_features.highlight_import': 'Import miejsc z plików KMZ/KML', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: aktualizacja OAuth 2.1', - 'system_notice.v3_mcp.body': 'Integracja MCP została całkowicie przeprojektowana. OAuth 2.1 jest teraz zalecaną metodą uwierzytelniania. Statyczne tokeny (trek_…) są przestarzałe i zostaną usunięte w przyszłej wersji.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 zalecany (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 szczegółowe zakresy uprawnień', - 'system_notice.v3_mcp.highlight_deprecated': 'Statyczne tokeny trek_ przestarzałe', - 'system_notice.v3_mcp.highlight_tools': 'Rozszerzony zestaw narzędzi i promptów', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Osobiste słowo ode mnie', - 'system_notice.v3_thankyou.body': 'Zanim pójdziesz dalej — chcę się na chwilę zatrzymać.\n\nTREK zaczął się jako poboczny projekt, który zbudowałem na własne podróże. Nigdy nie wyobrażałem sobie, że wyrośnie na coś, czemu 4000 z was ufa przy planowaniu swoich przygód. Każda gwiazdka, każdy issue, każda prośba o funkcję — czytam je wszystkie i to one trzymają mnie na nogach podczas późnych nocy między pracą na pełny etat a uczelnią.\n\nChcę, żebyście wiedzieli: TREK zawsze będzie open source, zawsze self-hosted, zawsze wasz. Bez śledzenia, bez subskrypcji, bez haczyków. Po prostu narzędzie zbudowane przez kogoś, kto kocha podróżowanie tak samo jak wy.\n\nSzczególne podziękowania dla [jubnl](https://github.com/jubnl) — stałeś się niesamowitym współpracownikiem. Tak wiele z tego, co czyni wersję 3.0 wspaniałą, nosi twój ślad. Dziękuję, że uwierzyłeś w ten projekt, gdy był jeszcze surowy.\n\nI każdemu z was, kto zgłosił błąd, przetłumaczył tekst, podzielił się TREK z przyjacielem lub po prostu użył go do zaplanowania podróży — **dziękuję**. To wy jesteście powodem, dla którego to istnieje.\n\nZa wiele kolejnych wspólnych przygód.\n\n— Maurice\n\n---\n\n[Dołącz do społeczności na Discordzie](https://discord.gg/7Q6M6jDwzf)\n\nJeśli TREK sprawia, że Twoje podróże są lepsze, [mała kawa](https://ko-fi.com/mauriceboe) zawsze pomaga utrzymać światła włączone.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Wymagane działanie: konflikt konta użytkownika', - 'system_notice.v3014_whitespace_collision.body': 'Aktualizacja 3.0.14 wykryła jeden lub więcej konfliktów nazwy użytkownika lub adresu e-mail spowodowanych spacjami na początku lub końcu przechowywanych wartości. Dotknięte konta zostały automatycznie przemianowane. Sprawdź logi serwera pod kątem wierszy zaczynających się od **[migration] WHITESPACE COLLISION**, aby zidentyfikować konta wymagające przeglądu.', - 'transport.addTransport': 'Dodaj transport', - 'transport.modalTitle.create': 'Dodaj transport', - 'transport.modalTitle.edit': 'Edytuj transport', - 'transport.title': 'Transport', - 'transport.addManual': 'Ręczny transport', -} - -export default pl diff --git a/client/src/i18n/translations/ru.ts b/client/src/i18n/translations/ru.ts deleted file mode 100644 index 967f5d3a..00000000 --- a/client/src/i18n/translations/ru.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const ru: Record = { - // Common - 'common.save': 'Сохранить', - 'common.showMore': 'Показать больше', - 'common.showLess': 'Показать меньше', - 'common.cancel': 'Отмена', - 'common.clear': 'Очистить', - 'common.delete': 'Удалить', - 'common.edit': 'Редактировать', - 'common.add': 'Добавить', - 'common.loading': 'Загрузка...', - 'common.import': 'Импорт', - 'common.select': 'Выбрать', - 'common.selectAll': 'Выбрать всё', - 'common.deselectAll': 'Снять выделение со всех', - 'common.error': 'Ошибка', - 'common.unknownError': 'Неизвестная ошибка', - 'common.tooManyAttempts': 'Слишком много попыток. Попробуйте позже.', - 'common.back': 'Назад', - 'common.all': 'Все', - 'common.close': 'Закрыть', - 'common.open': 'Открыть', - 'common.upload': 'Загрузить', - 'common.search': 'Поиск', - 'common.confirm': 'Подтвердить', - 'common.ok': 'ОК', - 'common.yes': 'Да', - 'common.no': 'Нет', - 'common.or': 'или', - 'common.none': 'Нет', - 'common.date': 'Дата', - 'common.rename': 'Переименовать', - 'common.discardChanges': 'Отменить изменения', - 'common.discard': 'Отменить', - 'common.name': 'Имя', - 'common.email': 'Эл. почта', - 'common.password': 'Пароль', - 'common.saving': 'Сохранение...', - 'common.saved': 'Сохранено', - 'common.expand': 'Развернуть', - 'common.collapse': 'Свернуть', - 'trips.memberRemoved': '{username} удалён', - 'trips.memberRemoveError': 'Не удалось удалить', - 'trips.memberAdded': '{username} добавлен', - 'trips.memberAddError': 'Не удалось добавить', - 'trips.reminder': 'Напоминание', - 'trips.reminderNone': 'Нет', - 'trips.reminderDay': 'день', - 'trips.reminderDays': 'дней', - 'trips.reminderCustom': 'Другое', - 'trips.reminderDaysBefore': 'дней до отъезда', - 'trips.reminderDisabledHint': 'Напоминания о поездках отключены. Включите их в Админ > Настройки > Уведомления.', - 'common.update': 'Обновить', - 'common.change': 'Изменить', - 'common.uploading': 'Загрузка…', - 'common.backToPlanning': 'Вернуться к планированию', - 'common.reset': 'Сбросить', - - // Navbar - 'nav.trip': 'Поездка', - 'nav.share': 'Поделиться', - 'nav.settings': 'Настройки', - 'nav.admin': 'Админ', - 'nav.logout': 'Выйти', - 'nav.lightMode': 'Светлая тема', - 'nav.darkMode': 'Тёмная тема', - 'nav.autoMode': 'Авто', - 'nav.administrator': 'Администратор', - - // Dashboard - 'dashboard.title': 'Мои поездки', - 'dashboard.subtitle.loading': 'Загрузка поездок...', - 'dashboard.subtitle.trips': '{count} поездок ({archived} в архиве)', - 'dashboard.subtitle.empty': 'Начните свою первую поездку', - 'dashboard.subtitle.activeOne': '{count} активная поездка', - 'dashboard.subtitle.activeMany': '{count} активных поездок', - 'dashboard.subtitle.archivedSuffix': ' · {count} в архиве', - 'dashboard.newTrip': 'Новая поездка', - 'dashboard.gridView': 'Плитка', - 'dashboard.listView': 'Список', - 'dashboard.currency': 'Валюта', - 'dashboard.timezone': 'Часовые пояса', - 'dashboard.localTime': 'Местное', - 'dashboard.timezoneCustomTitle': 'Свой часовой пояс', - 'dashboard.timezoneCustomLabelPlaceholder': 'Название (необязательно)', - 'dashboard.timezoneCustomTzPlaceholder': 'напр. America/New_York', - 'dashboard.timezoneCustomAdd': 'Добавить', - 'dashboard.timezoneCustomErrorEmpty': 'Введите идентификатор часового пояса', - 'dashboard.timezoneCustomErrorInvalid': 'Неверный часовой пояс. Используйте формат Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Уже добавлен', - 'dashboard.emptyTitle': 'Нет поездок', - 'dashboard.emptyText': 'Создайте свою первую поездку и начните планировать!', - 'dashboard.emptyButton': 'Создать первую поездку', - 'dashboard.nextTrip': 'Следующая поездка', - 'dashboard.shared': 'Общая', - 'dashboard.sharedBy': 'Поделился {name}', - 'dashboard.days': 'Дни', - 'dashboard.places': 'Места', - 'dashboard.members': 'Попутчики', - 'dashboard.archive': 'Архивировать', - 'dashboard.copyTrip': 'Копировать', - 'dashboard.copySuffix': 'копия', - 'dashboard.restore': 'Восстановить', - 'dashboard.archived': 'В архиве', - 'dashboard.status.ongoing': 'В процессе', - 'dashboard.status.today': 'Сегодня', - 'dashboard.status.tomorrow': 'Завтра', - 'dashboard.status.past': 'Прошло', - 'dashboard.status.daysLeft': 'осталось {count} дн.', - 'dashboard.toast.loadError': 'Не удалось загрузить поездки', - 'dashboard.toast.created': 'Поездка создана!', - 'dashboard.toast.createError': 'Не удалось создать поездку', - 'dashboard.toast.updated': 'Поездка обновлена!', - 'dashboard.toast.updateError': 'Не удалось обновить поездку', - 'dashboard.toast.deleted': 'Поездка удалена', - 'dashboard.toast.deleteError': 'Не удалось удалить поездку', - 'dashboard.toast.archived': 'Поездка архивирована', - 'dashboard.toast.archiveError': 'Не удалось архивировать поездку', - 'dashboard.toast.restored': 'Поездка восстановлена', - 'dashboard.toast.restoreError': 'Не удалось восстановить поездку', - 'dashboard.toast.copied': 'Поездка скопирована!', - 'dashboard.toast.copyError': 'Не удалось скопировать поездку', - 'dashboard.confirm.delete': 'Удалить поездку «{title}»? Все места и планы будут безвозвратно удалены.', - 'dashboard.editTrip': 'Редактировать поездку', - 'dashboard.createTrip': 'Создать новую поездку', - 'dashboard.tripTitle': 'Название', - 'dashboard.tripTitlePlaceholder': 'напр. Лето в Японии', - 'dashboard.tripDescription': 'Описание', - 'dashboard.tripDescriptionPlaceholder': 'О чём эта поездка?', - 'dashboard.startDate': 'Дата начала', - 'dashboard.endDate': 'Дата окончания', - 'dashboard.dayCount': 'Количество дней', - 'dashboard.dayCountHint': 'Сколько дней планировать, если даты поездки не указаны.', - 'dashboard.noDateHint': 'Дата не указана — будет создано 7 дней по умолчанию. Вы можете изменить это в любое время.', - 'dashboard.coverImage': 'Обложка', - 'dashboard.addCoverImage': 'Добавить обложку', - 'dashboard.addMembers': 'Попутчики', - 'dashboard.addMember': 'Добавить участника', - 'dashboard.coverSaved': 'Обложка сохранена', - 'dashboard.coverUploadError': 'Ошибка загрузки', - 'dashboard.coverRemoveError': 'Ошибка удаления', - 'dashboard.titleRequired': 'Название обязательно', - 'dashboard.endDateError': 'Дата окончания должна быть позже даты начала', - - // Settings - 'settings.title': 'Настройки', - 'settings.subtitle': 'Настройте свои персональные параметры', - 'settings.tabs.display': 'Дисплей', - 'settings.tabs.map': 'Карта', - 'settings.tabs.notifications': 'Уведомления', - 'settings.tabs.integrations': 'Интеграции', - 'settings.tabs.account': 'Аккаунт', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': 'О приложении', - 'settings.map': 'Карта', - 'settings.mapTemplate': 'Шаблон карты', - 'settings.mapTemplatePlaceholder.select': 'Выберите шаблон...', - 'settings.mapDefaultHint': 'Оставьте пустым для OpenStreetMap (по умолчанию)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL-шаблон для тайлов карты', - 'settings.mapProvider': 'Провайдер карты', - 'settings.mapProviderHint': 'Применяется к Trip Planner и Journey. Atlas всегда использует Leaflet.', - 'settings.mapLeafletSubtitle': 'Классические 2D, любые растровые тайлы', - 'settings.mapMapboxSubtitle': 'Векторные тайлы, 3D-здания и рельеф', - 'settings.mapExperimental': 'Экспериментально', - 'settings.mapMapboxToken': 'Токен доступа Mapbox', - 'settings.mapMapboxTokenHint': 'Публичный токен (pk.*) с', - 'settings.mapMapboxTokenLink': 'mapbox.com → Токены доступа', - 'settings.mapStyle': 'Стиль карты', - 'settings.mapStylePlaceholder': 'Выберите стиль Mapbox', - 'settings.mapStyleHint': 'Preset или собственный URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': '3D-здания и рельеф', - 'settings.map3dHint': 'Наклон + настоящие 3D-здания — работает со всеми стилями, включая спутник.', - 'settings.mapHighQuality': 'Режим высокого качества', - 'settings.mapHighQualityHint': 'Сглаживание + проекция глобуса для более чётких краёв и реалистичного вида мира.', - 'settings.mapHighQualityWarning': 'Может повлиять на производительность на слабых устройствах.', - 'settings.mapTipLabel': 'Совет:', - 'settings.mapTip': 'Зажмите правую кнопку мыши и перетащите, чтобы повернуть/наклонить карту. Клик средней кнопкой — добавить место (правая кнопка зарезервирована для вращения).', - 'settings.latitude': 'Широта', - 'settings.longitude': 'Долгота', - 'settings.saveMap': 'Сохранить карту', - 'settings.apiKeys': 'API-ключи', - 'settings.mapsKey': 'API-ключ Google Maps', - 'settings.mapsKeyHint': 'Для поиска мест. Требуется Places API (New). Получите на console.cloud.google.com', - 'settings.weatherKey': 'API-ключ OpenWeatherMap', - 'settings.weatherKeyHint': 'Для данных о погоде. Бесплатно на openweathermap.org/api', - 'settings.keyPlaceholder': 'Введите ключ...', - 'settings.configured': 'Настроено', - 'settings.saveKeys': 'Сохранить ключи', - 'settings.display': 'Отображение', - 'settings.colorMode': 'Цветовая схема', - 'settings.light': 'Светлая', - 'settings.dark': 'Тёмная', - 'settings.auto': 'Авто', - 'settings.language': 'Язык', - 'settings.temperature': 'Единица температуры', - 'settings.timeFormat': 'Формат времени', - 'settings.blurBookingCodes': 'Скрыть коды бронирования', - 'settings.notifications': 'Уведомления', - 'settings.notifyTripInvite': 'Приглашения в поездку', - 'settings.notifyBookingChange': 'Изменения бронирований', - 'settings.notifyTripReminder': 'Напоминания о поездке', - 'settings.notifyTodoDue': 'Задача к сроку', - 'settings.notifyVacayInvite': 'Приглашения слияния Vacay', - 'settings.notifyPhotosShared': 'Общие фото (Immich)', - 'settings.notifyCollabMessage': 'Сообщения чата (Collab)', - 'settings.notifyPackingTagged': 'Список вещей: назначения', - 'settings.notifyWebhook': 'Webhook-уведомления', - 'settings.notificationsDisabled': 'Уведомления не настроены. Попросите администратора включить уведомления по электронной почте или webhook.', - 'settings.notificationsActive': 'Активный канал', - 'settings.notificationsManagedByAdmin': 'События уведомлений настраиваются администратором.', - 'admin.notifications.title': 'Уведомления', - 'admin.notifications.hint': 'Выберите канал уведомлений. Одновременно может быть активен только один.', - 'admin.notifications.none': 'Отключено', - 'admin.notifications.email': 'Эл. почта (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Сохранить настройки уведомлений', - 'admin.notifications.saved': 'Настройки уведомлений сохранены', - 'admin.notifications.testWebhook': 'Отправить тестовый вебхук', - 'admin.notifications.testWebhookSuccess': 'Тестовый вебхук успешно отправлен', - 'admin.notifications.testWebhookFailed': 'Ошибка отправки тестового вебхука', - 'admin.smtp.title': 'Почта и уведомления', - 'admin.smtp.hint': 'Конфигурация SMTP для отправки уведомлений по электронной почте.', - 'admin.smtp.testButton': 'Отправить тестовое письмо', - 'admin.webhook.hint': 'Отправлять уведомления через внешний webhook (Discord, Slack и т.д.).', - 'admin.smtp.testSuccess': 'Тестовое письмо успешно отправлено', - 'admin.smtp.testFailed': 'Ошибка отправки тестового письма', - 'dayplan.icsTooltip': 'Экспорт календаря (ICS)', - 'share.linkTitle': 'Публичная ссылка', - 'share.linkHint': 'Создайте ссылку, по которой любой сможет просмотреть эту поездку без входа в систему. Только чтение — редактирование невозможно.', - 'share.createLink': 'Создать ссылку', - 'share.deleteLink': 'Удалить ссылку', - 'share.createError': 'Не удалось создать ссылку', - 'common.copy': 'Копировать', - 'common.copied': 'Скопировано', - 'share.permMap': 'Карта и план', - 'share.permBookings': 'Бронирования', - 'share.permPacking': 'Вещи', - 'shared.expired': 'Ссылка устарела или недействительна', - 'shared.expiredHint': 'Эта ссылка на поездку больше не активна.', - 'shared.readOnly': 'Режим только для чтения', - 'shared.tabPlan': 'План', - 'shared.tabBookings': 'Бронирования', - 'shared.tabPacking': 'Багаж', - 'shared.tabBudget': 'Бюджет', - 'shared.tabChat': 'Чат', - 'shared.days': 'дней', - 'shared.places': 'мест', - 'shared.other': 'Прочее', - 'shared.totalBudget': 'Общий бюджет', - 'shared.messages': 'сообщений', - 'shared.sharedVia': 'Поделено через', - 'shared.confirmed': 'Подтверждено', - 'shared.pending': 'Ожидает', - 'share.permBudget': 'Бюджет', - 'share.permCollab': 'Чат', - 'settings.on': 'Вкл.', - 'settings.off': 'Выкл.', - 'settings.mcp.title': 'Настройка MCP', - 'settings.mcp.endpoint': 'MCP-эндпоинт', - 'settings.mcp.clientConfig': 'Конфигурация клиента', - 'settings.mcp.clientConfigHint': 'Замените на API-токен из списка ниже. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Замените и на учётные данные из созданного выше клиента OAuth 2.1. При первом подключении mcp-remote откроет браузер для завершения авторизации. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).', - 'settings.mcp.copy': 'Копировать', - 'settings.mcp.copied': 'Скопировано!', - 'settings.mcp.apiTokens': 'API-токены', - 'settings.mcp.createToken': 'Создать токен', - 'settings.mcp.noTokens': 'Токенов пока нет. Создайте один для подключения MCP-клиентов.', - 'settings.mcp.tokenCreatedAt': 'Создан', - 'settings.mcp.tokenUsedAt': 'Использован', - 'settings.mcp.deleteTokenTitle': 'Удалить токен', - 'settings.mcp.deleteTokenMessage': 'Этот токен перестанет работать немедленно. Любой MCP-клиент, использующий его, потеряет доступ.', - 'settings.mcp.modal.createTitle': 'Создать API-токен', - 'settings.mcp.modal.tokenName': 'Название токена', - 'settings.mcp.modal.tokenNamePlaceholder': 'напр. Claude Desktop, Рабочий ноутбук', - 'settings.mcp.modal.creating': 'Создание…', - 'settings.mcp.modal.create': 'Создать токен', - 'settings.mcp.modal.createdTitle': 'Токен создан', - 'settings.mcp.modal.createdWarning': 'Этот токен будет показан только один раз. Скопируйте и сохраните его сейчас — восстановить его будет невозможно.', - 'settings.mcp.modal.done': 'Готово', - 'settings.mcp.toast.created': 'Токен создан', - 'settings.mcp.toast.createError': 'Не удалось создать токен', - 'settings.mcp.toast.deleted': 'Токен удалён', - 'settings.mcp.toast.deleteError': 'Не удалось удалить токен', - 'settings.mcp.apiTokensDeprecated': 'API-токены устарели и будут удалены в будущей версии. Пожалуйста, используйте клиенты OAuth 2.1.', - 'settings.oauth.clients': 'Клиенты OAuth 2.1', - 'settings.oauth.clientsHint': 'Зарегистрируйте клиенты OAuth 2.1, чтобы сторонние MCP-приложения (Claude Web, Cursor и др.) могли подключаться без статических токенов.', - 'settings.oauth.createClient': 'Новый клиент', - 'settings.oauth.noClients': 'Нет зарегистрированных клиентов OAuth.', - 'settings.oauth.clientId': 'ID клиента', - 'settings.oauth.clientSecret': 'Секрет клиента', - 'settings.oauth.deleteClient': 'Удалить клиента', - 'settings.oauth.deleteClientMessage': 'Этот клиент и все активные сессии будут удалены навсегда. Любое приложение, использующее его, немедленно потеряет доступ.', - 'settings.oauth.rotateSecret': 'Обновить секрет', - 'settings.oauth.rotateSecretMessage': 'Будет сгенерирован новый секрет клиента, а все существующие сессии будут немедленно аннулированы. Обновите приложение перед закрытием этого диалога.', - 'settings.oauth.rotateSecretConfirm': 'Обновить', - 'settings.oauth.rotateSecretConfirming': 'Обновление…', - 'settings.oauth.rotateSecretDoneTitle': 'Новый секрет сгенерирован', - 'settings.oauth.rotateSecretDoneWarning': 'Этот секрет отображается только один раз. Скопируйте его сейчас и обновите приложение — все предыдущие сессии были аннулированы.', - 'settings.oauth.activeSessions': 'Активные сессии OAuth', - 'settings.oauth.sessionScopes': 'Области доступа', - 'settings.oauth.sessionExpires': 'Истекает', - 'settings.oauth.revoke': 'Отозвать', - 'settings.oauth.revokeSession': 'Отозвать сессию', - 'settings.oauth.revokeSessionMessage': 'Это немедленно отзовёт доступ для данной сессии OAuth.', - 'settings.oauth.modal.createTitle': 'Зарегистрировать клиент OAuth', - 'settings.oauth.modal.presets': 'Быстрые настройки', - 'settings.oauth.modal.clientName': 'Название приложения', - 'settings.oauth.modal.clientNamePlaceholder': 'напр. Claude Web, Моё MCP-приложение', - 'settings.oauth.modal.redirectUris': 'URI перенаправления', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Один URI на строку. Требуется HTTPS (localhost исключён). Требуется точное совпадение.', - 'settings.oauth.modal.scopes': 'Разрешённые области доступа', - 'settings.oauth.modal.scopesHint': 'list_trips и get_trip_summary всегда доступны — область не требуется. Они помогают ИИ находить нужные ID поездок.', - 'settings.oauth.modal.selectAll': 'Выбрать все', - 'settings.oauth.modal.deselectAll': 'Снять выбор', - 'settings.oauth.modal.creating': 'Регистрация…', - 'settings.oauth.modal.create': 'Зарегистрировать клиента', - 'settings.oauth.modal.createdTitle': 'Клиент зарегистрирован', - 'settings.oauth.modal.createdWarning': 'Секрет клиента отображается только один раз. Скопируйте его сейчас — его нельзя будет восстановить.', - 'settings.oauth.toast.createError': 'Не удалось зарегистрировать клиент OAuth', - 'settings.oauth.toast.deleted': 'Клиент OAuth удалён', - 'settings.oauth.toast.deleteError': 'Не удалось удалить клиент OAuth', - 'settings.oauth.toast.revoked': 'Сессия отозвана', - 'settings.oauth.toast.revokeError': 'Не удалось отозвать сессию', - 'settings.oauth.toast.rotateError': 'Не удалось обновить секрет клиента', - 'settings.oauth.modal.machineClient': 'Машинный клиент (без входа через браузер)', - 'settings.oauth.modal.machineClientHint': 'Использует грант client_credentials — URI перенаправления не требуются. Токен выдаётся напрямую через client_id + client_secret и действует от вашего имени в пределах выбранных областей.', - 'settings.oauth.modal.machineClientUsage': 'Получить токен: POST /oauth/token с grant_type=client_credentials, client_id и client_secret. Без браузера, без токена обновления.', - 'settings.oauth.badge.machine': 'машинный', - 'settings.account': 'Аккаунт', - 'settings.about': 'О приложении', - 'settings.about.reportBug': 'Сообщить об ошибке', - 'settings.about.reportBugHint': 'Нашли проблему? Сообщите нам', - 'settings.about.featureRequest': 'Предложить функцию', - 'settings.about.featureRequestHint': 'Предложите новую функцию', - 'settings.about.wikiHint': 'Документация и руководства', - 'settings.about.supporters.badge': 'Ежемесячные спонсоры', - 'settings.about.supporters.title': 'Спутники TREK', - 'settings.about.supporters.subtitle': 'Пока ты планируешь следующий маршрут, эти люди планируют вместе со мной будущее TREK. Их ежемесячный взнос идёт напрямую в разработку и реально потраченные часы — чтобы TREK оставался Open Source.', - 'settings.about.supporters.since': 'спонсор с {date}', - 'settings.about.supporters.tierEmpty': 'Стань первым', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK — это самостоятельно размещаемый планировщик путешествий, который помогает организовать поездки от первой идеи до последнего воспоминания. Планирование по дням, бюджет, списки вещей, фото и многое другое — всё в одном месте, на вашем собственном сервере.', - 'settings.about.madeWith': 'Сделано с', - 'settings.about.madeBy': 'Морисом и растущим open-source сообществом.', - 'settings.username': 'Имя пользователя', - 'settings.email': 'Эл. почта', - 'settings.role': 'Роль', - 'settings.roleAdmin': 'Администратор', - 'settings.oidcLinked': 'Связан с', - 'settings.changePassword': 'Изменить пароль', - 'settings.mustChangePassword': 'Вы должны сменить пароль перед продолжением. Пожалуйста, установите новый пароль ниже.', - 'settings.currentPassword': 'Текущий пароль', - 'settings.currentPasswordRequired': 'Текущий пароль обязателен', - 'settings.newPassword': 'Новый пароль', - 'settings.confirmPassword': 'Подтвердите новый пароль', - 'settings.updatePassword': 'Обновить пароль', - 'settings.passwordRequired': 'Введите текущий и новый пароль', - 'settings.passwordTooShort': 'Пароль должен содержать не менее 8 символов', - 'settings.passwordMismatch': 'Пароли не совпадают', - 'settings.passwordWeak': 'Пароль должен содержать заглавные, строчные буквы, цифру и специальный символ', - 'settings.passwordChanged': 'Пароль успешно изменён', - 'settings.deleteAccount': 'Удалить аккаунт', - 'settings.deleteAccountTitle': 'Удалить ваш аккаунт?', - 'settings.deleteAccountWarning': 'Ваш аккаунт и все поездки, места и файлы будут безвозвратно удалены. Это действие нельзя отменить.', - 'settings.deleteAccountConfirm': 'Удалить безвозвратно', - 'settings.deleteBlockedTitle': 'Удаление невозможно', - 'settings.deleteBlockedMessage': 'Вы единственный администратор. Назначьте другого пользователя администратором перед удалением своего аккаунта.', - 'settings.roleUser': 'Пользователь', - 'settings.saveProfile': 'Сохранить профиль', - 'settings.mfa.title': 'Двухфакторная аутентификация (2FA)', - 'settings.mfa.description': 'Добавляет второй шаг при входе. Используйте приложение-аутентификатор (Google Authenticator, Authy и др.).', - 'settings.mfa.requiredByPolicy': 'Администратор требует двухфакторную аутентификацию. Настройте приложение-аутентификатор ниже, прежде чем продолжить.', - 'settings.mfa.backupTitle': 'Резервные коды', - 'settings.mfa.backupDescription': 'Используйте эти одноразовые коды, если потеряете доступ к приложению-аутентификатору.', - 'settings.mfa.backupWarning': 'Сохраните их сейчас. Каждый код можно использовать только один раз.', - 'settings.mfa.backupCopy': 'Скопировать коды', - 'settings.mfa.backupDownload': 'Скачать TXT', - 'settings.mfa.backupPrint': 'Печать / PDF', - 'settings.mfa.backupCopied': 'Резервные коды скопированы', - 'settings.mfa.enabled': '2FA включена для вашего аккаунта.', - 'settings.mfa.disabled': '2FA не включена.', - 'settings.mfa.setup': 'Настроить аутентификатор', - 'settings.mfa.scanQr': 'Отсканируйте QR-код приложением или введите ключ вручную.', - 'settings.mfa.secretLabel': 'Секретный ключ (ручной ввод)', - 'settings.mfa.codePlaceholder': '6-значный код', - 'settings.mfa.enable': 'Включить 2FA', - 'settings.mfa.cancelSetup': 'Отмена', - 'settings.mfa.disableTitle': 'Отключить 2FA', - 'settings.mfa.disableHint': 'Введите пароль аккаунта и текущий код из аутентификатора.', - 'settings.mfa.disable': 'Отключить 2FA', - 'settings.mfa.toastEnabled': 'Двухфакторная аутентификация включена', - 'settings.mfa.toastDisabled': 'Двухфакторная аутентификация отключена', - 'settings.mfa.demoBlocked': 'Недоступно в демо-режиме', - 'settings.toast.mapSaved': 'Настройки карты сохранены', - 'settings.toast.keysSaved': 'API-ключи сохранены', - 'settings.toast.displaySaved': 'Настройки отображения сохранены', - 'settings.toast.profileSaved': 'Профиль сохранён', - 'settings.uploadAvatar': 'Загрузить фото профиля', - 'settings.removeAvatar': 'Удалить фото профиля', - 'settings.avatarUploaded': 'Фото профиля обновлено', - 'settings.avatarRemoved': 'Фото профиля удалено', - 'settings.avatarError': 'Ошибка загрузки', - - // Login - 'login.error': 'Ошибка входа. Проверьте свои учётные данные.', - 'login.tagline': 'Ваши поездки.\nВаш план.', - 'login.description': 'Планируйте поездки совместно с интерактивными картами, бюджетами и синхронизацией в реальном времени.', - 'login.features.maps': 'Интерактивные карты', - 'login.features.mapsDesc': 'Google Places, маршруты и кластеризация', - 'login.features.realtime': 'Синхронизация в реальном времени', - 'login.features.realtimeDesc': 'Планируйте вместе через WebSocket', - 'login.features.budget': 'Контроль бюджета', - 'login.features.budgetDesc': 'Категории, графики и расходы на человека', - 'login.features.collab': 'Совместная работа', - 'login.features.collabDesc': 'Многопользовательский режим с общими поездками', - 'login.features.packing': 'Списки вещей', - 'login.features.packingDesc': 'Категории, прогресс и подсказки', - 'login.features.bookings': 'Бронирования', - 'login.features.bookingsDesc': 'Авиабилеты, отели, рестораны и многое другое', - 'login.features.files': 'Документы', - 'login.features.filesDesc': 'Загружайте и управляйте документами', - 'login.features.routes': 'Умные маршруты', - 'login.features.routesDesc': 'Автооптимизация и экспорт в Google Maps', - 'login.selfHosted': 'Самостоятельный хостинг · Открытый код · Ваши данные остаются у вас', - 'login.title': 'Вход', - 'login.subtitle': 'С возвращением', - 'login.signingIn': 'Вход…', - 'login.signIn': 'Войти', - 'login.createAdmin': 'Создать аккаунт администратора', - 'login.createAdminHint': 'Настройте первый аккаунт администратора для TREK.', - 'login.setNewPassword': 'Установить новый пароль', - 'login.setNewPasswordHint': 'Вы должны сменить пароль, прежде чем продолжить.', - 'login.createAccount': 'Создать аккаунт', - 'login.createAccountHint': 'Зарегистрируйте новый аккаунт.', - 'login.creating': 'Создание…', - 'login.noAccount': 'Нет аккаунта?', - 'login.hasAccount': 'Уже есть аккаунт?', - 'login.register': 'Регистрация', - 'login.emailPlaceholder': 'ваш@email.com', - 'login.username': 'Имя пользователя', - 'login.oidc.registrationDisabled': 'Регистрация отключена. Обратитесь к администратору.', - 'login.oidc.noEmail': 'Провайдер не предоставил адрес эл. почты.', - 'login.mfaTitle': 'Двухфакторная аутентификация', - 'login.mfaSubtitle': 'Введите 6-значный код из приложения-аутентификатора.', - 'login.mfaCodeLabel': 'Код подтверждения', - 'login.mfaCodeRequired': 'Введите код из приложения-аутентификатора.', - 'login.mfaHint': 'Откройте Google Authenticator, Authy или другое TOTP-приложение.', - 'login.mfaBack': '← Назад к входу', - 'login.mfaVerify': 'Подтвердить', - 'login.invalidInviteLink': 'Недействительная или истёкшая ссылка-приглашение', - 'login.oidcFailed': 'Ошибка входа через OIDC', - 'login.usernameRequired': 'Имя пользователя обязательно', - 'login.passwordMinLength': 'Пароль должен содержать не менее 8 символов', - 'login.forgotPassword': 'Забыли пароль?', - 'login.forgotPasswordTitle': 'Сброс пароля', - 'login.forgotPasswordBody': 'Введите e-mail, с которым вы регистрировались. Если аккаунт найдём — отправим ссылку для сброса.', - 'login.forgotPasswordSubmit': 'Отправить ссылку', - 'login.forgotPasswordSentTitle': 'Проверьте почту', - 'login.forgotPasswordSentBody': 'Если аккаунт существует, ссылка для сброса уже летит к вам. Она действительна 60 минут.', - 'login.forgotPasswordSmtpHintOff': 'Обратите внимание: администратор не настроил SMTP, поэтому ссылка для сброса будет записана в консоль сервера, а не отправлена по почте.', - 'login.backToLogin': 'Вернуться ко входу', - 'login.newPassword': 'Новый пароль', - 'login.confirmPassword': 'Подтвердите новый пароль', - 'login.passwordsDontMatch': 'Пароли не совпадают', - 'login.mfaCode': 'Код 2FA', - 'login.resetPasswordTitle': 'Задайте новый пароль', - 'login.resetPasswordBody': 'Выберите надёжный пароль, который вы здесь ещё не использовали. Минимум 8 символов.', - 'login.resetPasswordMfaBody': 'Введите код 2FA или резервный код, чтобы завершить сброс.', - 'login.resetPasswordSubmit': 'Сбросить пароль', - 'login.resetPasswordVerify': 'Проверить и сбросить', - 'login.resetPasswordSuccessTitle': 'Пароль обновлён', - 'login.resetPasswordSuccessBody': 'Теперь вы можете войти с новым паролем.', - 'login.resetPasswordInvalidLink': 'Неверная ссылка сброса', - 'login.resetPasswordInvalidLinkBody': 'Ссылка отсутствует или повреждена. Запросите новую, чтобы продолжить.', - 'login.resetPasswordFailed': 'Сброс не удался. Возможно, срок действия ссылки истёк.', - 'login.oidc.tokenFailed': 'Аутентификация не удалась.', - 'login.oidc.invalidState': 'Недействительная сессия. Попробуйте снова.', - 'login.demoFailed': 'Ошибка демо-входа', - 'login.oidcSignIn': 'Войти через {name}', - 'login.oidcOnly': 'Вход по паролю отключён. Используйте вашего провайдера SSO для входа.', - 'login.oidcLoggedOut': 'Вы вышли из системы. Войдите снова через вашего провайдера SSO.', - 'login.demoHint': 'Попробуйте демо — регистрация не требуется', - - // Register - 'register.passwordMismatch': 'Пароли не совпадают', - 'register.passwordTooShort': 'Пароль должен содержать не менее 8 символов', - 'register.failed': 'Ошибка регистрации', - 'register.getStarted': 'Начать', - 'register.subtitle': 'Создайте аккаунт и начните планировать поездки мечты.', - 'register.feature1': 'Неограниченные планы поездок', - 'register.feature2': 'Интерактивная карта', - 'register.feature3': 'Управление местами и категориями', - 'register.feature4': 'Отслеживание бронирований', - 'register.feature5': 'Создание списков вещей', - 'register.feature6': 'Хранение фото и файлов', - 'register.createAccount': 'Создать аккаунт', - 'register.startPlanning': 'Начните планировать свои поездки', - 'register.minChars': 'Мин. 6 символов', - 'register.confirmPassword': 'Подтвердите пароль', - 'register.repeatPassword': 'Повторите пароль', - 'register.registering': 'Регистрация...', - 'register.register': 'Зарегистрироваться', - 'register.hasAccount': 'Уже есть аккаунт?', - 'register.signIn': 'Войти', - - // Admin - 'admin.title': 'Администрирование', - 'admin.subtitle': 'Управление пользователями и системные настройки', - 'admin.tabs.users': 'Пользователи', - 'admin.tabs.categories': 'Категории', - 'admin.tabs.backup': 'Резервная копия', - 'admin.tabs.audit': 'Аудит', - 'admin.stats.users': 'Пользователи', - 'admin.stats.trips': 'Поездки', - 'admin.stats.places': 'Места', - 'admin.stats.photos': 'Фото', - 'admin.stats.files': 'Файлы', - 'admin.table.user': 'Пользователь', - 'admin.table.email': 'Эл. почта', - 'admin.table.role': 'Роль', - 'admin.table.created': 'Создан', - 'admin.table.lastLogin': 'Последний вход', - 'admin.table.actions': 'Действия', - 'admin.you': '(Вы)', - 'admin.editUser': 'Редактировать пользователя', - 'admin.newPassword': 'Новый пароль', - 'admin.newPasswordHint': 'Оставьте пустым, чтобы сохранить текущий пароль', - 'admin.deleteUser': 'Удалить пользователя «{name}»? Все поездки будут безвозвратно удалены.', - 'admin.deleteUserTitle': 'Удалить пользователя', - 'admin.newPasswordPlaceholder': 'Введите новый пароль…', - 'admin.toast.loadError': 'Не удалось загрузить данные администрирования', - 'admin.toast.userUpdated': 'Пользователь обновлён', - 'admin.toast.updateError': 'Ошибка обновления', - 'admin.toast.userDeleted': 'Пользователь удалён', - 'admin.toast.deleteError': 'Ошибка удаления', - 'admin.toast.cannotDeleteSelf': 'Нельзя удалить собственный аккаунт', - 'admin.toast.userCreated': 'Пользователь создан', - 'admin.toast.createError': 'Ошибка создания пользователя', - 'admin.toast.fieldsRequired': 'Имя пользователя, эл. почта и пароль обязательны', - 'admin.createUser': 'Создать пользователя', - 'admin.invite.title': 'Ссылки-приглашения', - 'admin.invite.subtitle': 'Создание одноразовых ссылок для регистрации', - 'admin.invite.create': 'Создать ссылку', - 'admin.invite.createAndCopy': 'Создать и скопировать', - 'admin.invite.empty': 'Ссылки-приглашения ещё не созданы', - 'admin.invite.maxUses': 'Макс. использований', - 'admin.invite.expiry': 'Действует', - 'admin.invite.uses': 'использовано', - 'admin.invite.expiresAt': 'истекает', - 'admin.invite.createdBy': 'от', - 'admin.invite.active': 'Активна', - 'admin.invite.expired': 'Истекла', - 'admin.invite.usedUp': 'Исчерпана', - 'admin.invite.copied': 'Ссылка-приглашение скопирована', - 'admin.invite.copyLink': 'Копировать ссылку', - 'admin.invite.deleted': 'Ссылка-приглашение удалена', - 'admin.invite.createError': 'Ошибка при создании ссылки', - 'admin.invite.deleteError': 'Ошибка при удалении ссылки', - 'admin.tabs.settings': 'Настройки', - 'admin.allowRegistration': 'Разрешить регистрацию', - 'admin.allowRegistrationHint': 'Новые пользователи могут регистрироваться самостоятельно', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Требовать двухфакторную аутентификацию (2FA)', - 'admin.requireMfaHint': 'Пользователи без 2FA должны завершить настройку в разделе «Настройки» перед использованием приложения.', - 'admin.apiKeys': 'API-ключи', - 'admin.apiKeysHint': 'Необязательно. Включает расширенные данные о местах, такие как фото и погода.', - 'admin.mapsKey': 'API-ключ Google Maps', - 'admin.mapsKeyHint': 'Необходим для поиска мест. Получите на console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Без API-ключа используется OpenStreetMap для поиска мест. С ключом Google API можно загружать фото, рейтинги и часы работы. Получите ключ на console.cloud.google.com.', - 'admin.recommended': 'Рекомендуется', - 'admin.weatherKey': 'API-ключ OpenWeatherMap', - 'admin.weatherKeyHint': 'Для данных о погоде. Бесплатно на openweathermap.org', - 'admin.validateKey': 'Проверить', - 'admin.keyValid': 'Подключено', - 'admin.keyInvalid': 'Недействителен', - 'admin.keySaved': 'API-ключи сохранены', - 'admin.oidcTitle': 'Единый вход (OIDC)', - 'admin.oidcSubtitle': 'Разрешить вход через внешних провайдеров, таких как Google, Apple, Authentik или Keycloak.', - 'admin.oidcDisplayName': 'Отображаемое имя', - 'admin.oidcIssuer': 'URL издателя', - 'admin.oidcIssuerHint': 'URL издателя OpenID Connect провайдера. Напр. https://accounts.google.com', - 'admin.oidcSaved': 'Конфигурация OIDC сохранена', - 'admin.oidcOnlyMode': 'Отключить вход по паролю', - 'admin.oidcOnlyModeHint': 'При включении разрешён только вход через SSO. Вход и регистрация по паролю будут заблокированы.', - - // File Types - 'admin.fileTypes': 'Разрешённые типы файлов', - 'admin.fileTypesHint': 'Настройте, какие типы файлов могут загружать пользователи.', - 'admin.fileTypesFormat': 'Расширения через запятую (напр. jpg,png,pdf,doc). Используйте * для разрешения всех типов.', - 'admin.fileTypesSaved': 'Настройки типов файлов сохранены', - - 'admin.placesPhotos.title': 'Фотографии мест', - 'admin.placesPhotos.subtitle': 'Загрузка фотографий из Google Places API. Отключите для экономии квоты API. Фотографии Wikimedia не затронуты.', - 'admin.placesAutocomplete.title': 'Автодополнение мест', - 'admin.placesAutocomplete.subtitle': 'Использование Google Places API для поисковых подсказок. Отключите для экономии квоты API.', - 'admin.placesDetails.title': 'Сведения о месте', - 'admin.placesDetails.subtitle': 'Загрузка подробной информации о месте (часы работы, рейтинг, веб-сайт) из Google Places API. Отключите для экономии квоты API.', - 'admin.bagTracking.title': 'Отслеживание багажа', - 'admin.bagTracking.subtitle': 'Включить вес и привязку к багажу для вещей', - 'admin.collab.chat.title': 'Чат', - 'admin.collab.chat.subtitle': 'Обмен сообщениями для совместной работы', - 'admin.collab.notes.title': 'Заметки', - 'admin.collab.notes.subtitle': 'Общие заметки и документы', - 'admin.collab.polls.title': 'Опросы', - 'admin.collab.polls.subtitle': 'Групповые опросы и голосования', - 'admin.collab.whatsnext.title': 'Что дальше', - 'admin.collab.whatsnext.subtitle': 'Предложения активностей и следующие шаги', - 'admin.tabs.config': 'Персонализация', - 'admin.tabs.defaults': 'Настройки по умолчанию', - 'admin.defaultSettings.title': 'Настройки пользователей по умолчанию', - 'admin.defaultSettings.description': 'Задайте значения по умолчанию для всего экземпляра. Пользователи, не изменившие параметр, увидят эти значения. Их собственные изменения всегда имеют приоритет.', - 'admin.defaultSettings.saved': 'Значение по умолчанию сохранено', - 'admin.defaultSettings.reset': 'Сбросить до встроенного значения', - 'admin.defaultSettings.resetToBuiltIn': 'сбросить', - 'admin.tabs.templates': 'Шаблоны упаковки', - 'admin.packingTemplates.title': 'Шаблоны упаковки', - 'admin.packingTemplates.subtitle': 'Создавайте многоразовые списки вещей для поездок', - 'admin.packingTemplates.create': 'Новый шаблон', - 'admin.packingTemplates.namePlaceholder': 'Название шаблона (напр. Пляжный отдых)', - 'admin.packingTemplates.empty': 'Шаблоны ещё не созданы', - 'admin.packingTemplates.items': 'вещей', - 'admin.packingTemplates.categories': 'категорий', - 'admin.packingTemplates.itemName': 'Название вещи', - 'admin.packingTemplates.itemCategory': 'Категория', - 'admin.packingTemplates.categoryName': 'Название категории (напр. Одежда)', - 'admin.packingTemplates.addCategory': 'Добавить категорию', - 'admin.packingTemplates.created': 'Шаблон создан', - 'admin.packingTemplates.deleted': 'Шаблон удалён', - 'admin.packingTemplates.loadError': 'Ошибка загрузки шаблонов', - 'admin.packingTemplates.createError': 'Ошибка создания шаблона', - 'admin.packingTemplates.deleteError': 'Ошибка удаления шаблона', - 'admin.packingTemplates.saveError': 'Ошибка сохранения', - - // Addons - 'admin.tabs.addons': 'Дополнения', - 'admin.addons.title': 'Дополнения', - 'admin.addons.subtitle': 'Включайте или отключайте функции для настройки TREK под себя.', - 'admin.addons.catalog.memories.name': 'Фото (Immich)', - 'admin.addons.catalog.memories.description': 'Делитесь фотографиями из поездок через Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Протокол контекста модели для интеграции с ИИ-ассистентами', - 'admin.addons.catalog.packing.name': 'Списки', - 'admin.addons.catalog.packing.description': 'Списки вещей и задачи для ваших поездок', - 'admin.addons.catalog.budget.name': 'Бюджет', - 'admin.addons.catalog.budget.description': 'Отслеживайте расходы и планируйте бюджет поездки', - 'admin.addons.catalog.documents.name': 'Документы', - 'admin.addons.catalog.documents.description': 'Храните и управляйте документами для путешествий', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Личный планировщик отпусков с календарём', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Карта мира с посещёнными странами и статистикой путешествий', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Заметки в реальном времени, опросы и чат для планирования поездок', - 'admin.addons.subtitleBefore': 'Включайте или отключайте функции для настройки ', - 'admin.addons.subtitleAfter': ' под себя.', - 'admin.addons.enabled': 'Включено', - 'admin.addons.disabled': 'Отключено', - 'admin.addons.type.trip': 'Поездка', - 'admin.addons.type.global': 'Глобально', - 'admin.addons.type.integration': 'Интеграция', - 'admin.addons.tripHint': 'Доступно как вкладка внутри каждой поездки', - 'admin.addons.globalHint': 'Доступно как отдельный раздел в основной навигации', - 'admin.addons.integrationHint': 'Фоновые сервисы и API-интеграции без отдельной страницы', - 'admin.addons.toast.updated': 'Дополнение обновлено', - 'admin.addons.toast.error': 'Не удалось обновить дополнение', - 'admin.addons.noAddons': 'Нет доступных дополнений', - // Weather info - 'admin.weather.title': 'Данные о погоде', - 'admin.weather.badge': 'С 24 марта 2026', - 'admin.weather.description': 'TREK использует Open-Meteo как источник данных о погоде. Open-Meteo — бесплатный сервис с открытым кодом, API-ключ не требуется.', - 'admin.weather.forecast': 'Прогноз на 16 дней', - 'admin.weather.forecastDesc': 'Ранее 5 дней (OpenWeatherMap)', - 'admin.weather.climate': 'Исторические климатические данные', - 'admin.weather.climateDesc': 'Средние значения за последние 85 лет для дней за пределами 16-дневного прогноза', - 'admin.weather.requests': '10 000 запросов / день', - 'admin.weather.requestsDesc': 'Бесплатно, API-ключ не требуется', - 'admin.weather.locationHint': 'Погода основана на первом месте с координатами в каждом дне. Если ни одно место не назначено на день, в качестве ориентира используется любое место из списка.', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP-доступ', - 'admin.mcpTokens.title': 'MCP-доступ', - 'admin.mcpTokens.subtitle': 'Управление OAuth-сессиями и API-токенами всех пользователей', - 'admin.mcpTokens.sectionTitle': 'API-токены', - 'admin.mcpTokens.owner': 'Владелец', - 'admin.mcpTokens.tokenName': 'Название токена', - 'admin.mcpTokens.created': 'Создан', - 'admin.mcpTokens.lastUsed': 'Последнее использование', - 'admin.mcpTokens.never': 'Никогда', - 'admin.mcpTokens.empty': 'MCP-токены ещё не созданы', - 'admin.mcpTokens.deleteTitle': 'Удалить токен', - 'admin.mcpTokens.deleteMessage': 'Токен будет немедленно отозван. Пользователь потеряет доступ к MCP через этот токен.', - 'admin.mcpTokens.deleteSuccess': 'Токен удалён', - 'admin.mcpTokens.deleteError': 'Не удалось удалить токен', - 'admin.mcpTokens.loadError': 'Не удалось загрузить токены', - 'admin.oauthSessions.sectionTitle': 'OAuth-сессии', - 'admin.oauthSessions.clientName': 'Клиент', - 'admin.oauthSessions.owner': 'Владелец', - 'admin.oauthSessions.scopes': 'Права доступа', - 'admin.oauthSessions.created': 'Создано', - 'admin.oauthSessions.empty': 'Нет активных OAuth-сессий', - 'admin.oauthSessions.revokeTitle': 'Отозвать сессию', - 'admin.oauthSessions.revokeMessage': 'Эта OAuth-сессия будет немедленно отозвана. Клиент потеряет доступ к MCP.', - 'admin.oauthSessions.revokeSuccess': 'Сессия отозвана', - 'admin.oauthSessions.revokeError': 'Не удалось отозвать сессию', - 'admin.oauthSessions.loadError': 'Не удалось загрузить OAuth-сессии', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'События, связанные с безопасностью и администрированием (резервные копии, пользователи, MFA, настройки).', - 'admin.audit.empty': 'Записей аудита пока нет.', - 'admin.audit.refresh': 'Обновить', - 'admin.audit.loadMore': 'Загрузить ещё', - 'admin.audit.showing': 'Загружено: {count} · всего {total}', - 'admin.audit.col.time': 'Время', - 'admin.audit.col.user': 'Пользователь', - 'admin.audit.col.action': 'Действие', - 'admin.audit.col.resource': 'Объект', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Подробности', - - 'admin.github.title': 'История релизов', - 'admin.github.subtitle': 'Последние обновления из {repo}', - 'admin.github.latest': 'Последний', - 'admin.github.prerelease': 'Пре-релиз', - 'admin.github.showDetails': 'Показать подробности', - 'admin.github.hideDetails': 'Скрыть подробности', - 'admin.github.loadMore': 'Загрузить ещё', - 'admin.github.loading': 'Загрузка...', - 'admin.github.support': 'Помогает продолжать разработку TREK', - 'admin.github.error': 'Не удалось загрузить релизы', - 'admin.github.by': 'от', - - 'admin.update.available': 'Доступно обновление', - 'admin.update.text': 'Доступна версия TREK {version}. У вас установлена {current}.', - 'admin.update.button': 'Посмотреть на GitHub', - 'admin.update.install': 'Установить обновление', - 'admin.update.confirmTitle': 'Установить обновление?', - 'admin.update.confirmText': 'TREK будет обновлён с {current} до {version}. Сервер перезапустится автоматически.', - 'admin.update.dataInfo': 'Все ваши данные (поездки, пользователи, API-ключи, загрузки, Vacay, Atlas, бюджеты) будут сохранены.', - 'admin.update.warning': 'Приложение будет кратковременно недоступно во время перезапуска.', - 'admin.update.confirm': 'Обновить сейчас', - 'admin.update.installing': 'Обновление…', - 'admin.update.success': 'Обновление установлено! Сервер перезапускается…', - 'admin.update.failed': 'Ошибка обновления', - 'admin.update.backupHint': 'Рекомендуем создать резервную копию перед обновлением.', - 'admin.update.backupLink': 'Перейти к резервным копиям', - 'admin.update.howTo': 'Как обновить', - 'admin.update.dockerText': 'Ваш экземпляр TREK работает в Docker. Для обновления до {version} выполните следующие команды на сервере:', - 'admin.update.reloadHint': 'Перезагрузите страницу через несколько секунд.', - - // Vacay addon - 'vacay.subtitle': 'Планируйте и управляйте днями отпуска', - 'vacay.settings': 'Настройки', - 'vacay.year': 'Год', - 'vacay.addYear': 'Добавить следующий год', - 'vacay.addPrevYear': 'Добавить предыдущий год', - 'vacay.removeYear': 'Удалить год', - 'vacay.removeYearConfirm': 'Удалить {year}?', - 'vacay.removeYearHint': 'Все записи об отпуске и корпоративные выходные за этот год будут безвозвратно удалены.', - 'vacay.remove': 'Удалить', - 'vacay.persons': 'Люди', - 'vacay.noPersons': 'Никто не добавлен', - 'vacay.addPerson': 'Добавить человека', - 'vacay.editPerson': 'Редактировать', - 'vacay.removePerson': 'Удалить человека', - 'vacay.removePersonConfirm': 'Удалить {name}?', - 'vacay.removePersonHint': 'Все записи об отпуске этого человека будут безвозвратно удалены.', - 'vacay.personName': 'Имя', - 'vacay.personNamePlaceholder': 'Введите имя', - 'vacay.color': 'Цвет', - 'vacay.add': 'Добавить', - 'vacay.legend': 'Легенда', - 'vacay.publicHoliday': 'Государственный праздник', - 'vacay.companyHoliday': 'Корпоративный выходной', - 'vacay.weekend': 'Выходные', - 'vacay.modeVacation': 'Отпуск', - 'vacay.modeCompany': 'Корпоративный выходной', - 'vacay.entitlement': 'Право на отпуск', - 'vacay.entitlementDays': 'Дни', - 'vacay.used': 'Использовано', - 'vacay.remaining': 'Осталось', - 'vacay.carriedOver': 'из {year}', - 'vacay.blockWeekends': 'Блокировать выходные', - 'vacay.blockWeekendsHint': 'Запретить записи об отпуске в субботу и воскресенье', - 'vacay.weekendDays': 'Выходные дни', - 'vacay.mon': 'Пн', - 'vacay.tue': 'Вт', - 'vacay.wed': 'Ср', - 'vacay.thu': 'Чт', - 'vacay.fri': 'Пт', - 'vacay.sat': 'Сб', - 'vacay.sun': 'Вс', - 'vacay.publicHolidays': 'Государственные праздники', - 'vacay.publicHolidaysHint': 'Отмечать государственные праздники в календаре', - 'vacay.selectCountry': 'Выберите страну', - 'vacay.selectRegion': 'Выберите регион (необязательно)', - 'vacay.companyHolidays': 'Корпоративные выходные', - 'vacay.companyHolidaysHint': 'Разрешить отмечать корпоративные выходные дни', - 'vacay.companyHolidaysNoDeduct': 'Корпоративные выходные не вычитаются из дней отпуска.', - 'vacay.weekStart': 'Неделя начинается с', - 'vacay.weekStartHint': 'Выберите, начинается ли неделя с понедельника или воскресенья', - 'vacay.carryOver': 'Перенос', - 'vacay.carryOverHint': 'Автоматически переносить оставшиеся дни отпуска на следующий год', - 'vacay.sharing': 'Общий доступ', - 'vacay.sharingHint': 'Поделитесь планом отпуска с другими пользователями TREK', - 'vacay.owner': 'Владелец', - 'vacay.shareEmailPlaceholder': 'Эл. почта пользователя TREK', - 'vacay.shareSuccess': 'План успешно предоставлен', - 'vacay.shareError': 'Не удалось поделиться планом', - 'vacay.dissolve': 'Разделить объединение', - 'vacay.dissolveHint': 'Снова разделить календари. Ваши записи будут сохранены.', - 'vacay.dissolveAction': 'Разделить', - 'vacay.dissolved': 'Календарь разделён', - 'vacay.fusedWith': 'Объединён с', - 'vacay.you': 'вы', - 'vacay.noData': 'Нет данных', - 'vacay.changeColor': 'Изменить цвет', - 'vacay.inviteUser': 'Пригласить пользователя', - 'vacay.inviteHint': 'Пригласите другого пользователя TREK для совместного календаря отпусков.', - 'vacay.selectUser': 'Выберите пользователя', - 'vacay.sendInvite': 'Отправить приглашение', - 'vacay.inviteSent': 'Приглашение отправлено', - 'vacay.inviteError': 'Не удалось отправить приглашение', - 'vacay.pending': 'ожидание', - 'vacay.noUsersAvailable': 'Нет доступных пользователей', - 'vacay.accept': 'Принять', - 'vacay.decline': 'Отклонить', - 'vacay.acceptFusion': 'Принять и объединить', - 'vacay.inviteTitle': 'Запрос на объединение', - 'vacay.inviteWantsToFuse': 'хочет объединить календарь отпусков с вами.', - 'vacay.fuseInfo1': 'Вы оба будете видеть все записи об отпуске в одном общем календаре.', - 'vacay.fuseInfo2': 'Обе стороны могут создавать и редактировать записи друг для друга.', - 'vacay.fuseInfo3': 'Обе стороны могут удалять записи и изменять право на отпуск.', - 'vacay.fuseInfo4': 'Настройки, такие как праздники и корпоративные выходные, становятся общими.', - 'vacay.fuseInfo5': 'Объединение можно отменить в любое время любой из сторон. Ваши записи будут сохранены.', - 'vacay.addCalendar': 'Добавить календарь', - 'vacay.calendarColor': 'Цвет', - 'vacay.calendarLabel': 'Название', - 'vacay.noCalendars': 'Нет календарей', - 'nav.myTrips': 'Мои поездки', - - // Atlas addon - 'atlas.subtitle': 'Ваш след путешествий по всему миру', - 'atlas.countries': 'Страны', - 'atlas.trips': 'Поездки', - 'atlas.places': 'Места', - 'atlas.days': 'Дни', - 'atlas.visitedCountries': 'Посещённые страны', - 'atlas.cities': 'Города', - 'atlas.noData': 'Данных о поездках пока нет', - 'atlas.noDataHint': 'Создайте поездку и добавьте места, чтобы увидеть карту мира', - 'atlas.lastTrip': 'Последняя поездка', - 'atlas.nextTrip': 'Следующая поездка', - 'atlas.daysLeft': 'дней осталось', - 'atlas.streak': 'Серия', - 'atlas.year': 'год', - 'atlas.years': 'лет', - 'atlas.yearInRow': 'год подряд', - 'atlas.yearsInRow': 'лет подряд', - 'atlas.tripIn': 'поездка в', - 'atlas.tripsIn': 'поездок в', - 'atlas.since': 'с', - 'atlas.europe': 'Европа', - 'atlas.asia': 'Азия', - 'atlas.northAmerica': 'Сев. Америка', - 'atlas.southAmerica': 'Юж. Америка', - 'atlas.africa': 'Африка', - 'atlas.oceania': 'Океания', - 'atlas.other': 'Другое', - 'atlas.firstVisit': 'Первая поездка', - 'atlas.lastVisitLabel': 'Последняя поездка', - 'atlas.tripSingular': 'Поездка', - 'atlas.tripPlural': 'Поездки', - 'atlas.placeVisited': 'Посещённое место', - 'atlas.placesVisited': 'Посещённые места', - 'atlas.statsTab': 'Статистика', - 'atlas.bucketTab': 'Список желаний', - 'atlas.addBucket': 'Добавить в список желаний', - 'atlas.bucketNamePlaceholder': 'Место или направление...', - 'atlas.bucketNotesPlaceholder': 'Заметки (необязательно)', - 'atlas.bucketEmpty': 'Ваш список желаний пуст', - 'atlas.bucketEmptyHint': 'Добавьте места, которые мечтаете посетить', - 'atlas.unmark': 'Удалить', - 'atlas.confirmMark': 'Отметить эту страну как посещённую?', - 'atlas.confirmUnmark': 'Удалить эту страну из списка посещённых?', - 'atlas.confirmUnmarkRegion': 'Удалить этот регион из списка посещённых?', - 'atlas.markVisited': 'Отметить как посещённую', - 'atlas.markVisitedHint': 'Добавить эту страну в список посещённых', - 'atlas.markRegionVisitedHint': 'Добавить этот регион в список посещённых', - 'atlas.addToBucket': 'В список желаний', - 'atlas.addPoi': 'Добавить место', - 'atlas.searchCountry': 'Поиск страны...', - 'atlas.month': 'Месяц', - 'atlas.addToBucketHint': 'Сохранить как место для посещения', - 'atlas.bucketWhen': 'Когда вы планируете поехать?', - - // Trip Planner - 'trip.tabs.plan': 'План', - 'trip.tabs.transports': 'Транспорт', - 'trip.tabs.reservations': 'Бронирования', - 'trip.tabs.reservationsShort': 'Брони', - 'trip.tabs.packing': 'Список вещей', - 'trip.tabs.packingShort': 'Вещи', - 'trip.tabs.lists': 'Списки', - 'trip.tabs.listsShort': 'Списки', - 'trip.tabs.budget': 'Бюджет', - 'trip.tabs.files': 'Файлы', - 'trip.loading': 'Загрузка поездки...', - 'trip.loadingPhotos': 'Загрузка фото мест...', - 'trip.mobilePlan': 'План', - 'trip.mobilePlaces': 'Места', - 'trip.toast.placeUpdated': 'Место обновлено', - 'trip.toast.placeAdded': 'Место добавлено', - 'trip.toast.placeDeleted': 'Место удалено', - 'trip.toast.selectDay': 'Сначала выберите день', - 'trip.toast.assignedToDay': 'Место назначено на день', - 'trip.toast.reorderError': 'Ошибка изменения порядка', - 'trip.toast.reservationUpdated': 'Бронирование обновлено', - 'trip.toast.reservationAdded': 'Бронирование добавлено', - 'trip.toast.deleted': 'Удалено', - 'trip.confirm.deletePlace': 'Вы уверены, что хотите удалить это место?', - 'trip.confirm.deletePlaces': 'Удалить {count} мест?', - 'trip.toast.placesDeleted': '{count} мест удалено', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'На этот день мест не запланировано', - 'dayplan.addNote': 'Добавить заметку', - 'dayplan.editNote': 'Редактировать заметку', - 'dayplan.noteAdd': 'Добавить заметку', - 'dayplan.noteEdit': 'Редактировать заметку', - 'dayplan.noteTitle': 'Заметка', - 'dayplan.noteSubtitle': 'Заметка на день', - 'dayplan.totalCost': 'Общая стоимость', - 'dayplan.days': 'Дни', - 'dayplan.dayN': 'День {n}', - 'dayplan.calculating': 'Расчёт...', - 'dayplan.route': 'Маршрут', - 'dayplan.optimize': 'Оптимизировать', - 'dayplan.optimized': 'Маршрут оптимизирован', - 'dayplan.routeError': 'Не удалось рассчитать маршрут', - 'dayplan.toast.needTwoPlaces': 'Для оптимизации маршрута нужно минимум два места', - 'dayplan.toast.routeOptimized': 'Маршрут оптимизирован', - 'dayplan.toast.noGeoPlaces': 'Не найдено мест с координатами для расчёта маршрута', - 'dayplan.confirmed': 'Подтверждено', - 'dayplan.pendingRes': 'Ожидание', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Экспортировать план дня в PDF', - 'dayplan.pdfError': 'Ошибка экспорта PDF', - 'dayplan.cannotReorderTransport': 'Бронирования с фиксированным временем нельзя перемещать', - 'dayplan.confirmRemoveTimeTitle': 'Удалить время?', - 'dayplan.confirmRemoveTimeBody': 'У этого места фиксированное время ({time}). При перемещении время будет удалено, и станет доступна свободная сортировка.', - 'dayplan.confirmRemoveTimeAction': 'Удалить время и переместить', - 'dayplan.cannotDropOnTimed': 'Элементы нельзя размещать между записями с фиксированным временем', - 'dayplan.cannotBreakChronology': 'Это нарушит хронологический порядок запланированных элементов и бронирований', - - // Places Sidebar - 'places.addPlace': 'Добавить место/активность', - 'places.importFile': 'Импортировать файл', - 'places.sidebarDrop': 'Отпустите для импорта', - 'places.importFileHint': 'Импортируйте файлы .gpx, .kml или .kmz из инструментов, таких как Google My Maps, Google Earth или GPS-трекер.', - 'places.importFileDropHere': 'Нажмите для выбора файла или перетащите его сюда', - 'places.importFileDropActive': 'Отпустите файл для выбора', - 'places.importFileUnsupported': 'Неподдерживаемый тип файла. Используйте .gpx, .kml или .kmz.', - 'places.importFileTooLarge': 'Файл слишком большой. Максимальный размер загрузки — {maxMb} MB.', - 'places.importFileError': 'Ошибка импорта', - 'places.importAllSkipped': 'Все места уже были в поездке.', - 'places.gpxImported': '{count} мест импортировано из GPX', - 'places.gpxImportTypes': 'Что импортировать?', - 'places.gpxImportWaypoints': 'Путевые точки', - 'places.gpxImportRoutes': 'Маршруты', - 'places.gpxImportTracks': 'Треки (с геометрией пути)', - 'places.gpxImportNoneSelected': 'Выберите хотя бы один тип для импорта.', - 'places.kmlImportTypes': 'Что вы хотите импортировать?', - 'places.kmlImportPoints': 'Точки (Placemarks)', - 'places.kmlImportPaths': 'Маршруты (LineStrings)', - 'places.kmlImportNoneSelected': 'Выберите хотя бы один тип.', - 'places.selectionCount': '{count} выбрано', - 'places.deleteSelected': 'Удалить выбранные', - 'places.kmlKmzImported': '{count} мест импортировано из KMZ/KML', - 'places.urlResolved': 'Место импортировано из URL', - 'places.importList': 'Импорт списка', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Импортировано: {created} • Пропущено: {skipped}', - 'places.importGoogleList': 'Список Google', - 'places.importNaverList': 'Список Naver', - 'places.googleListHint': 'Вставьте ссылку на общий список Google Maps для импорта всех мест.', - 'places.googleListImported': '{count} мест импортировано из "{list}"', - 'places.googleListError': 'Не удалось импортировать список Google Maps', - 'places.naverListHint': 'Вставьте ссылку на общий список Naver Maps для импорта всех мест.', - 'places.naverListImported': '{count} мест импортировано из "{list}"', - 'places.naverListError': 'Не удалось импортировать список Naver Maps', - 'places.viewDetails': 'Подробности', - 'places.assignToDay': 'Добавить в какой день?', - 'places.all': 'Все', - 'places.unplanned': 'Незапланированные', - 'places.filterTracks': 'Треки', - 'places.search': 'Поиск мест...', - 'places.allCategories': 'Все категории', - 'places.categoriesSelected': 'категорий', - 'places.clearFilter': 'Сбросить фильтр', - 'places.count': '{count} мест', - 'places.countSingular': '1 место', - 'places.allPlanned': 'Все места запланированы', - 'places.noneFound': 'Места не найдены', - 'places.editPlace': 'Редактировать место', - 'places.formName': 'Название', - 'places.formNamePlaceholder': 'напр. Эйфелева башня', - 'places.formDescription': 'Описание', - 'places.formDescriptionPlaceholder': 'Краткое описание...', - 'places.formAddress': 'Адрес', - 'places.formAddressPlaceholder': 'Улица, город, страна', - 'places.formLat': 'Широта (напр. 48.8566)', - 'places.formLng': 'Долгота (напр. 2.3522)', - 'places.formCategory': 'Категория', - 'places.noCategory': 'Без категории', - 'places.categoryNamePlaceholder': 'Название категории', - 'places.formTime': 'Время', - 'places.startTime': 'Начало', - 'places.endTime': 'Конец', - 'places.endTimeBeforeStart': 'Время окончания раньше времени начала', - 'places.timeCollision': 'Пересечение по времени с:', - 'places.formWebsite': 'Сайт', - 'places.formNotes': 'Заметки', - 'places.formNotesPlaceholder': 'Личные заметки...', - 'places.formReservation': 'Бронирование', - 'places.reservationNotesPlaceholder': 'Заметки о бронировании, номер подтверждения...', - 'places.mapsSearchPlaceholder': 'Поиск мест...', - 'places.mapsSearchError': 'Ошибка поиска мест.', - 'places.loadingDetails': 'Загрузка данных о месте…', - 'places.osmHint': 'Поиск через OpenStreetMap (без фото, часов работы и рейтингов). Добавьте API-ключ Google в настройках для полной информации.', - 'places.osmActive': 'Поиск через OpenStreetMap (без фото, рейтингов и часов работы). Добавьте API-ключ Google в настройках для расширенных данных.', - 'places.categoryCreateError': 'Не удалось создать категорию', - 'places.nameRequired': 'Введите название', - 'places.saveError': 'Ошибка сохранения', - // Place Inspector - 'inspector.opened': 'Открыто', - 'inspector.closed': 'Закрыто', - 'inspector.openingHours': 'Часы работы', - 'inspector.showHours': 'Показать часы работы', - 'inspector.files': 'Файлы', - 'inspector.filesCount': '{count} файлов', - 'inspector.removeFromDay': 'Убрать из дня', - 'inspector.remove': 'Удалить', - 'inspector.addToDay': 'Добавить в день', - 'inspector.confirmedRes': 'Подтверждённое бронирование', - 'inspector.pendingRes': 'Ожидающее бронирование', - 'inspector.google': 'Открыть в Google Maps', - 'inspector.website': 'Открыть сайт', - 'inspector.addRes': 'Бронирование', - 'inspector.editRes': 'Редактировать бронирование', - 'inspector.participants': 'Участники', - 'inspector.trackStats': 'Данные маршрута', - - // Reservations - 'reservations.title': 'Бронирования', - 'reservations.empty': 'Пока нет бронирований', - 'reservations.emptyHint': 'Добавьте бронирования на авиабилеты, отели и другое', - 'reservations.add': 'Добавить бронирование', - 'reservations.addManual': 'Ручное бронирование', - 'reservations.placeHint': 'Совет: бронирования лучше создавать прямо из места, чтобы связать их с планом дня.', - 'reservations.confirmed': 'Подтверждено', - 'reservations.pending': 'Ожидание', - 'reservations.summary': '{confirmed} подтв., {pending} ожид.', - 'reservations.fromPlan': 'Из плана', - 'reservations.showFiles': 'Показать файлы', - 'reservations.editTitle': 'Редактировать бронирование', - 'reservations.status': 'Статус', - 'reservations.datetime': 'Дата и время', - 'reservations.startTime': 'Время начала', - 'reservations.endTime': 'Время окончания', - 'reservations.date': 'Дата', - 'reservations.time': 'Время', - 'reservations.timeAlt': 'Время (альтернативное, напр. 19:30)', - 'reservations.notes': 'Заметки', - 'reservations.notesPlaceholder': 'Дополнительные заметки...', - 'reservations.meta.airline': 'Авиакомпания', - 'reservations.meta.flightNumber': 'Номер рейса', - 'reservations.meta.from': 'Откуда', - 'reservations.meta.to': 'Куда', - 'reservations.needsReview': 'Проверить', - 'reservations.needsReviewHint': 'Аэропорт не удалось определить автоматически — подтвердите местоположение.', - 'reservations.searchLocation': 'Искать станцию, порт, адрес...', - 'airport.searchPlaceholder': 'Код аэропорта или город (напр. FRA)', - 'map.connections': 'Соединения', - 'map.showConnections': 'Показать маршруты бронирований', - 'map.hideConnections': 'Скрыть маршруты бронирований', - 'settings.bookingLabels': 'Подписи маршрутов бронирований', - 'settings.bookingLabelsHint': 'Отображает названия станций / аэропортов на карте. Если выключено, показывается только значок.', - 'reservations.meta.trainNumber': 'Номер поезда', - 'reservations.meta.platform': 'Платформа', - 'reservations.meta.seat': 'Место', - 'reservations.meta.checkIn': 'Заезд', - 'reservations.meta.checkInUntil': 'Заселение до', - 'reservations.meta.checkOut': 'Выезд', - 'reservations.meta.linkAccommodation': 'Жильё', - 'reservations.meta.pickAccommodation': 'Привязать к жилью', - 'reservations.meta.noAccommodation': 'Нет', - 'reservations.meta.hotelPlace': 'Жильё', - 'reservations.meta.pickHotel': 'Выбрать жильё', - 'reservations.meta.fromDay': 'С', - 'reservations.meta.toDay': 'По', - 'reservations.meta.selectDay': 'Выбрать день', - 'reservations.type.flight': 'Авиабилет', - 'reservations.type.hotel': 'Жильё', - 'reservations.type.restaurant': 'Ресторан', - 'reservations.type.train': 'Поезд', - 'reservations.type.car': 'Автомобиль', - 'reservations.type.cruise': 'Круиз', - 'reservations.type.event': 'Мероприятие', - 'reservations.type.tour': 'Экскурсия', - 'reservations.type.other': 'Другое', - 'reservations.confirm.delete': 'Вы уверены, что хотите удалить бронирование «{name}»?', - 'reservations.confirm.deleteTitle': 'Удалить бронирование?', - 'reservations.confirm.deleteBody': '«{name}» будет удалено навсегда.', - 'reservations.toast.updated': 'Бронирование обновлено', - 'reservations.toast.removed': 'Бронирование удалено', - 'reservations.toast.fileUploaded': 'Файл загружен', - 'reservations.toast.uploadError': 'Ошибка загрузки', - 'reservations.newTitle': 'Новое бронирование', - 'reservations.bookingType': 'Тип бронирования', - 'reservations.titleLabel': 'Название', - 'reservations.titlePlaceholder': 'напр. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Местоположение / Адрес', - 'reservations.locationPlaceholder': 'Адрес, аэропорт, отель...', - 'reservations.confirmationCode': 'Код бронирования', - 'reservations.confirmationPlaceholder': 'напр. ABC12345', - 'reservations.day': 'День', - 'reservations.noDay': 'Без дня', - 'reservations.place': 'Место', - 'reservations.noPlace': 'Без места', - 'reservations.pendingSave': 'будет сохранено…', - 'reservations.uploading': 'Загрузка...', - 'reservations.attachFile': 'Прикрепить файл', - 'reservations.linkExisting': 'Привязать существующий файл', - 'reservations.toast.saveError': 'Ошибка сохранения', - 'reservations.toast.updateError': 'Ошибка обновления', - 'reservations.toast.deleteError': 'Ошибка удаления', - 'reservations.confirm.remove': 'Удалить бронирование для «{name}»?', - 'reservations.linkAssignment': 'Привязать к назначению дня', - 'reservations.pickAssignment': 'Выберите назначение из вашего плана...', - 'reservations.noAssignment': 'Без привязки (самостоятельное)', - 'reservations.price': 'Цена', - 'reservations.budgetCategory': 'Категория бюджета', - 'reservations.budgetCategoryPlaceholder': 'напр. Транспорт, Проживание', - 'reservations.budgetCategoryAuto': 'Авто (по типу бронирования)', - 'reservations.budgetHint': 'При сохранении будет автоматически создана запись бюджета.', - 'reservations.departureDate': 'Вылет', - 'reservations.arrivalDate': 'Прилёт', - 'reservations.departureTime': 'Время вылета', - 'reservations.arrivalTime': 'Время прилёта', - 'reservations.pickupDate': 'Получение', - 'reservations.returnDate': 'Возврат', - 'reservations.pickupTime': 'Время получения', - 'reservations.returnTime': 'Время возврата', - 'reservations.endDate': 'Дата окончания', - 'reservations.meta.departureTimezone': 'TZ вылета', - 'reservations.meta.arrivalTimezone': 'TZ прилёта', - 'reservations.span.departure': 'Вылет', - 'reservations.span.arrival': 'Прилёт', - 'reservations.span.inTransit': 'В пути', - 'reservations.span.pickup': 'Получение', - 'reservations.span.return': 'Возврат', - 'reservations.span.active': 'Активно', - 'reservations.span.start': 'Начало', - 'reservations.span.end': 'Конец', - 'reservations.span.ongoing': 'Продолжается', - 'reservations.validation.endBeforeStart': 'Дата/время окончания должны быть позже даты/времени начала', - 'reservations.addBooking': 'Добавить бронирование', - - // Budget - 'budget.title': 'Бюджет', - 'budget.exportCsv': 'Экспорт CSV', - 'budget.emptyTitle': 'Бюджет ещё не создан', - 'budget.emptyText': 'Создайте категории и записи для планирования бюджета поездки', - 'budget.emptyPlaceholder': 'Введите название категории...', - 'budget.createCategory': 'Создать категорию', - 'budget.category': 'Категория', - 'budget.categoryName': 'Название категории', - 'budget.table.name': 'Название', - 'budget.table.total': 'Итого', - 'budget.table.persons': 'Человек', - 'budget.table.days': 'Дней', - 'budget.table.perPerson': 'На человека', - 'budget.table.perDay': 'В день', - 'budget.table.perPersonDay': 'Чел. / день', - 'budget.table.note': 'Заметка', - 'budget.table.date': 'Дата', - 'budget.newEntry': 'Новая запись', - 'budget.defaultEntry': 'Новая запись', - 'budget.defaultCategory': 'Новая категория', - 'budget.total': 'Итого', - 'budget.totalBudget': 'Общий бюджет', - 'budget.byCategory': 'По категориям', - 'budget.editTooltip': 'Нажмите для редактирования', - 'budget.linkedToReservation': 'Связано с бронированием — редактируйте название там', - 'budget.confirm.deleteCategory': 'Вы уверены, что хотите удалить категорию «{name}» с {count} записями?', - 'budget.deleteCategory': 'Удалить категорию', - 'budget.perPerson': 'На человека', - 'budget.paid': 'Оплачено', - 'budget.open': 'Не оплачено', - 'budget.noMembers': 'Участники не назначены', - 'budget.settlement': 'Взаиморасчёт', - 'budget.settlementInfo': 'Нажмите на аватар участника в строке бюджета, чтобы отметить его зелёным — это значит, что он заплатил. Взаиморасчёт покажет, кто кому и сколько должен.', - 'budget.netBalances': 'Чистые балансы', - - // Files - 'files.title': 'Файлы', - 'files.pageTitle': 'Файлы и документы', - 'files.subtitle': '{count} файлов для {trip}', - 'files.download': 'Скачать', - 'files.openError': 'Не удалось открыть файл', - 'files.downloadPdf': 'Скачать PDF', - 'files.count': '{count} файлов', - 'files.countSingular': '1 файл', - 'files.uploaded': '{count} загружено', - 'files.uploadError': 'Ошибка загрузки', - 'files.dropzone': 'Перетащите файлы сюда', - 'files.dropzoneHint': 'или нажмите для выбора', - 'files.allowedTypes': 'Изображения, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Макс. 50 МБ', - 'files.uploading': 'Загрузка...', - 'files.filterAll': 'Все', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Изображения', - 'files.filterDocs': 'Документы', - 'files.filterCollab': 'Заметки Collab', - 'files.sourceCollab': 'Из заметок Collab', - 'files.empty': 'Файлов пока нет', - 'files.emptyHint': 'Загрузите файлы, чтобы прикрепить их к поездке', - 'files.openTab': 'Открыть в новой вкладке', - 'files.confirm.delete': 'Вы уверены, что хотите удалить этот файл?', - 'files.toast.deleted': 'Файл удалён', - 'files.toast.deleteError': 'Не удалось удалить файл', - 'files.sourcePlan': 'План дня', - 'files.sourceBooking': 'Бронирование', - 'files.sourceTransport': 'Транспорт', - 'files.attach': 'Прикрепить', - 'files.pasteHint': 'Также можно вставить изображения из буфера обмена (Ctrl+V)', - 'files.trash': 'Корзина', - 'files.trashEmpty': 'Корзина пуста', - 'files.emptyTrash': 'Очистить корзину', - 'files.restore': 'Восстановить', - 'files.star': 'В избранное', - 'files.unstar': 'Из избранного', - 'files.assign': 'Назначить', - 'files.assignTitle': 'Назначить файл', - 'files.assignPlace': 'Место', - 'files.assignBooking': 'Бронирование', - 'files.assignTransport': 'Транспорт', - 'files.unassigned': 'Не назначен', - 'files.unlink': 'Удалить связь', - 'files.toast.trashed': 'Перемещено в корзину', - 'files.toast.restored': 'Файл восстановлен', - 'files.toast.trashEmptied': 'Корзина очищена', - 'files.toast.assigned': 'Файл назначен', - 'files.toast.assignError': 'Ошибка назначения', - 'files.toast.restoreError': 'Ошибка восстановления', - 'files.confirm.permanentDelete': 'Безвозвратно удалить этот файл? Это действие нельзя отменить.', - 'files.confirm.emptyTrash': 'Безвозвратно удалить все файлы из корзины? Это действие нельзя отменить.', - 'files.noteLabel': 'Заметка', - 'files.notePlaceholder': 'Добавить заметку...', - - // Packing - 'packing.title': 'Список вещей', - 'packing.empty': 'Список вещей пуст', - 'packing.import': 'Импорт', - 'packing.importTitle': 'Импорт списка вещей', - 'packing.importHint': 'Один предмет на строку. Категория и количество — через запятую, точку с запятой или табуляцию: Название, Категория, Количество', - 'packing.importPlaceholder': 'Зубная щётка\nСолнцезащитный крем, Гигиена\nФутболки, Одежда, 5\nПаспорт, Документы', - 'packing.importCsv': 'Загрузить CSV/TXT', - 'packing.importAction': 'Импортировать {count}', - 'packing.importSuccess': '{count} предметов импортировано', - 'packing.importError': 'Ошибка импорта', - 'packing.importEmpty': 'Нет предметов для импорта', - 'packing.progress': '{packed} из {total} собрано ({percent}%)', - 'packing.clearChecked': 'Удалить {count} отмеченных', - 'packing.clearCheckedShort': 'Удалить {count}', - 'packing.suggestions': 'Подсказки', - 'packing.suggestionsTitle': 'Добавить подсказки', - 'packing.allSuggested': 'Все подсказки добавлены', - 'packing.allPacked': 'Всё собрано!', - 'packing.addPlaceholder': 'Добавить вещь...', - 'packing.categoryPlaceholder': 'Категория...', - 'packing.filterAll': 'Все', - 'packing.filterOpen': 'Не собрано', - 'packing.filterDone': 'Собрано', - 'packing.emptyTitle': 'Список вещей пуст', - 'packing.emptyHint': 'Добавьте вещи или используйте подсказки', - 'packing.emptyFiltered': 'Нет вещей, соответствующих фильтру', - 'packing.menuRename': 'Переименовать', - 'packing.menuCheckAll': 'Отметить все', - 'packing.menuUncheckAll': 'Снять отметки', - 'packing.menuDeleteCat': 'Удалить категорию', - 'packing.addItem': 'Добавить вещь', - 'packing.addItemPlaceholder': 'Название...', - 'packing.addCategory': 'Добавить категорию', - 'packing.newCategoryPlaceholder': 'Название категории (напр. Одежда)', - 'packing.applyTemplate': 'Применить шаблон', - 'packing.template': 'Шаблон', - 'packing.templateApplied': '{count} вещей добавлено из шаблона', - 'packing.templateError': 'Ошибка применения шаблона', - 'packing.saveAsTemplate': 'Сохранить как шаблон', - 'packing.templateName': 'Название шаблона', - 'packing.templateSaved': 'Список вещей сохранён как шаблон', - 'packing.noMembers': 'Нет участников', - 'packing.bags': 'Багаж', - 'packing.noBag': 'Не назначено', - 'packing.totalWeight': 'Общий вес', - 'packing.bagName': 'Название...', - 'packing.addBag': 'Добавить багаж', - 'packing.changeCategory': 'Изменить категорию', - 'packing.confirm.clearChecked': 'Вы уверены, что хотите удалить {count} отмеченных вещей?', - 'packing.confirm.deleteCat': 'Вы уверены, что хотите удалить категорию «{name}» с {count} вещами?', - 'packing.defaultCategory': 'Другое', - 'packing.toast.saveError': 'Ошибка сохранения', - 'packing.toast.deleteError': 'Ошибка удаления', - 'packing.toast.renameError': 'Ошибка переименования', - 'packing.toast.addError': 'Ошибка добавления', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Паспорт', category: 'Документы' }, - { name: 'Удостоверение личности', category: 'Документы' }, - { name: 'Страховка', category: 'Документы' }, - { name: 'Авиабилеты', category: 'Документы' }, - { name: 'Банковская карта', category: 'Финансы' }, - { name: 'Наличные', category: 'Финансы' }, - { name: 'Виза', category: 'Документы' }, - { name: 'Футболки', category: 'Одежда' }, - { name: 'Брюки', category: 'Одежда' }, - { name: 'Нижнее бельё', category: 'Одежда' }, - { name: 'Носки', category: 'Одежда' }, - { name: 'Куртка', category: 'Одежда' }, - { name: 'Пижама', category: 'Одежда' }, - { name: 'Купальник', category: 'Одежда' }, - { name: 'Дождевик', category: 'Одежда' }, - { name: 'Удобная обувь', category: 'Одежда' }, - { name: 'Зубная щётка', category: 'Гигиена' }, - { name: 'Зубная паста', category: 'Гигиена' }, - { name: 'Шампунь', category: 'Гигиена' }, - { name: 'Дезодорант', category: 'Гигиена' }, - { name: 'Солнцезащитный крем', category: 'Гигиена' }, - { name: 'Бритва', category: 'Гигиена' }, - { name: 'Зарядное устройство', category: 'Электроника' }, - { name: 'Внешний аккумулятор', category: 'Электроника' }, - { name: 'Наушники', category: 'Электроника' }, - { name: 'Адаптер для розеток', category: 'Электроника' }, - { name: 'Фотоаппарат', category: 'Электроника' }, - { name: 'Обезболивающее', category: 'Здоровье' }, - { name: 'Пластыри', category: 'Здоровье' }, - { name: 'Антисептик', category: 'Здоровье' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Поделиться поездкой', - 'members.inviteUser': 'Пригласить пользователя', - 'members.selectUser': 'Выберите пользователя…', - 'members.invite': 'Пригласить', - 'members.allHaveAccess': 'У всех пользователей уже есть доступ.', - 'members.access': 'Доступ', - 'members.person': 'человек', - 'members.persons': 'человек', - 'members.you': 'вы', - 'members.owner': 'Владелец', - 'members.leaveTrip': 'Покинуть поездку', - 'members.removeAccess': 'Отозвать доступ', - 'members.confirmLeave': 'Покинуть поездку? Вы потеряете доступ.', - 'members.confirmRemove': 'Отозвать доступ у этого пользователя?', - 'members.loadError': 'Не удалось загрузить участников', - 'members.added': 'добавлен', - 'members.addError': 'Ошибка добавления', - 'members.removed': 'Участник удалён', - 'members.removeError': 'Ошибка удаления', - - // Categories (Admin) - 'categories.title': 'Категории', - 'categories.subtitle': 'Управление категориями мест', - 'categories.new': 'Новая категория', - 'categories.empty': 'Категорий пока нет', - 'categories.namePlaceholder': 'Название категории', - 'categories.icon': 'Иконка', - 'categories.color': 'Цвет', - 'categories.customColor': 'Выбрать свой цвет', - 'categories.preview': 'Предпросмотр', - 'categories.defaultName': 'Категория', - 'categories.update': 'Обновить', - 'categories.create': 'Создать', - 'categories.confirm.delete': 'Удалить категорию? Места в этой категории не будут удалены.', - 'categories.toast.loadError': 'Не удалось загрузить категории', - 'categories.toast.nameRequired': 'Введите название', - 'categories.toast.updated': 'Категория обновлена', - 'categories.toast.created': 'Категория создана', - 'categories.toast.saveError': 'Ошибка сохранения', - 'categories.toast.deleted': 'Категория удалена', - 'categories.toast.deleteError': 'Ошибка удаления', - - // Backup (Admin) - 'backup.title': 'Резервная копия', - 'backup.subtitle': 'База данных и все загруженные файлы', - 'backup.refresh': 'Обновить', - 'backup.upload': 'Загрузить копию', - 'backup.uploading': 'Загрузка…', - 'backup.create': 'Создать копию', - 'backup.creating': 'Создание…', - 'backup.empty': 'Резервных копий нет', - 'backup.createFirst': 'Создать первую копию', - 'backup.download': 'Скачать', - 'backup.restore': 'Восстановить', - 'backup.confirm.restore': 'Восстановить копию «{name}»?\n\nВсе текущие данные будут заменены данными из копии.', - 'backup.confirm.uploadRestore': 'Загрузить и восстановить файл копии «{name}»?\n\nВсе текущие данные будут перезаписаны.', - 'backup.confirm.delete': 'Удалить копию «{name}»?', - 'backup.toast.loadError': 'Не удалось загрузить резервные копии', - 'backup.toast.created': 'Резервная копия создана', - 'backup.toast.createError': 'Не удалось создать резервную копию', - 'backup.toast.restored': 'Копия восстановлена. Страница перезагрузится…', - 'backup.toast.restoreError': 'Ошибка восстановления', - 'backup.toast.uploadError': 'Ошибка загрузки', - 'backup.toast.deleted': 'Резервная копия удалена', - 'backup.toast.deleteError': 'Ошибка удаления', - 'backup.toast.downloadError': 'Ошибка скачивания', - 'backup.toast.settingsSaved': 'Настройки автокопирования сохранены', - 'backup.toast.settingsError': 'Не удалось сохранить настройки', - 'backup.auto.title': 'Автокопирование', - 'backup.auto.subtitle': 'Автоматическое резервное копирование по расписанию', - 'backup.auto.enable': 'Включить автокопирование', - 'backup.auto.enableHint': 'Резервные копии будут создаваться автоматически по выбранному расписанию', - 'backup.auto.interval': 'Интервал', - 'backup.auto.hour': 'Запуск в час', - 'backup.auto.hourHint': 'Местное время сервера (формат {format})', - 'backup.auto.dayOfWeek': 'День недели', - 'backup.auto.dayOfMonth': 'День месяца', - 'backup.auto.dayOfMonthHint': 'Ограничено 1–28 для совместимости со всеми месяцами', - 'backup.auto.scheduleSummary': 'Расписание', - 'backup.auto.summaryDaily': 'Каждый день в {hour}:00', - 'backup.auto.summaryWeekly': 'Каждый {day} в {hour}:00', - 'backup.auto.summaryMonthly': '{day}-го числа каждого месяца в {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Автокопирование настроено через переменные окружения Docker. Чтобы изменить параметры, обновите docker-compose.yml и перезапустите контейнер.', - 'backup.auto.copyEnv': 'Скопировать переменные окружения Docker', - 'backup.auto.envCopied': 'Переменные окружения Docker скопированы в буфер обмена', - 'backup.auto.keepLabel': 'Удалять старые копии через', - 'backup.dow.sunday': 'Вс', - 'backup.dow.monday': 'Пн', - 'backup.dow.tuesday': 'Вт', - 'backup.dow.wednesday': 'Ср', - 'backup.dow.thursday': 'Чт', - 'backup.dow.friday': 'Пт', - 'backup.dow.saturday': 'Сб', - 'backup.interval.hourly': 'Каждый час', - 'backup.interval.daily': 'Ежедневно', - 'backup.interval.weekly': 'Еженедельно', - 'backup.interval.monthly': 'Ежемесячно', - 'backup.keep.1day': '1 день', - 'backup.keep.3days': '3 дня', - 'backup.keep.7days': '7 дней', - 'backup.keep.14days': '14 дней', - 'backup.keep.30days': '30 дней', - 'backup.keep.forever': 'Хранить вечно', - - // Photos - 'photos.title': 'Фотографии', - 'photos.subtitle': '{count} фото для {trip}', - 'photos.dropHere': 'Перетащите фото сюда...', - 'photos.dropHereActive': 'Перетащите фото сюда', - 'photos.captionForAll': 'Подпись (для всех)', - 'photos.captionPlaceholder': 'Необязательная подпись...', - 'photos.addCaption': 'Добавить подпись...', - 'photos.allDays': 'Все дни', - 'photos.noPhotos': 'Фото пока нет', - 'photos.uploadHint': 'Загрузите фото из путешествия', - 'photos.clickToSelect': 'или нажмите для выбора', - 'photos.linkPlace': 'Привязать место', - 'photos.noPlace': 'Без места', - 'photos.uploadN': '{n} фото загружено', - 'photos.linkDay': 'Связать день', - 'photos.noDay': 'Нет дня', - 'photos.dayLabel': 'День {number}', - 'photos.photoSelected': 'Фото выбрано', - 'photos.photosSelected': 'Фото выбраны', - 'photos.fileTypeHint': 'JPG, PNG, WebP · макс. 10 МБ · до 30 фото', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Восстановить копию?', - 'backup.restoreWarning': 'Все текущие данные (поездки, места, пользователи, загрузки) будут безвозвратно заменены данными из копии. Это действие нельзя отменить.', - 'backup.restoreTip': 'Совет: создайте резервную копию текущего состояния перед восстановлением.', - 'backup.restoreConfirm': 'Да, восстановить', - - // PDF - 'pdf.travelPlan': 'План поездки', - 'pdf.planned': 'Запланировано', - 'pdf.costLabel': 'Стоимость EUR', - 'pdf.preview': 'Предпросмотр PDF', - 'pdf.saveAsPdf': 'Сохранить как PDF', - - // Planner - 'planner.places': 'Места', - 'planner.bookings': 'Бронирования', - 'planner.packingList': 'Список вещей', - 'planner.documents': 'Документы', - 'planner.dayPlan': 'План дня', - 'planner.reservations': 'Бронирования', - 'planner.minTwoPlaces': 'Нужно минимум 2 места с координатами', - 'planner.noGeoPlaces': 'Нет мест с координатами', - 'planner.routeCalculated': 'Маршрут рассчитан', - 'planner.routeCalcFailed': 'Не удалось рассчитать маршрут', - 'planner.routeError': 'Ошибка расчёта маршрута', - 'planner.icsExportFailed': 'Не удалось экспортировать ICS', - 'planner.routeOptimized': 'Маршрут оптимизирован', - 'planner.reservationUpdated': 'Бронирование обновлено', - 'planner.reservationAdded': 'Бронирование добавлено', - 'planner.confirmDeleteReservation': 'Удалить бронирование?', - 'planner.reservationDeleted': 'Бронирование удалено', - 'planner.days': 'Дни', - 'planner.allPlaces': 'Все места', - 'planner.totalPlaces': 'Всего {n} мест', - 'planner.noDaysPlanned': 'Дни ещё не запланированы', - 'planner.editTrip': 'Редактировать поездку \u2192', - 'planner.placeOne': '1 место', - 'planner.placeN': '{n} мест', - 'planner.addNote': 'Добавить заметку', - 'planner.noEntries': 'На этот день записей нет', - 'planner.addPlace': 'Добавить место/активность', - 'planner.addPlaceShort': '+ Добавить место/активность', - 'planner.resPending': 'Бронирование ожидает · ', - 'planner.resConfirmed': 'Бронирование подтверждено · ', - 'planner.notePlaceholder': 'Заметка…', - 'planner.noteTimePlaceholder': 'Время (необязательно)', - 'planner.noteExamplePlaceholder': 'напр. S3 в 14:30 с вокзала, паром с причала 7, обеденный перерыв…', - 'planner.totalCost': 'Общая стоимость', - 'planner.searchPlaces': 'Поиск мест…', - 'planner.allCategories': 'Все категории', - 'planner.noPlacesFound': 'Места не найдены', - 'planner.addFirstPlace': 'Добавить первое место', - 'planner.noReservations': 'Нет бронирований', - 'planner.addFirstReservation': 'Добавить первое бронирование', - 'planner.new': 'Новое', - 'planner.addToDay': '+ День', - 'planner.calculating': 'Расчёт…', - 'planner.route': 'Маршрут', - 'planner.optimize': 'Оптимизировать', - 'planner.openGoogleMaps': 'Открыть в Google Maps', - 'planner.selectDayHint': 'Выберите день из списка слева для просмотра плана дня', - 'planner.noPlacesForDay': 'На этот день мест пока нет', - 'planner.addPlacesLink': 'Добавить места \u2192', - 'planner.minTotal': 'мин. всего', - 'planner.noReservation': 'Нет бронирования', - 'planner.removeFromDay': 'Убрать из дня', - 'planner.addToThisDay': 'Добавить в день', - 'planner.overview': 'Обзор', - 'planner.noDays': 'Дней нет', - 'planner.editTripToAddDays': 'Отредактируйте поездку для добавления дней', - 'planner.dayCount': '{n} дней', - 'planner.clickToUnlock': 'Нажмите для разблокировки', - 'planner.keepPosition': 'Сохранить позицию при оптимизации маршрута', - 'planner.dayDetails': 'Подробности дня', - 'planner.dayN': 'День {n}', - - // Dashboard Stats - 'stats.countries': 'Страны', - 'stats.cities': 'Города', - 'stats.trips': 'Поездки', - 'stats.places': 'Места', - 'stats.worldProgress': 'Прогресс по миру', - 'stats.visited': 'посещено', - 'stats.remaining': 'осталось', - 'stats.visitedCountries': 'Посещённые страны', - - // Day Detail Panel - 'day.precipProb': 'Вероятность осадков', - 'day.precipitation': 'Осадки', - 'day.wind': 'Ветер', - 'day.sunrise': 'Восход', - 'day.sunset': 'Закат', - 'day.hourlyForecast': 'Почасовой прогноз', - 'day.climateHint': 'Исторические средние — реальный прогноз доступен за 16 дней до этой даты.', - 'day.noWeather': 'Данные о погоде недоступны. Добавьте место с координатами.', - 'day.overview': 'Обзор дня', - 'day.accommodation': 'Жильё', - 'day.addAccommodation': 'Добавить жильё', - 'day.hotelDayRange': 'Применить к дням', - 'day.noPlacesForHotel': 'Сначала добавьте места в поездку', - 'day.allDays': 'Все', - 'day.checkIn': 'Заезд', - 'day.checkInUntil': 'До', - 'day.checkOut': 'Выезд', - 'day.confirmation': 'Подтверждение', - 'day.editAccommodation': 'Редактировать жильё', - 'day.reservations': 'Бронирования', - - // Memories / Immich - 'memories.title': 'Фото', - 'memories.notConnected': 'Immich не подключён', - 'memories.notConnectedHint': 'Подключите Immich в настройках, чтобы видеть фотографии из поездок.', - 'memories.notConnectedMultipleHint': 'Подключите одного из этих фотопровайдеров: {provider_names} в Настройках, чтобы добавлять фотографии к этому путешествию.', - 'memories.noDates': 'Добавьте даты поездки для загрузки фотографий.', - 'memories.noPhotos': 'Фотографии не найдены', - 'memories.noPhotosHint': 'В Immich нет фотографий за период этой поездки.', - 'memories.photosFound': 'фото', - 'memories.fromOthers': 'от других', - 'memories.sharePhotos': 'Поделиться фото', - 'memories.sharing': 'Общий доступ', - 'memories.reviewTitle': 'Проверьте ваши фото', - 'memories.reviewHint': 'Нажмите на фото, чтобы исключить его из общего доступа.', - 'memories.shareCount': 'Поделиться ({count} фото)', - 'memories.providerUrl': 'URL сервера', - 'memories.providerApiKey': 'API-ключ', - 'memories.providerUsername': 'Имя пользователя', - 'memories.providerPassword': 'Пароль', - 'memories.providerOTP': 'Код MFA (если включён)', - 'memories.skipSSLVerification': 'Пропустить проверку SSL-сертификата', - 'memories.immichAutoUpload': 'Дублировать фото journey в Immich при загрузке', - 'memories.providerUrlHintSynology': 'Включите путь приложения Photos в URL, например https://nas:5001/photo', - 'memories.testConnection': 'Проверить подключение', - 'memories.testShort': 'Проверить', - 'memories.testFirst': 'Сначала проверьте подключение', - 'memories.connected': 'Подключено', - 'memories.disconnected': 'Не подключено', - 'memories.connectionSuccess': 'Подключение к Immich установлено', - 'memories.connectionError': 'Не удалось подключиться к Immich', - 'memories.saved': 'Настройки {provider_name} сохранены', - 'memories.providerDisconnectedBanner': 'Соединение с {provider_name} потеряно. Переподключитесь в Настройках для просмотра фотографий.', - 'memories.saveError': 'Не удалось сохранить настройки {provider_name}', - 'memories.oldest': 'Сначала старые', - 'memories.newest': 'Сначала новые', - 'memories.allLocations': 'Все места', - 'memories.addPhotos': 'Добавить фото', - 'memories.linkAlbum': 'Привязать альбом', - 'memories.selectAlbum': 'Выбрать альбом Immich', - 'memories.selectAlbumMultiple': 'Выбрать альбом', - 'memories.noAlbums': 'Альбомы не найдены', - 'memories.syncAlbum': 'Синхронизировать', - 'memories.unlinkAlbum': 'Отвязать', - 'memories.photos': 'фото', - 'memories.selectPhotos': 'Выбрать фото из Immich', - 'memories.selectPhotosMultiple': 'Выбрать фотографии', - 'memories.selectHint': 'Нажмите на фото, чтобы выбрать их.', - 'memories.selected': 'выбрано', - 'memories.addSelected': 'Добавить {count} фото', - 'memories.alreadyAdded': 'Добавлено', - 'memories.private': 'Приватное', - 'memories.stopSharing': 'Прекратить доступ', - 'memories.tripDates': 'Даты поездки', - 'memories.allPhotos': 'Все фото', - 'memories.confirmShareTitle': 'Поделиться с участниками поездки?', - 'memories.confirmShareHint': '{count} фото станут видны всем участникам этой поездки. Вы сможете сделать отдельные фото приватными позже.', - 'memories.confirmShareButton': 'Поделиться фото', - - // Collab Addon - 'collab.tabs.chat': 'Чат', - 'collab.tabs.notes': 'Заметки', - 'collab.tabs.polls': 'Опросы', - 'collab.whatsNext.title': 'Что дальше', - 'collab.whatsNext.today': 'Сегодня', - 'collab.whatsNext.tomorrow': 'Завтра', - 'collab.whatsNext.empty': 'Нет предстоящих активностей', - 'collab.whatsNext.until': 'до', - 'collab.whatsNext.emptyHint': 'Активности со временем будут отображаться здесь', - 'collab.chat.send': 'Отправить', - 'collab.chat.placeholder': 'Введите сообщение...', - 'collab.chat.empty': 'Начните разговор', - 'collab.chat.emptyHint': 'Сообщения видны всем участникам поездки', - 'collab.chat.emptyDesc': 'Делитесь идеями, планами и новостями с вашей группой', - 'collab.chat.today': 'Сегодня', - 'collab.chat.yesterday': 'Вчера', - 'collab.chat.deletedMessage': 'удалил(а) сообщение', - 'collab.chat.reply': 'Ответить', - 'collab.chat.loadMore': 'Загрузить старые сообщения', - 'collab.chat.justNow': 'только что', - 'collab.chat.minutesAgo': '{n} мин. назад', - 'collab.chat.hoursAgo': '{n} ч. назад', - 'collab.notes.title': 'Заметки', - 'collab.notes.new': 'Новая заметка', - 'collab.notes.empty': 'Заметок пока нет', - 'collab.notes.emptyHint': 'Начните записывать идеи и планы', - 'collab.notes.all': 'Все', - 'collab.notes.titlePlaceholder': 'Название заметки', - 'collab.notes.contentPlaceholder': 'Напишите что-нибудь...', - 'collab.notes.categoryPlaceholder': 'Категория', - 'collab.notes.newCategory': 'Новая категория...', - 'collab.notes.category': 'Категория', - 'collab.notes.noCategory': 'Без категории', - 'collab.notes.color': 'Цвет', - 'collab.notes.save': 'Сохранить', - 'collab.notes.cancel': 'Отмена', - 'collab.notes.edit': 'Редактировать', - 'collab.notes.delete': 'Удалить', - 'collab.notes.pin': 'Закрепить', - 'collab.notes.unpin': 'Открепить', - 'collab.notes.daysAgo': '{n} дн. назад', - 'collab.notes.categorySettings': 'Управление категориями', - 'collab.notes.create': 'Создать', - 'collab.notes.website': 'Сайт', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Прикрепить файлы', - 'collab.notes.noCategoriesYet': 'Категорий пока нет', - 'collab.notes.emptyDesc': 'Создайте заметку, чтобы начать', - 'collab.polls.title': 'Опросы', - 'collab.polls.new': 'Новый опрос', - 'collab.polls.empty': 'Опросов пока нет', - 'collab.polls.emptyHint': 'Задайте вопрос группе и голосуйте вместе', - 'collab.polls.question': 'Вопрос', - 'collab.polls.questionPlaceholder': 'Что нам делать?', - 'collab.polls.addOption': '+ Добавить вариант', - 'collab.polls.optionPlaceholder': 'Вариант {n}', - 'collab.polls.create': 'Создать опрос', - 'collab.polls.close': 'Закрыть', - 'collab.polls.closed': 'Закрыт', - 'collab.polls.votes': '{n} голосов', - 'collab.polls.vote': '{n} голос', - 'collab.polls.multipleChoice': 'Множественный выбор', - 'collab.polls.multiChoice': 'Множественный выбор', - 'collab.polls.deadline': 'Срок', - 'collab.polls.option': 'Вариант', - 'collab.polls.options': 'Варианты', - 'collab.polls.delete': 'Удалить', - 'collab.polls.closedSection': 'Закрытые', - - // Permissions - 'admin.tabs.permissions': 'Разрешения', - 'perm.title': 'Настройки разрешений', - 'perm.subtitle': 'Управляйте тем, кто может выполнять действия в приложении', - 'perm.saved': 'Настройки разрешений сохранены', - 'perm.resetDefaults': 'Сбросить по умолчанию', - 'perm.customized': 'изменено', - 'perm.level.admin': 'Только администратор', - 'perm.level.tripOwner': 'Владелец поездки', - 'perm.level.tripMember': 'Участники поездки', - 'perm.level.everybody': 'Все', - 'perm.cat.trip': 'Управление поездками', - 'perm.cat.members': 'Управление участниками', - 'perm.cat.files': 'Файлы', - 'perm.cat.content': 'Контент и расписание', - 'perm.cat.extras': 'Бюджет, сборы и совместная работа', - 'perm.action.trip_create': 'Создавать поездки', - 'perm.action.trip_edit': 'Редактировать детали поездки', - 'perm.action.trip_delete': 'Удалять поездки', - 'perm.action.trip_archive': 'Архивировать / разархивировать поездки', - 'perm.action.trip_cover_upload': 'Загружать обложку', - 'perm.action.member_manage': 'Добавлять / удалять участников', - 'perm.action.file_upload': 'Загружать файлы', - 'perm.action.file_edit': 'Редактировать метаданные файлов', - 'perm.action.file_delete': 'Удалять файлы', - 'perm.action.place_edit': 'Добавлять / редактировать / удалять места', - 'perm.action.day_edit': 'Редактировать дни, заметки и назначения', - 'perm.action.reservation_edit': 'Управлять бронированиями', - 'perm.action.budget_edit': 'Управлять бюджетом', - 'perm.action.packing_edit': 'Управлять списками вещей', - 'perm.action.collab_edit': 'Совместная работа (заметки, опросы, чат)', - 'perm.action.share_manage': 'Управлять ссылками для обмена', - 'perm.actionHint.trip_create': 'Кто может создавать новые поездки', - 'perm.actionHint.trip_edit': 'Кто может менять название, даты, описание и валюту поездки', - 'perm.actionHint.trip_delete': 'Кто может безвозвратно удалить поездку', - 'perm.actionHint.trip_archive': 'Кто может архивировать или разархивировать поездку', - 'perm.actionHint.trip_cover_upload': 'Кто может загружать или менять обложку', - 'perm.actionHint.member_manage': 'Кто может приглашать или удалять участников поездки', - 'perm.actionHint.file_upload': 'Кто может загружать файлы в поездку', - 'perm.actionHint.file_edit': 'Кто может редактировать описания и ссылки файлов', - 'perm.actionHint.file_delete': 'Кто может перемещать файлы в корзину или безвозвратно удалять', - 'perm.actionHint.place_edit': 'Кто может добавлять, редактировать или удалять места', - 'perm.actionHint.day_edit': 'Кто может редактировать дни, заметки к дням и назначения мест', - 'perm.actionHint.reservation_edit': 'Кто может создавать, редактировать или удалять бронирования', - 'perm.actionHint.budget_edit': 'Кто может создавать, редактировать или удалять статьи бюджета', - 'perm.actionHint.packing_edit': 'Кто может управлять вещами для сборов и сумками', - 'perm.actionHint.collab_edit': 'Кто может создавать заметки, опросы и отправлять сообщения', - 'perm.actionHint.share_manage': 'Кто может создавать или удалять публичные ссылки для обмена', - // Undo - 'undo.button': 'Отменить', - 'undo.tooltip': 'Отменить: {action}', - 'undo.assignPlace': 'Место добавлено в день', - 'undo.removeAssignment': 'Место удалено из дня', - 'undo.reorder': 'Места переупорядочены', - 'undo.optimize': 'Маршрут оптимизирован', - 'undo.deletePlace': 'Место удалено', - 'undo.deletePlaces': 'Места удалены', - 'undo.moveDay': 'Место перемещено в другой день', - 'undo.lock': 'Блокировка места изменена', - 'undo.importGpx': 'Импорт GPX', - 'undo.importKeyholeMarkup': 'Импорт KMZ/KML', - 'undo.importGoogleList': 'Импорт из Google Maps', - 'undo.importNaverList': 'Импорт из Naver Maps', - - // Notifications - 'notifications.title': 'Уведомления', - 'notifications.markAllRead': 'Отметить все прочитанными', - 'notifications.deleteAll': 'Удалить все', - 'notifications.showAll': 'Показать все уведомления', - 'notifications.empty': 'Нет уведомлений', - 'notifications.emptyDescription': 'Вы в курсе всех событий!', - 'notifications.all': 'Все', - 'notifications.unreadOnly': 'Непрочитанные', - 'notifications.markRead': 'Отметить как прочитанное', - 'notifications.markUnread': 'Отметить как непрочитанное', - 'notifications.delete': 'Удалить', - 'notifications.system': 'Система', - 'notifications.synologySessionCleared.title': 'Synology Photos отключено', - 'notifications.synologySessionCleared.text': 'Ваш сервер или аккаунт изменился — перейдите в Настройки, чтобы проверить соединение снова.', - 'memories.error.loadAlbums': 'Не удалось загрузить альбомы', - 'memories.error.linkAlbum': 'Не удалось привязать альбом', - 'memories.error.unlinkAlbum': 'Не удалось отвязать альбом', - 'memories.error.syncAlbum': 'Не удалось синхронизировать альбом', - 'memories.error.loadPhotos': 'Не удалось загрузить фотографии', - 'memories.error.addPhotos': 'Не удалось добавить фотографии', - 'memories.error.removePhoto': 'Не удалось удалить фотографию', - 'memories.error.toggleSharing': 'Не удалось обновить настройки доступа', - 'undo.addPlace': 'Место добавлено', - 'undo.done': 'Отменено: {action}', - 'notifications.test.title': 'Тестовое уведомление от {actor}', - 'notifications.test.text': 'Это простое тестовое уведомление.', - 'notifications.test.booleanTitle': '{actor} запрашивает подтверждение', - 'notifications.test.booleanText': 'Тестовое уведомление с выбором.', - 'notifications.test.accept': 'Подтвердить', - 'notifications.test.decline': 'Отклонить', - 'notifications.test.navigateTitle': 'Посмотрите на это', - 'notifications.test.navigateText': 'Тестовое уведомление с переходом.', - 'notifications.test.goThere': 'Перейти', - 'notifications.test.adminTitle': 'Рассылка администратора', - 'notifications.test.adminText': '{actor} отправил тестовое уведомление всем администраторам.', - 'notifications.test.tripTitle': '{actor} написал в вашей поездке', - 'notifications.test.tripText': 'Тестовое уведомление для поездки "{trip}".', - - // Todo - 'todo.subtab.packing': 'Список вещей', - 'todo.subtab.todo': 'Задачи', - 'todo.completed': 'выполнено', - 'todo.filter.all': 'Все', - 'todo.filter.open': 'Открытые', - 'todo.filter.done': 'Выполненные', - 'todo.uncategorized': 'Без категории', - 'todo.namePlaceholder': 'Название задачи', - 'todo.descriptionPlaceholder': 'Описание (необязательно)', - 'todo.unassigned': 'Не назначено', - 'todo.noCategory': 'Без категории', - 'todo.hasDescription': 'Есть описание', - 'todo.addItem': 'Новая задача', - 'todo.sidebar.sortBy': 'Сортировать по', - 'todo.priority': 'Приоритет', - 'todo.newCategoryLabel': 'новая', - 'budget.categoriesLabel': 'категорий', - 'todo.newCategory': 'Название категории', - 'todo.addCategory': 'Добавить категорию', - 'todo.newItem': 'Новая задача', - 'todo.empty': 'Задач пока нет. Добавьте задачу, чтобы начать!', - 'todo.filter.my': 'Мои задачи', - 'todo.filter.overdue': 'Просроченные', - 'todo.sidebar.tasks': 'Задачи', - 'todo.sidebar.categories': 'Категории', - 'todo.detail.title': 'Задача', - 'todo.detail.description': 'Описание', - 'todo.detail.category': 'Категория', - 'todo.detail.dueDate': 'Срок выполнения', - 'todo.detail.assignedTo': 'Назначено', - 'todo.detail.delete': 'Удалить', - 'todo.detail.save': 'Сохранить изменения', - 'todo.detail.create': 'Создать задачу', - 'todo.detail.priority': 'Приоритет', - 'todo.detail.noPriority': 'Нет', - 'todo.sortByPrio': 'Приоритет', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Доступна новая версия', - 'settings.notificationPreferences.noChannels': 'Каналы уведомлений не настроены. Попросите администратора настроить уведомления по электронной почте или через webhook.', - 'settings.webhookUrl.label': 'URL вебхука', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Введите URL вашего вебхука Discord, Slack или пользовательского для получения уведомлений.', - 'settings.webhookUrl.saved': 'URL вебхука сохранён', - 'settings.webhookUrl.test': 'Тест', - 'settings.webhookUrl.testSuccess': 'Тестовый вебхук успешно отправлен', - 'settings.webhookUrl.testFailed': 'Ошибка тестового вебхука', - 'settings.ntfyUrl.topicLabel': 'Тема Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL сервера Ntfy (необязательно)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Введите тему Ntfy для получения push-уведомлений. Оставьте поле сервера пустым, чтобы использовать настройку по умолчанию, заданную администратором.', - 'settings.ntfyUrl.tokenLabel': 'Токен доступа (необязательно)', - 'settings.ntfyUrl.tokenHint': 'Требуется для тем, защищённых паролем.', - 'settings.ntfyUrl.saved': 'Настройки Ntfy сохранены', - 'settings.ntfyUrl.test': 'Тест', - 'settings.ntfyUrl.testSuccess': 'Тестовое уведомление Ntfy успешно отправлено', - 'settings.ntfyUrl.testFailed': 'Ошибка отправки тестового уведомления Ntfy', - 'settings.ntfyUrl.tokenCleared': 'Токен доступа очищен', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Уведомления в приложении всегда активны и не могут быть отключены глобально.', - 'admin.notifications.adminWebhookPanel.title': 'Вебхук администратора', - 'admin.notifications.adminWebhookPanel.hint': 'Этот вебхук используется исключительно для уведомлений администратора (например, оповещения о версиях). Он независим от пользовательских вебхуков и отправляется автоматически при наличии URL.', - 'admin.notifications.adminWebhookPanel.saved': 'URL вебхука администратора сохранён', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Тестовый вебхук успешно отправлен', - 'admin.notifications.adminWebhookPanel.testFailed': 'Ошибка тестового вебхука', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Вебхук администратора отправляется автоматически при наличии URL', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Позволяет пользователям настраивать собственные темы ntfy для push-уведомлений. Установите сервер по умолчанию ниже, чтобы предварительно заполнить настройки пользователей.', - 'admin.notifications.testNtfy': 'Отправить тестовое Ntfy', - 'admin.notifications.testNtfySuccess': 'Тестовое Ntfy успешно отправлено', - 'admin.notifications.testNtfyFailed': 'Ошибка отправки тестового Ntfy', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy администратора', - 'admin.notifications.adminNtfyPanel.hint': 'Эта тема Ntfy используется исключительно для уведомлений администратора (например, оповещения о версиях). Она независима от тем пользователей и всегда отправляется при наличии настройки.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Также используется как сервер по умолчанию для ntfy-уведомлений пользователей. Оставьте пустым, чтобы использовать ntfy.sh. Пользователи могут изменить это в своих настройках.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема администратора', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Токен доступа (необязательно)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Токен доступа администратора очищен', - 'admin.notifications.adminNtfyPanel.saved': 'Настройки Ntfy администратора сохранены', - 'admin.notifications.adminNtfyPanel.test': 'Отправить тестовое Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Тестовое Ntfy успешно отправлено', - 'admin.notifications.adminNtfyPanel.testFailed': 'Ошибка отправки тестового Ntfy', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Ntfy администратора всегда отправляется при наличии настроенной темы', - 'admin.notifications.adminNotificationsHint': 'Настройте, какие каналы доставляют уведомления администратора (например, оповещения о версиях). Вебхук отправляется автоматически, если задан URL вебхука администратора.', - 'admin.notifications.tripReminders.title': 'Напоминания о поездках', - 'admin.notifications.tripReminders.hint': 'Отправляет напоминание перед началом поездки (необходимо указать дни напоминания в параметрах поездки).', - 'admin.notifications.tripReminders.enabled': 'Напоминания о поездках включены', - 'admin.notifications.tripReminders.disabled': 'Напоминания о поездках отключены', - 'admin.tabs.notifications': 'Уведомления', - 'notifications.versionAvailable.title': 'Доступно обновление', - 'notifications.versionAvailable.text': 'TREK {version} теперь доступен.', - 'notifications.versionAvailable.button': 'Подробнее', - 'notif.test.title': '[Тест] Уведомление', - 'notif.test.simple.text': 'Это простое тестовое уведомление.', - 'notif.test.boolean.text': 'Вы принимаете это тестовое уведомление?', - 'notif.test.navigate.text': 'Нажмите ниже для перехода на панель управления.', - - // Notifications - 'notif.trip_invite.title': 'Приглашение в поездку', - 'notif.trip_invite.text': '{actor} пригласил вас в {trip}', - 'notif.booking_change.title': 'Бронирование обновлено', - 'notif.booking_change.text': '{actor} обновил бронирование в {trip}', - 'notif.trip_reminder.title': 'Напоминание о поездке', - 'notif.trip_reminder.text': 'Ваша поездка {trip} скоро начнётся!', - 'notif.todo_due.title': 'Задача к сроку', - 'notif.todo_due.text': '{todo} в {trip} — срок {due}', - 'notif.vacay_invite.title': 'Приглашение Vacay Fusion', - 'notif.vacay_invite.text': '{actor} приглашает вас объединить планы отпуска', - 'notif.photos_shared.title': 'Фото опубликованы', - 'notif.photos_shared.text': '{actor} поделился {count} фото в {trip}', - 'notif.collab_message.title': 'Новое сообщение', - 'notif.collab_message.text': '{actor} отправил сообщение в {trip}', - 'notif.packing_tagged.title': 'Задание для упаковки', - 'notif.packing_tagged.text': '{actor} назначил вас в {category} в {trip}', - 'notif.version_available.title': 'Доступна новая версия', - 'notif.version_available.text': 'TREK {version} теперь доступен', - 'notif.action.view_trip': 'Открыть поездку', - 'notif.action.view_collab': 'Открыть сообщения', - 'notif.action.view_packing': 'Открыть упаковку', - 'notif.action.view_photos': 'Открыть фото', - 'notif.action.view_vacay': 'Открыть Vacay', - 'notif.action.view_admin': 'Перейти в админ', - 'notif.action.view': 'Открыть', - 'notif.action.accept': 'Принять', - 'notif.action.decline': 'Отклонить', - 'notif.generic.title': 'Уведомление', - 'notif.generic.text': 'У вас новое уведомление', - 'notif.dev.unknown_event.title': '[DEV] Неизвестное событие', - 'notif.dev.unknown_event.text': 'Тип события "{event}" не зарегистрирован в EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'только что', - 'common.hoursAgo': '{count} ч назад', - 'common.daysAgo': '{count} д назад', - 'memories.saveRouteNotConfigured': 'Маршрут сохранения не настроен для этого провайдера', - 'memories.testRouteNotConfigured': 'Маршрут тестирования не настроен для этого провайдера', - 'memories.fillRequiredFields': 'Пожалуйста, заполните все обязательные поля', - 'journey.search.placeholder': 'Поиск путешествий…', - 'journey.search.noResults': 'Путешествий по запросу «{query}» не найдено', - 'journey.title': 'Путешествие', - 'journey.subtitle': 'Отслеживайте свои путешествия в реальном времени', - 'journey.new': 'Новое путешествие', - 'journey.create': 'Создать', - 'journey.titlePlaceholder': 'Куда вы едете?', - 'journey.empty': 'Пока нет путешествий', - 'journey.emptyHint': 'Начните документировать свою следующую поездку', - 'journey.deleted': 'Путешествие удалено', - 'journey.createError': 'Не удалось создать путешествие', - 'journey.deleteError': 'Не удалось удалить путешествие', - 'journey.deleteConfirmTitle': 'Удалить', - 'journey.deleteConfirmMessage': 'Удалить «{title}»? Это действие нельзя отменить.', - 'journey.deleteConfirmGeneric': 'Вы уверены, что хотите удалить это?', - 'journey.notFound': 'Путешествие не найдено', - 'journey.photos': 'Фото', - 'journey.timelineEmpty': 'Пока нет остановок', - 'journey.timelineEmptyHint': 'Добавьте отметку или напишите запись в дневник', - 'journey.status.draft': 'Черновик', - 'journey.status.active': 'Активно', - 'journey.status.completed': 'Завершено', - 'journey.status.upcoming': 'Предстоящее', - 'journey.status.archived': 'В архиве', - 'journey.checkin.add': 'Отметиться', - 'journey.checkin.namePlaceholder': 'Название места', - 'journey.checkin.notesPlaceholder': 'Заметки (необязательно)', - 'journey.checkin.save': 'Сохранить', - 'journey.checkin.error': 'Не удалось сохранить отметку', - 'journey.entry.add': 'Дневник', - 'journey.entry.edit': 'Редактировать запись', - 'journey.entry.titlePlaceholder': 'Заголовок (необязательно)', - 'journey.entry.bodyPlaceholder': 'Что произошло сегодня?', - 'journey.entry.save': 'Сохранить', - 'journey.entry.error': 'Не удалось сохранить запись', - 'journey.photo.add': 'Фото', - 'journey.photo.uploadError': 'Загрузка не удалась', - 'journey.share.share': 'Поделиться', - 'journey.share.public': 'Публичный', - 'journey.share.linkCopied': 'Публичная ссылка скопирована', - 'journey.share.disabled': 'Публичный доступ отключён', - 'journey.editor.titlePlaceholder': 'Дайте название этому моменту...', - 'journey.editor.bodyPlaceholder': 'Расскажите историю этого дня...', - 'journey.editor.placePlaceholder': 'Местоположение (необязательно)', - 'journey.editor.tagsPlaceholder': 'Теги: скрытая жемчужина, лучшая еда, стоит вернуться...', - 'journey.visibility.private': 'Приватный', - 'journey.visibility.shared': 'Общий', - 'journey.visibility.public': 'Публичный', - 'journey.emptyState.title': 'Ваша история начинается здесь', - 'journey.emptyState.subtitle': 'Отметьтесь в месте или напишите первую запись в дневник', - 'journey.frontpage.subtitle': 'Превращайте поездки в истории, которые вы никогда не забудете', - 'journey.frontpage.createJourney': 'Создать путешествие', - 'journey.frontpage.activeJourney': 'Активное путешествие', - 'journey.frontpage.allJourneys': 'Все путешествия', - 'journey.frontpage.journeys': 'путешествий', - 'journey.frontpage.createNew': 'Создать новое путешествие', - 'journey.frontpage.createNewSub': 'Выберите поездки, пишите истории, делитесь приключениями', - 'journey.frontpage.live': 'В эфире', - 'journey.frontpage.synced': 'Синхронизировано', - 'journey.frontpage.continueWriting': 'Продолжить писать', - 'journey.frontpage.updated': 'Обновлено {time}', - 'journey.frontpage.suggestionLabel': 'Поездка только что завершилась', - 'journey.frontpage.suggestionText': 'Превратите {title} в путешествие', - 'journey.frontpage.dismiss': 'Скрыть', - 'journey.frontpage.journeyName': 'Название путешествия', - 'journey.frontpage.namePlaceholder': 'напр. Юго-Восточная Азия 2026', - 'journey.frontpage.selectTrips': 'Выбрать поездки', - 'journey.frontpage.tripsSelected': 'поездок выбрано', - 'journey.frontpage.trips': 'поездок', - 'journey.frontpage.placesImported': 'мест будет импортировано', - 'journey.frontpage.places': 'мест', - 'journey.detail.backToJourney': 'Назад к путешествию', - 'journey.detail.syncedWithTrips': 'Синхронизировано с поездками', - 'journey.detail.addEntry': 'Добавить запись', - 'journey.detail.newEntry': 'Новая запись', - 'journey.detail.editEntry': 'Редактировать запись', - 'journey.detail.noEntries': 'Пока нет записей', - 'journey.detail.noEntriesHint': 'Добавьте поездку, чтобы начать с шаблонных записей', - 'journey.detail.noPhotos': 'Пока нет фото', - 'journey.detail.noPhotosHint': 'Загрузите фото в записи или просмотрите библиотеку Immich/Synology', - 'journey.detail.journeyStats': 'Статистика путешествия', - 'journey.detail.syncedTrips': 'Синхронизированные поездки', - 'journey.detail.noTripsLinked': 'Пока нет привязанных поездок', - 'journey.detail.contributors': 'Участники', - 'journey.detail.readMore': 'Читать далее', - 'journey.detail.prosCons': 'Плюсы и минусы', - 'journey.detail.photos': 'фото', - 'journey.detail.day': 'День {number}', - 'journey.detail.places': 'мест', - 'journey.stats.days': 'Дней', - 'journey.stats.cities': 'Городов', - 'journey.stats.entries': 'Записей', - 'journey.stats.photos': 'Фото', - 'journey.stats.places': 'Мест', - 'journey.skeletons.show': 'Показать предложения', - 'journey.skeletons.hide': 'Скрыть предложения', - 'journey.verdict.lovedIt': 'Понравилось', - 'journey.verdict.couldBeBetter': 'Могло быть лучше', - 'journey.synced.places': 'мест', - 'journey.synced.synced': 'синхронизировано', - 'journey.editor.discardChangesConfirm': 'У вас есть несохранённые изменения. Отменить?', - 'journey.editor.uploadFailed': 'Не удалось загрузить фото', - 'journey.editor.uploadPhotos': 'Загрузить фото', - 'journey.editor.uploading': 'Загрузка...', - 'journey.editor.uploadingProgress': 'Загрузка {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{failed} из {total} фото не удалось загрузить — сохраните снова для повтора', - 'journey.editor.fromGallery': 'Из галереи', - 'journey.editor.allPhotosAdded': 'Все фото уже добавлены', - 'journey.editor.writeStory': 'Напишите свою историю...', - 'journey.editor.prosCons': 'Плюсы и минусы', - 'journey.editor.pros': 'Плюсы', - 'journey.editor.cons': 'Минусы', - 'journey.editor.proPlaceholder': 'Что-то отличное...', - 'journey.editor.conPlaceholder': 'Не очень хорошо...', - 'journey.editor.addAnother': 'Добавить ещё', - 'journey.editor.date': 'Дата', - 'journey.editor.location': 'Местоположение', - 'journey.editor.searchLocation': 'Поиск местоположения...', - 'journey.editor.mood': 'Настроение', - 'journey.editor.weather': 'Погода', - 'journey.editor.photoFirst': '1-е', - 'journey.editor.makeFirst': 'Сделать 1-м', - 'journey.editor.searching': 'Поиск...', - 'journey.mood.amazing': 'Потрясающе', - 'journey.mood.good': 'Хорошо', - 'journey.mood.neutral': 'Нейтрально', - 'journey.mood.rough': 'Тяжело', - 'journey.weather.sunny': 'Солнечно', - 'journey.weather.partly': 'Переменная облачность', - 'journey.weather.cloudy': 'Облачно', - 'journey.weather.rainy': 'Дождливо', - 'journey.weather.stormy': 'Гроза', - 'journey.weather.cold': 'Снежно', - 'journey.trips.linkTrip': 'Привязать поездку', - 'journey.trips.searchTrip': 'Поиск поездки', - 'journey.trips.searchPlaceholder': 'Название поездки или направление...', - 'journey.trips.noTripsAvailable': 'Нет доступных поездок', - 'journey.trips.link': 'Привязать', - 'journey.trips.tripLinked': 'Поездка привязана', - 'journey.trips.linkFailed': 'Не удалось привязать поездку', - 'journey.trips.addTrip': 'Добавить поездку', - 'journey.trips.unlinkTrip': 'Отвязать поездку', - 'journey.trips.unlinkMessage': 'Отвязать «{title}»? Все синхронизированные записи и фото из этой поездки будут безвозвратно удалены. Это действие нельзя отменить.', - 'journey.trips.unlink': 'Отвязать', - 'journey.trips.tripUnlinked': 'Поездка отвязана', - 'journey.trips.unlinkFailed': 'Не удалось отвязать поездку', - 'journey.trips.noTripsLinkedSettings': 'Нет привязанных поездок', - 'journey.contributors.invite': 'Пригласить участника', - 'journey.contributors.searchUser': 'Поиск пользователя', - 'journey.contributors.searchPlaceholder': 'Имя пользователя или email...', - 'journey.contributors.noUsers': 'Пользователи не найдены', - 'journey.contributors.role': 'Роль', - 'journey.contributors.added': 'Участник добавлен', - 'journey.contributors.addFailed': 'Не удалось добавить участника', - 'journey.share.publicShare': 'Публичный доступ', - 'journey.share.createLink': 'Создать ссылку для общего доступа', - 'journey.share.linkCreated': 'Ссылка создана', - 'journey.share.createFailed': 'Не удалось создать ссылку', - 'journey.share.copy': 'Копировать', - 'journey.share.copied': 'Скопировано!', - 'journey.share.timeline': 'Хронология', - 'journey.share.gallery': 'Галерея', - 'journey.share.map': 'Карта', - 'journey.share.removeLink': 'Удалить ссылку', - 'journey.share.linkDeleted': 'Ссылка удалена', - 'journey.share.deleteFailed': 'Не удалось удалить', - 'journey.share.updateFailed': 'Не удалось обновить', - - // Journey — Invite - 'journey.invite.role': 'Роль', - 'journey.invite.viewer': 'Наблюдатель', - 'journey.invite.editor': 'Редактор', - 'journey.invite.invite': 'Пригласить', - 'journey.invite.inviting': 'Приглашаем...', - 'journey.settings.title': 'Настройки путешествия', - 'journey.settings.coverImage': 'Обложка', - 'journey.settings.changeCover': 'Сменить обложку', - 'journey.settings.addCover': 'Добавить обложку', - 'journey.settings.name': 'Название', - 'journey.settings.subtitle': 'Подзаголовок', - 'journey.settings.subtitlePlaceholder': 'напр. Таиланд, Вьетнам и Камбоджа', - 'journey.settings.endJourney': 'Архивировать путешествие', - 'journey.settings.reopenJourney': 'Восстановить путешествие', - 'journey.settings.archived': 'Путешествие архивировано', - 'journey.settings.reopened': 'Путешествие возобновлено', - 'journey.settings.endDescription': 'Скрывает значок «В эфире». Вы можете возобновить в любое время.', - 'journey.settings.delete': 'Удалить', - 'journey.settings.deleteJourney': 'Удалить путешествие', - 'journey.settings.deleteMessage': 'Удалить «{title}»? Все записи и фото будут потеряны.', - 'journey.settings.saved': 'Настройки сохранены', - 'journey.settings.saveFailed': 'Не удалось сохранить', - 'journey.settings.coverUpdated': 'Обложка обновлена', - 'journey.settings.coverFailed': 'Загрузка не удалась', - 'journey.settings.failedToDelete': 'Не удалось удалить', - 'journey.entries.deleteTitle': 'Удалить запись', - 'journey.photosUploaded': '{count} фото загружено', - 'journey.photosUploadFailed': 'Некоторые фото не удалось загрузить', - 'journey.photosAdded': '{count} фото добавлено', - 'journey.public.notFound': 'Не найдено', - 'journey.public.notFoundMessage': 'Это путешествие не существует или ссылка устарела.', - 'journey.public.readOnly': 'Только для чтения · Публичное путешествие', - 'journey.public.tagline': 'Инструмент планирования и исследования путешествий', - 'journey.public.sharedVia': 'Опубликовано через', - 'journey.public.madeWith': 'Сделано с помощью', - 'journey.pdf.journeyBook': 'Книга путешествия', - 'journey.pdf.madeWith': 'Сделано с помощью TREK', - 'journey.pdf.day': 'День', - 'journey.pdf.theEnd': 'Конец', - 'journey.pdf.saveAsPdf': 'Сохранить как PDF', - 'journey.pdf.pages': 'страниц', - 'journey.picker.tripPeriod': 'Период поездки', - 'journey.picker.dateRange': 'Диапазон дат', - 'journey.picker.allPhotos': 'Все фото', - 'journey.picker.albums': 'Альбомы', - 'journey.picker.selected': 'выбрано', - 'journey.picker.addTo': 'Добавить в', - 'journey.picker.newGallery': 'Новая галерея', - 'journey.picker.selectAll': 'Выбрать все', - 'journey.picker.deselectAll': 'Снять выбор', - 'journey.picker.noAlbums': 'Альбомы не найдены', - 'journey.picker.selectDate': 'Выберите дату', - 'journey.picker.search': 'Поиск', - 'dashboard.greeting.morning': 'Доброе утро,', - 'dashboard.greeting.afternoon': 'Добрый день,', - 'dashboard.greeting.evening': 'Добрый вечер,', - 'dashboard.mobile.liveNow': 'Сейчас в пути', - 'dashboard.mobile.tripProgress': 'Прогресс поездки', - 'dashboard.mobile.daysLeft': 'осталось {count} дн.', - 'dashboard.mobile.places': 'Места', - 'dashboard.mobile.buddies': 'Попутчики', - 'dashboard.mobile.newTrip': 'Новая поездка', - 'dashboard.mobile.currency': 'Валюта', - 'dashboard.mobile.timezone': 'Часовой пояс', - 'dashboard.mobile.upcomingTrips': 'Предстоящие поездки', - 'dashboard.mobile.yourTrips': 'Ваши поездки', - 'dashboard.mobile.trips': 'поездок', - 'dashboard.mobile.starts': 'Начало', - 'dashboard.mobile.duration': 'Продолжительность', - 'dashboard.mobile.day': 'день', - 'dashboard.mobile.days': 'дней', - 'dashboard.mobile.ongoing': 'В процессе', - 'dashboard.mobile.startsToday': 'Начинается сегодня', - 'dashboard.mobile.tomorrow': 'Завтра', - 'dashboard.mobile.inDays': 'Через {count} дн.', - 'dashboard.mobile.inMonths': 'Через {count} мес.', - 'dashboard.mobile.completed': 'Завершено', - 'dashboard.mobile.currencyConverter': 'Конвертер валют', - 'nav.profile': 'Профиль', - 'nav.bottomSettings': 'Настройки', - 'nav.bottomAdmin': 'Администрирование', - 'nav.bottomLogout': 'Выйти', - 'nav.bottomAdminBadge': 'Админ', - 'dayplan.mobile.addPlace': 'Добавить место', - 'dayplan.mobile.searchPlaces': 'Поиск мест...', - 'dayplan.mobile.allAssigned': 'Все места распределены', - 'dayplan.mobile.noMatch': 'Нет совпадений', - 'dayplan.mobile.createNew': 'Создать новое место', - 'admin.addons.catalog.journey.name': 'Путешествие', - 'admin.addons.catalog.journey.description': 'Отслеживание поездок и дневник путешествий с отметками, фото и ежедневными историями', - // OAuth scope groups - 'oauth.scope.group.trips': 'Поездки', - 'oauth.scope.group.places': 'Места', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Вещи', - 'oauth.scope.group.todos': 'Задачи', - 'oauth.scope.group.budget': 'Бюджет', - 'oauth.scope.group.reservations': 'Бронирования', - 'oauth.scope.group.collab': 'Сотрудничество', - 'oauth.scope.group.notifications': 'Уведомления', - 'oauth.scope.group.vacay': 'Отпуск', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Погода', - 'oauth.scope.group.journey': 'Путешествия', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Просмотр поездок и маршрутов', - 'oauth.scope.trips:read.description': 'Чтение поездок, дней, заметок и участников', - 'oauth.scope.trips:write.label': 'Редактирование поездок и маршрутов', - 'oauth.scope.trips:write.description': 'Создание и обновление поездок, дней, заметок и управление участниками', - 'oauth.scope.trips:delete.label': 'Удаление поездок', - 'oauth.scope.trips:delete.description': 'Безвозвратное удаление поездок — это действие необратимо', - 'oauth.scope.trips:share.label': 'Управление ссылками на совместный доступ', - 'oauth.scope.trips:share.description': 'Создание, обновление и отзыв публичных ссылок на поездки', - 'oauth.scope.places:read.label': 'Просмотр мест и данных карты', - 'oauth.scope.places:read.description': 'Чтение мест, назначений по дням, тегов и категорий', - 'oauth.scope.places:write.label': 'Управление местами', - 'oauth.scope.places:write.description': 'Создание, обновление и удаление мест, назначений и тегов', - 'oauth.scope.atlas:read.label': 'Просмотр Atlas', - 'oauth.scope.atlas:read.description': 'Чтение посещённых стран, регионов и списка желаний', - 'oauth.scope.atlas:write.label': 'Управление Atlas', - 'oauth.scope.atlas:write.description': 'Отмечать посещённые страны и регионы, управлять списком желаний', - 'oauth.scope.packing:read.label': 'Просмотр списков вещей', - 'oauth.scope.packing:read.description': 'Чтение вещей, сумок и назначений категорий', - 'oauth.scope.packing:write.label': 'Управление списками вещей', - 'oauth.scope.packing:write.description': 'Добавление, обновление, удаление, отметка и переупорядочивание вещей и сумок', - 'oauth.scope.todos:read.label': 'Просмотр списков задач', - 'oauth.scope.todos:read.description': 'Чтение задач поездки и назначений категорий', - 'oauth.scope.todos:write.label': 'Управление списками задач', - 'oauth.scope.todos:write.description': 'Создание, обновление, отметка, удаление и переупорядочивание задач', - 'oauth.scope.budget:read.label': 'Просмотр бюджета', - 'oauth.scope.budget:read.description': 'Чтение статей бюджета и разбивки расходов', - 'oauth.scope.budget:write.label': 'Управление бюджетом', - 'oauth.scope.budget:write.description': 'Создание, обновление и удаление статей бюджета', - 'oauth.scope.reservations:read.label': 'Просмотр бронирований', - 'oauth.scope.reservations:read.description': 'Чтение бронирований и сведений о проживании', - 'oauth.scope.reservations:write.label': 'Управление бронированиями', - 'oauth.scope.reservations:write.description': 'Создание, обновление, удаление и переупорядочивание бронирований', - 'oauth.scope.collab:read.label': 'Просмотр совместной работы', - 'oauth.scope.collab:read.description': 'Чтение совместных заметок, опросов и сообщений', - 'oauth.scope.collab:write.label': 'Управление совместной работой', - 'oauth.scope.collab:write.description': 'Создание, обновление и удаление заметок, опросов и сообщений', - 'oauth.scope.notifications:read.label': 'Просмотр уведомлений', - 'oauth.scope.notifications:read.description': 'Чтение уведомлений в приложении и количества непрочитанных', - 'oauth.scope.notifications:write.label': 'Управление уведомлениями', - 'oauth.scope.notifications:write.description': 'Отмечать уведомления как прочитанные и отвечать на них', - 'oauth.scope.vacay:read.label': 'Просмотр планов отпуска', - 'oauth.scope.vacay:read.description': 'Чтение данных планирования отпуска, записей и статистики', - 'oauth.scope.vacay:write.label': 'Управление планами отпуска', - 'oauth.scope.vacay:write.description': 'Создание и управление записями отпуска, праздниками и командными планами', - 'oauth.scope.geo:read.label': 'Карты и геокодирование', - 'oauth.scope.geo:read.description': 'Поиск мест, разрешение URL карт и обратное геокодирование координат', - 'oauth.scope.weather:read.label': 'Прогнозы погоды', - 'oauth.scope.weather:read.description': 'Получение прогнозов погоды для мест и дат поездки', - 'oauth.scope.journey:read.label': 'Просмотр путешествий', - 'oauth.scope.journey:read.description': 'Чтение путешествий, записей и списка участников', - 'oauth.scope.journey:write.label': 'Управление путешествиями', - 'oauth.scope.journey:write.description': 'Создание, обновление и удаление путешествий и их записей', - 'oauth.scope.journey:share.label': 'Управление ссылками на путешествия', - 'oauth.scope.journey:share.description': 'Создание, обновление и отзыв публичных ссылок для путешествий', - - // System notices - 'system_notice.welcome_v1.title': 'Добро пожаловать в TREK', - 'system_notice.welcome_v1.body': 'Ваш универсальный планировщик путешествий. Создавайте маршруты, делитесь поездками с друзьями и оставайтесь организованными — онлайн и офлайн.', - 'system_notice.welcome_v1.cta_label': 'Спланировать поездку', - 'system_notice.welcome_v1.hero_alt': 'Живописное место назначения с интерфейсом TREK', - 'system_notice.welcome_v1.highlight_plan': 'Маршруты по дням', - 'system_notice.welcome_v1.highlight_share': 'Совместное планирование с партнёрами', - 'system_notice.welcome_v1.highlight_offline': 'Работает офлайн на мобильном', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Предыдущее уведомление', - 'system_notice.pager.next': 'Следующее уведомление', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Перейти к уведомлению {n}', - 'system_notice.pager.position': 'Уведомление {current} из {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Фото перемещены в версии 3.0', - 'system_notice.v3_photos.body': 'Вкладка **Фото** в Планировщике путешествий удалена. Ваши фото в безопасности — TREK никогда не изменял вашу библиотеку Immich или Synology.\n\nФото теперь доступны в дополнении **Journey**. Journey необязателен — если он ещё недоступен, попросите администратора включить его в разделе Admin → Дополнения.', - 'system_notice.v3_journey.title': 'Знакомьтесь с Journey', - 'system_notice.v3_journey.body': 'Документируйте путешествия в виде рассказов с хронологиями, фотогалереями и интерактивными картами.', - 'system_notice.v3_journey.cta_label': 'Открыть Journey', - 'system_notice.v3_journey.highlight_timeline': 'Ежедневная хронология и галерея', - 'system_notice.v3_journey.highlight_photos': 'Импорт из Immich или Synology', - 'system_notice.v3_journey.highlight_share': 'Общий доступ — без входа', - 'system_notice.v3_journey.highlight_export': 'Экспорт в PDF-фотокнигу', - 'system_notice.v3_features.title': 'Ещё нового в версии 3.0', - 'system_notice.v3_features.body': 'Несколько других важных новшеств в этом релизе.', - 'system_notice.v3_features.highlight_dashboard': 'Переработанная панель в mobile-first стиле', - 'system_notice.v3_features.highlight_offline': 'Полный офлайн-режим как PWA', - 'system_notice.v3_features.highlight_search': 'Автодополнение поиска мест в реальном времени', - 'system_notice.v3_features.highlight_import': 'Импорт мест из KMZ/KML-файлов', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: обновление OAuth 2.1', - 'system_notice.v3_mcp.body': 'Интеграция MCP была полностью переработана. OAuth 2.1 теперь является рекомендуемым методом аутентификации. Статические токены (trek_…) устарели и будут удалены в будущей версии.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 рекомендуется (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 детальных области разрешений', - 'system_notice.v3_mcp.highlight_deprecated': 'Статические токены trek_ устарели', - 'system_notice.v3_mcp.highlight_tools': 'Расширенный набор инструментов', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Личное слово от меня', - 'system_notice.v3_thankyou.body': 'Прежде чем продолжить — хочу остановиться на мгновение.\n\nTREK начинался как сторонний проект, который я создал для собственных поездок. Я никогда не думал, что он вырастет во что-то, чему 4 000 из вас доверяют планирование своих приключений. Каждая звёздочка, каждый issue, каждый запрос на фичу — я читаю их все, и именно они поддерживают меня в поздние ночи между основной работой и университетом.\n\nХочу, чтобы вы знали: TREK всегда будет open source, всегда self-hosted, всегда вашим. Никакого отслеживания, никаких подписок, никаких подвохов. Просто инструмент, созданный человеком, который любит путешествовать так же, как и вы.\n\nОсобая благодарность [jubnl](https://github.com/jubnl) — ты стал невероятным соратником. Многое из того, что делает версию 3.0 великолепной, несёт твой отпечаток. Спасибо, что поверил в этот проект, когда он был ещё сырым.\n\nИ каждому из вас, кто сообщил об ошибке, перевёл строку, поделился TREK с другом или просто использовал его для планирования поездки — **спасибо**. Вы — причина, по которой всё это существует.\n\nЗа множество новых приключений вместе.\n\n— Maurice\n\n---\n\n[Присоединяйся к сообществу в Discord](https://discord.gg/7Q6M6jDwzf)\n\nЕсли TREK делает твои путешествия лучше, [маленький кофе](https://ko-fi.com/mauriceboe) всегда помогает держать свет включённым.', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Требуется действие: конфликт учётных записей', - 'system_notice.v3014_whitespace_collision.body': 'Обновление 3.0.14 обнаружило один или несколько конфликтов имён пользователей или адресов электронной почты, вызванных ведущими или завершающими пробелами в сохранённых значениях. Затронутые учётные записи были автоматически переименованы. Проверьте логи сервера на строки, начинающиеся с **[migration] WHITESPACE COLLISION**, чтобы определить учётные записи, требующие проверки.', - 'transport.addTransport': 'Добавить транспорт', - 'transport.modalTitle.create': 'Добавить транспорт', - 'transport.modalTitle.edit': 'Изменить транспорт', - 'transport.title': 'Транспорт', - 'transport.addManual': 'Ручной транспорт', -} - -export default ru - diff --git a/client/src/i18n/translations/tr.ts b/client/src/i18n/translations/tr.ts deleted file mode 100644 index d2637fb0..00000000 --- a/client/src/i18n/translations/tr.ts +++ /dev/null @@ -1,2431 +0,0 @@ -const tr: Record = { - // Common - 'common.save': 'Kaydet', - 'common.showMore': 'Daha fazla göster', - 'common.showLess': 'Daha az göster', - 'common.cancel': 'İptal', - 'common.clear': 'Temizle', - 'common.delete': 'Sil', - 'common.edit': 'Düzenle', - 'common.add': 'Ekle', - 'common.loading': 'Yükleniyor...', - 'common.import': 'İçe aktar', - 'common.select': 'Seç', - 'common.selectAll': 'Tümünü seç', - 'common.deselectAll': 'Seçimi kaldır', - 'common.error': 'Hata', - 'common.unknownError': 'Bilinmeyen hata', - 'common.tooManyAttempts': 'Çok fazla deneme. Lütfen daha sonra tekrar deneyin.', - 'common.back': 'Geri', - 'common.all': 'Tümü', - 'common.close': 'Kapat', - 'common.open': 'Aç', - 'common.upload': 'Yükle', - 'common.search': 'Ara', - 'common.confirm': 'Onayla', - 'common.ok': 'OK', - 'common.yes': 'Evet', - 'common.no': 'Hayır', - 'common.or': 'veya', - 'common.none': 'Yok', - 'common.date': 'Tarih', - 'common.rename': 'Yeniden adlandır', - 'common.discardChanges': 'Değişiklikleri iptal et', - 'common.discard': 'Vazgeç', - 'common.name': 'Ad', - 'common.email': 'E-posta', - 'common.password': 'Parola', - 'common.saving': 'Kaydediliyor...', - 'common.justNow': 'az önce', - 'common.hoursAgo': '{count}s önce', - 'common.daysAgo': '{count}g önce', - 'common.saved': 'Kaydedildi', - 'trips.memberRemoved': '{username} kaldırıldı', - 'trips.memberRemoveError': 'Kaldırılamadı', - 'trips.memberAdded': '{username} eklendi', - 'trips.memberAddError': 'Eklenemedi', - 'trips.reminder': 'Hatırlatma', - 'trips.reminderNone': 'Yok', - 'trips.reminderDay': 'gün', - 'trips.reminderDays': 'gün', - 'trips.reminderCustom': 'Özel', - 'trips.reminderDaysBefore': 'kalkıştan önceki gün sayısı', - 'trips.reminderDisabledHint': 'Gezi hatırlatmaları devre dışı. Yönetim > Ayarlar > Bildirimler bölümünden etkinleştirin.', - 'common.update': 'Güncelle', - 'common.change': 'Değiştir', - 'common.uploading': 'Yükleniyor…', - 'common.backToPlanning': 'Planlamaya dön', - 'common.reset': 'Sıfırla', - 'common.expand': 'Genişlet', - 'common.collapse': 'Daralt', - - // Navbar - 'nav.trip': 'Gezi', - 'nav.share': 'Paylaş', - 'nav.settings': 'Ayarlar', - 'nav.admin': 'Yönetim', - 'nav.logout': 'Çıkış yap', - 'nav.lightMode': 'Açık tema', - 'nav.darkMode': 'Koyu tema', - 'nav.autoMode': 'Otomatik tema', - 'nav.administrator': 'Yönetici', - - // Dashboard - 'dashboard.title': 'Gezilerim', - 'dashboard.subtitle.loading': 'Geziler yükleniyor...', - 'dashboard.subtitle.trips': '{count} gezi ({archived} arşivlenmiş)', - 'dashboard.subtitle.empty': 'İlk gezinizi başlatın', - 'dashboard.subtitle.activeOne': '{count} aktif gezi', - 'dashboard.subtitle.activeMany': '{count} aktif gezi', - 'dashboard.subtitle.archivedSuffix': ' · {count} arşivlendi', - 'dashboard.newTrip': 'Yeni gezi', - 'dashboard.gridView': 'Izgara görünümü', - 'dashboard.listView': 'Liste görünümü', - 'dashboard.currency': 'Para birimi', - 'dashboard.timezone': 'Saat dilimleri', - 'dashboard.localTime': 'Yerel', - 'dashboard.timezoneCustomTitle': 'Özel saat dilimi', - 'dashboard.timezoneCustomLabelPlaceholder': 'Etiket (opsiyonel)', - 'dashboard.timezoneCustomTzPlaceholder': 'örn. Europe/Istanbul', - 'dashboard.timezoneCustomAdd': 'Ekle', - 'dashboard.timezoneCustomErrorEmpty': 'Bir saat dilimi kimliği girin', - 'dashboard.timezoneCustomErrorInvalid': 'Geçersiz saat dilimi. Europe/Istanbul gibi bir format kullanın', - 'dashboard.timezoneCustomErrorDuplicate': 'Zaten eklendi', - 'dashboard.emptyTitle': 'Henüz gezi yok', - 'dashboard.emptyText': 'İlk gezinizi oluşturun ve planlamaya başlayın!', - 'dashboard.emptyButton': 'İlk geziyi oluştur', - 'dashboard.nextTrip': 'Sıradaki gezi', - 'dashboard.shared': 'Paylaşılan', - 'dashboard.sharedBy': '{name} tarafından paylaşıldı', - 'dashboard.days': 'Gün', - 'dashboard.places': 'Yer', - 'dashboard.members': 'Arkadaş', - 'dashboard.archive': 'Arşivle', - 'dashboard.copyTrip': 'Kopyala', - 'dashboard.copySuffix': 'kopya', - 'dashboard.restore': 'Geri yükle', - 'dashboard.archived': 'Arşivlendi', - 'dashboard.status.ongoing': 'Devam ediyor', - 'dashboard.status.today': 'Bugün', - 'dashboard.status.tomorrow': 'Yarın', - 'dashboard.status.past': 'Geçmiş', - 'dashboard.status.daysLeft': '{count} gün kaldı', - 'dashboard.toast.loadError': 'Failed to load trips', - 'dashboard.toast.created': 'Trip created successfully!', - 'dashboard.toast.createError': 'Failed to create trip', - 'dashboard.toast.updated': 'Trip updated!', - 'dashboard.toast.updateError': 'Failed to update trip', - 'dashboard.toast.deleted': 'Trip deleted', - 'dashboard.toast.deleteError': 'Failed to delete trip', - 'dashboard.toast.archived': 'Trip archived', - 'dashboard.toast.archiveError': 'Failed to archive trip', - 'dashboard.toast.restored': 'Trip restored', - 'dashboard.toast.restoreError': 'Failed to restore trip', - 'dashboard.toast.copied': 'Trip copied!', - 'dashboard.toast.copyError': 'Failed to copy trip', - 'dashboard.confirm.delete': 'Delete trip "{title}"? All places and plans will be permanently deleted.', - 'dashboard.confirm.copy.title': 'Copy this trip?', - 'dashboard.confirm.copy.willCopy': 'Will be copied', - 'dashboard.confirm.copy.will1': 'Days, places & day assignments', - 'dashboard.confirm.copy.will2': 'Accommodations & reservations', - 'dashboard.confirm.copy.will3': 'Budget items & category order', - 'dashboard.confirm.copy.will4': 'Packing lists (unchecked)', - 'dashboard.confirm.copy.will5': 'TODOs (unassigned & unchecked)', - 'dashboard.confirm.copy.will6': 'Day notes', - 'dashboard.confirm.copy.wontCopy': "Won't be copied", - 'dashboard.confirm.copy.wont1': 'Collaborators & member assignments', - 'dashboard.confirm.copy.wont2': 'Collab notes, polls & messages', - 'dashboard.confirm.copy.wont3': 'Files & photos', - 'dashboard.confirm.copy.wont4': 'Share tokens', - 'dashboard.confirm.copy.confirm': 'Copy trip', - 'dashboard.editTrip': 'Geziyi düzenle', - 'dashboard.createTrip': 'Yeni gezi oluştur', - 'dashboard.tripTitle': 'Başlık', - 'dashboard.tripTitlePlaceholder': 'e.g. Summer in Japan', - 'dashboard.tripDescription': 'Açıklama', - 'dashboard.tripDescriptionPlaceholder': 'What is this trip about?', - 'dashboard.startDate': 'Başlangıç tarihi', - 'dashboard.endDate': 'Bitiş tarihi', - 'dashboard.dayCount': 'Gün sayısı', - 'dashboard.dayCountHint': 'How many days to plan for when no travel dates are set.', - 'dashboard.noDateHint': 'No date set — 7 default days will be created. You can change this anytime.', - 'dashboard.coverImage': 'Kapak resmi', - 'dashboard.addCoverImage': 'Add cover image (or drag & drop)', - 'dashboard.addMembers': 'Seyahat arkadaşları', - 'dashboard.addMember': 'Üye ekle', - 'dashboard.coverSaved': 'Cover image saved', - 'dashboard.coverUploadError': 'Failed to upload', - 'dashboard.coverRemoveError': 'Failed to remove', - 'dashboard.titleRequired': 'Başlık zorunludur', - 'dashboard.endDateError': 'Bitiş tarihi başlangıç tarihinden sonra olmalıdır', - - // Settings - 'settings.title': 'Ayarlar', - 'settings.subtitle': 'Kişisel ayarlarınızı yapılandırın', - 'settings.tabs.display': 'Görünüm', - 'settings.tabs.map': 'Harita', - 'settings.tabs.notifications': 'Bildirimler', - 'settings.tabs.integrations': 'Entegrasyonlar', - 'settings.tabs.account': 'Hesap', - 'settings.tabs.offline': 'Çevrimdışı', - 'settings.tabs.about': 'Hakkında', - 'settings.map': 'Map', - 'settings.mapTemplate': 'Map Template', - 'settings.mapTemplatePlaceholder.select': 'Select template...', - 'settings.mapDefaultHint': 'Leave empty for OpenStreetMap (default)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL template for map tiles', - 'settings.mapProvider': 'Map Provider', - 'settings.mapProviderHint': 'Affects Trip Planner and Journey maps. Atlas always uses Leaflet.', - 'settings.mapLeafletSubtitle': 'Classic 2D, any raster tiles', - 'settings.mapMapboxSubtitle': 'Vector tiles, 3D buildings & terrain', - 'settings.mapExperimental': 'Experimental', - 'settings.mapMapboxToken': 'Mapbox Access Token', - 'settings.mapMapboxTokenHint': 'Public token (pk.*) from', - 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', - 'settings.mapStyle': 'Map Style', - 'settings.mapStylePlaceholder': 'Select a Mapbox style', - 'settings.mapStyleHint': 'Preset or your own mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D Buildings & Terrain', - 'settings.map3dHint': 'Pitch + real 3D building extrusions — works on every style, including satellite.', - 'settings.mapHighQuality': 'High Quality Mode', - 'settings.mapHighQualityHint': 'Antialiasing + globe projection for sharper edges and a realistic world view.', - 'settings.mapHighQualityWarning': 'May impact performance on lower-end devices.', - 'settings.mapTipLabel': 'Tip:', - 'settings.mapTip': 'right-click and drag to rotate/pitch the map. Middle-click to add a place (right-click is reserved for rotation).', - 'settings.latitude': 'Latitude', - 'settings.longitude': 'Longitude', - 'settings.saveMap': 'Save Map', - 'settings.apiKeys': 'API Keys', - 'settings.mapsKey': 'Google Maps API Key', - 'settings.mapsKeyHint': 'For place search. Requires Places API (New). Get at console.cloud.google.com', - 'settings.weatherKey': 'OpenWeatherMap API Key', - 'settings.weatherKeyHint': 'For weather data. Free at openweathermap.org/api', - 'settings.keyPlaceholder': 'Enter key...', - 'settings.configured': 'Configured', - 'settings.saveKeys': 'Save Keys', - 'settings.display': 'Görünüm', - 'settings.colorMode': 'Tema modu', - 'settings.light': 'Açık', - 'settings.dark': 'Koyu', - 'settings.auto': 'Otomatik', - 'settings.language': 'Dil', - 'settings.temperature': 'Sıcaklık birimi', - 'settings.timeFormat': 'Saat biçimi', - 'settings.bookingLabels': 'Booking route labels', - 'settings.bookingLabelsHint': 'Show station / airport names on the map. When off, only the icon is shown.', - 'settings.blurBookingCodes': 'Blur Booking Codes', - 'settings.notifications': 'Bildirimler', - 'settings.notifyTripInvite': 'Trip invitations', - 'settings.notifyBookingChange': 'Booking changes', - 'settings.notifyTripReminder': 'Trip reminders', - 'settings.notifyTodoDue': 'Todo due soon', - 'settings.notifyVacayInvite': 'Vacay fusion invitations', - 'settings.notifyPhotosShared': 'Shared photos (Immich)', - 'settings.notifyCollabMessage': 'Chat messages (Collab)', - 'settings.notifyPackingTagged': 'Packing list: assignments', - 'settings.notifyWebhook': 'Webhook notifications', - 'settings.notifyVersionAvailable': 'New version available', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': 'No notification channels are configured. Ask an admin to set up email or webhook notifications.', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Enter your Discord, Slack, or custom webhook URL to receive notifications.', - 'settings.webhookUrl.saved': 'Webhook URL saved', - 'settings.webhookUrl.test': 'Test', - 'settings.webhookUrl.testSuccess': 'Test webhook sent successfully', - 'settings.webhookUrl.testFailed': 'Test webhook failed', - 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', - 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', - 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', - 'settings.ntfyUrl.saved': 'Ntfy settings saved', - 'settings.ntfyUrl.test': 'Test', - 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', - 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', - 'settings.ntfyUrl.tokenCleared': 'Access token cleared', - 'admin.notifications.title': 'Notifications', - 'admin.notifications.hint': 'Choose one notification channel. Only one can be active at a time.', - 'admin.notifications.none': 'Disabled', - 'admin.notifications.email': 'Email (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Allow users to configure their own ntfy topics for push notifications. Set the default server below to pre-fill user settings.', - 'admin.notifications.save': 'Save notification settings', - 'admin.notifications.saved': 'Notification settings saved', - 'admin.notifications.testWebhook': 'Send test webhook', - 'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully', - 'admin.notifications.testWebhookFailed': 'Test webhook failed', - 'admin.notifications.testNtfy': 'Send test ntfy', - 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', - 'admin.notifications.testNtfyFailed': 'Test ntfy failed', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'In-app notifications are always active and cannot be disabled globally.', - 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', - 'admin.notifications.adminWebhookPanel.hint': 'This webhook is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user webhooks and always fires when set.', - 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL saved', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook sent successfully', - 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook always fires when a URL is configured', - 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', - 'admin.notifications.adminNtfyPanel.hint': 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', - 'admin.notifications.adminNtfyPanel.serverHint': 'Also used as the default server for user ntfy notifications. Leave blank to default to ntfy.sh. Users can override this in their own settings.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Admin access token cleared', - 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', - 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Test ntfy sent successfully', - 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Admin ntfy always fires when a topic is configured', - 'admin.notifications.adminNotificationsHint': 'Configure which channels deliver admin-only notifications (e.g. version alerts).', - 'admin.notifications.tripReminders.title': 'Trip Reminders', - 'admin.notifications.tripReminders.hint': 'Send a reminder notification before a trip starts (requires reminder days to be set on the trip).', - 'admin.notifications.tripReminders.enabled': 'Trip reminders enabled', - 'admin.notifications.tripReminders.disabled': 'Trip reminders disabled', - 'admin.smtp.title': 'Email & Notifications', - 'admin.smtp.hint': 'SMTP configuration for sending email notifications.', - 'admin.smtp.testButton': 'Send test email', - 'admin.webhook.hint': 'Allow users to configure their own webhook URLs for notifications (Discord, Slack, etc.).', - 'admin.smtp.testSuccess': 'Test email sent successfully', - 'admin.smtp.testFailed': 'Test email failed', - 'settings.notificationsDisabled': 'Notifications are not configured. Ask an admin to enable email or webhook notifications.', - 'settings.notificationsActive': 'Active channel', - 'settings.notificationsManagedByAdmin': 'Notification events are configured by your administrator.', - 'dayplan.icsTooltip': 'Export calendar (ICS)', - 'share.linkTitle': 'Public Link', - 'share.linkHint': 'Create a link anyone can use to view this trip without logging in. Read-only — no editing possible.', - 'share.createLink': 'Create link', - 'share.deleteLink': 'Delete link', - 'share.createError': 'Could not create link', - 'common.copy': 'Copy', - 'common.copied': 'Copied', - 'share.permMap': 'Map & Plan', - 'share.permBookings': 'Bookings', - 'share.permPacking': 'Packing', - 'shared.expired': 'Link expired or invalid', - 'shared.expiredHint': 'This shared trip link is no longer active.', - 'shared.readOnly': 'Read-only shared view', - 'shared.tabPlan': 'Plan', - 'shared.tabBookings': 'Bookings', - 'shared.tabPacking': 'Packing', - 'shared.tabBudget': 'Budget', - 'shared.tabChat': 'Chat', - 'shared.days': 'days', - 'shared.places': 'places', - 'shared.other': 'Other', - 'shared.totalBudget': 'Total Budget', - 'shared.messages': 'messages', - 'shared.sharedVia': 'Shared via', - 'shared.confirmed': 'Confirmed', - 'shared.pending': 'Pending', - 'share.permBudget': 'Budget', - 'share.permCollab': 'Chat', - 'settings.on': 'On', - 'settings.off': 'Off', - 'settings.mcp.title': 'MCP Configuration', - 'settings.mcp.endpoint': 'MCP Endpoint', - 'settings.mcp.clientConfig': 'Client Configuration', - 'settings.mcp.clientConfigHint': 'Replace with an API token from the list below. The path to npx may need to be adjusted for your system (e.g. C:\\PROGRA~1\\nodejs\\npx.cmd on Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:\PROGRA~1\nodejs\npx.cmd on Windows).', - 'settings.mcp.copy': 'Copy', - 'settings.mcp.copied': 'Copied!', - 'settings.mcp.apiTokens': 'API Tokens', - 'settings.mcp.createToken': 'Create New Token', - 'settings.mcp.noTokens': 'No tokens yet. Create one to connect MCP clients.', - 'settings.mcp.tokenCreatedAt': 'Created', - 'settings.mcp.tokenUsedAt': 'Used', - 'settings.mcp.deleteTokenTitle': 'Delete Token', - 'settings.mcp.deleteTokenMessage': 'This token will stop working immediately. Any MCP client using it will lose access.', - 'settings.mcp.modal.createTitle': 'Create API Token', - 'settings.mcp.modal.tokenName': 'Token Name', - 'settings.mcp.modal.tokenNamePlaceholder': 'e.g. Claude Desktop, Work laptop', - 'settings.mcp.modal.creating': 'Creating…', - 'settings.mcp.modal.create': 'Create Token', - 'settings.mcp.modal.createdTitle': 'Token Created', - 'settings.mcp.modal.createdWarning': 'This token will only be shown once. Copy and store it now — it cannot be recovered.', - 'settings.mcp.modal.done': 'Done', - 'settings.mcp.toast.created': 'Token created', - 'settings.mcp.toast.createError': 'Failed to create token', - 'settings.mcp.toast.deleted': 'Token deleted', - 'settings.mcp.toast.deleteError': 'Failed to delete token', - 'settings.mcp.apiTokensDeprecated': 'API Tokens are deprecated and will be removed in a future release. Please use OAuth 2.1 Clients instead.', - 'settings.oauth.clients': 'OAuth 2.1 Clients', - 'settings.oauth.clientsHint': 'Register OAuth 2.1 clients to let third-party MCP applications (Claude Web, Cursor, etc.) connect without static tokens.', - 'settings.oauth.createClient': 'New Client', - 'settings.oauth.noClients': 'No OAuth clients registered.', - 'settings.oauth.clientId': 'Client ID', - 'settings.oauth.clientSecret': 'Client Secret', - 'settings.oauth.deleteClient': 'Delete Client', - 'settings.oauth.deleteClientMessage': 'This client and all active sessions will be permanently removed. Any application using it will lose access immediately.', - 'settings.oauth.rotateSecret': 'Rotate Secret', - 'settings.oauth.rotateSecretMessage': 'A new client secret will be generated and all existing sessions will be invalidated immediately. Update your application before closing this dialog.', - 'settings.oauth.rotateSecretConfirm': 'Rotate', - 'settings.oauth.rotateSecretConfirming': 'Rotating…', - 'settings.oauth.rotateSecretDoneTitle': 'New Secret Generated', - 'settings.oauth.rotateSecretDoneWarning': 'This secret is shown only once. Copy it now and update your application — all previous sessions have been invalidated.', - 'settings.oauth.activeSessions': 'Active OAuth Sessions', - 'settings.oauth.sessionScopes': 'Scopes', - 'settings.oauth.sessionExpires': 'Expires', - 'settings.oauth.revoke': 'Revoke', - 'settings.oauth.revokeSession': 'Revoke Session', - 'settings.oauth.revokeSessionMessage': 'This will immediately revoke access for this OAuth session.', - 'settings.oauth.modal.createTitle': 'Register OAuth Client', - 'settings.oauth.modal.presets': 'Quick presets', - 'settings.oauth.modal.clientName': 'Application Name', - 'settings.oauth.modal.clientNamePlaceholder': 'e.g. Claude Web, My MCP App', - 'settings.oauth.modal.redirectUris': 'Redirect URIs', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.', - 'settings.oauth.modal.scopes': 'Allowed Scopes', - 'settings.oauth.modal.scopesHint': 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.', - 'settings.oauth.modal.selectAll': 'Select all', - 'settings.oauth.modal.deselectAll': 'Deselect all', - 'settings.oauth.modal.creating': 'Registering…', - 'settings.oauth.modal.create': 'Register Client', - 'settings.oauth.modal.createdTitle': 'Client Registered', - 'settings.oauth.modal.createdWarning': 'The client secret is shown only once. Copy it now — it cannot be recovered.', - 'settings.oauth.toast.createError': 'Failed to register OAuth client', - 'settings.oauth.toast.deleted': 'OAuth client deleted', - 'settings.oauth.toast.deleteError': 'Failed to delete OAuth client', - 'settings.oauth.toast.revoked': 'Session revoked', - 'settings.oauth.toast.revokeError': 'Failed to revoke session', - 'settings.oauth.toast.rotateError': 'Failed to rotate client secret', - 'settings.account': 'Hesap', - 'settings.about': 'Hakkında', - 'settings.about.reportBug': 'Report a Bug', - 'settings.about.reportBugHint': 'Found an issue? Let us know', - 'settings.about.featureRequest': 'Feature Request', - 'settings.about.featureRequestHint': 'Suggest a new feature', - 'settings.about.wikiHint': 'Documentation & guides', - 'settings.about.supporters.badge': 'Monthly Supporters', - 'settings.about.supporters.title': 'Travel companions for TREK', - 'settings.about.supporters.subtitle': "While you're planning your next route, these folks are helping plan TREK's future. Their monthly contribution goes straight into development and real hours spent — so TREK stays Open Source.", - 'settings.about.supporters.since': 'supporter since {date}', - 'settings.about.supporters.tierEmpty': 'Be the first', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK is a self-hosted travel planner that helps you organize your trips from the first idea to the last memory. Day planning, budget, packing lists, photos and much more — all in one place, on your own server.', - 'settings.about.madeWith': 'Made with', - 'settings.about.madeBy': 'by Maurice and a growing open-source community.', - 'settings.username': 'Kullanıcı adı', - 'settings.email': 'Email', - 'settings.role': 'Rol', - 'settings.roleAdmin': 'Yönetici', - 'settings.oidcLinked': 'Linked with', - 'settings.changePassword': 'Parolayı değiştir', - 'settings.currentPassword': 'Mevcut parola', - 'settings.currentPasswordRequired': 'Current password is required', - 'settings.newPassword': 'Yeni parola', - 'settings.confirmPassword': 'Yeni parolayı doğrula', - 'settings.updatePassword': 'Parolayı güncelle', - 'settings.passwordRequired': 'Please enter current and new password', - 'settings.passwordTooShort': 'Password must be at least 8 characters', - 'settings.passwordMismatch': 'Passwords do not match', - 'settings.passwordWeak': 'Password must contain uppercase, lowercase, a number, and a special character', - 'settings.passwordChanged': 'Password changed successfully', - 'settings.mustChangePassword': 'You must change your password before you can continue. Please set a new password below.', - 'settings.deleteAccount': 'Hesabı sil', - 'settings.deleteAccountTitle': 'Delete your account?', - 'settings.deleteAccountWarning': 'Your account and all your trips, places, and files will be permanently deleted. This action cannot be undone.', - 'settings.deleteAccountConfirm': 'Delete permanently', - 'settings.deleteBlockedTitle': 'Deletion not possible', - 'settings.deleteBlockedMessage': 'You are the only administrator. Promote another user to admin before deleting your account.', - 'settings.roleUser': 'Kullanıcı', - 'settings.saveProfile': 'Profili kaydet', - 'settings.toast.mapSaved': 'Map settings saved', - 'settings.toast.keysSaved': 'API keys saved', - 'settings.toast.displaySaved': 'Display settings saved', - 'settings.toast.profileSaved': 'Profile saved', - 'settings.uploadAvatar': 'Upload Profile Picture', - 'settings.removeAvatar': 'Remove Profile Picture', - 'settings.avatarUploaded': 'Profile picture updated', - 'settings.avatarRemoved': 'Profile picture removed', - 'settings.avatarError': 'Upload failed', - 'settings.mfa.title': 'Two-factor authentication (2FA)', - 'settings.mfa.description': 'Adds a second step when you sign in with email and password. Use an authenticator app (Google Authenticator, Authy, etc.).', - 'settings.mfa.requiredByPolicy': 'Your administrator requires two-factor authentication. Set up an authenticator app below before continuing.', - 'settings.mfa.backupTitle': 'Backup codes', - 'settings.mfa.backupDescription': 'Use these one-time backup codes if you lose access to your authenticator app.', - 'settings.mfa.backupWarning': 'Save these codes now. Each code can only be used once.', - 'settings.mfa.backupCopy': 'Copy codes', - 'settings.mfa.backupDownload': 'Download TXT', - 'settings.mfa.backupPrint': 'Print / PDF', - 'settings.mfa.backupCopied': 'Backup codes copied', - 'settings.mfa.enabled': '2FA is enabled on your account.', - 'settings.mfa.disabled': '2FA is not enabled.', - 'settings.mfa.setup': 'Set up authenticator', - 'settings.mfa.scanQr': 'Scan this QR code with your app, or enter the secret manually.', - 'settings.mfa.secretLabel': 'Secret key (manual entry)', - 'settings.mfa.codePlaceholder': '6-digit code', - 'settings.mfa.enable': 'Enable 2FA', - 'settings.mfa.cancelSetup': 'Cancel', - 'settings.mfa.disableTitle': 'Disable 2FA', - 'settings.mfa.disableHint': 'Enter your account password and a current code from your authenticator.', - 'settings.mfa.disable': 'Disable 2FA', - 'settings.mfa.toastEnabled': 'Two-factor authentication enabled', - 'settings.mfa.toastDisabled': 'Two-factor authentication disabled', - 'settings.mfa.demoBlocked': 'Not available in demo mode', - - // Login - 'login.error': 'Login failed. Please check your credentials.', - 'login.tagline': 'Your Trips.\nYour Plan.', - 'login.description': 'Plan trips collaboratively with interactive maps, budgets, and real-time sync.', - 'login.features.maps': 'Interactive Maps', - 'login.features.mapsDesc': 'Google Places, routes & clustering', - 'login.features.realtime': 'Real-Time Sync', - 'login.features.realtimeDesc': 'Plan together via WebSocket', - 'login.features.budget': 'Budget Tracking', - 'login.features.budgetDesc': 'Categories, charts & per-person costs', - 'login.features.collab': 'Collaboration', - 'login.features.collabDesc': 'Multi-user with shared trips', - 'login.features.packing': 'Packing Lists', - 'login.features.packingDesc': 'Categories, progress & suggestions', - 'login.features.bookings': 'Reservations', - 'login.features.bookingsDesc': 'Flights, hotels, restaurants & more', - 'login.features.files': 'Documents', - 'login.features.filesDesc': 'Upload & manage documents', - 'login.features.routes': 'Smart Routes', - 'login.features.routesDesc': 'Auto-optimize & Google Maps export', - 'login.selfHosted': 'Self-hosted \u00B7 Open Source \u00B7 Your data stays yours', - 'login.title': 'Giriş yap', - 'login.subtitle': 'Tekrar hoş geldiniz', - 'login.signingIn': 'Giriş yapılıyor…', - 'login.signIn': 'Giriş yap', - 'login.createAdmin': 'Create Admin Account', - 'login.createAdminHint': 'Set up the first admin account for TREK.', - 'login.setNewPassword': 'Set New Password', - 'login.setNewPasswordHint': 'You must change your password before continuing.', - 'login.createAccount': 'Hesap oluştur', - 'login.createAccountHint': 'Register a new account.', - 'login.creating': 'Creating…', - 'login.noAccount': "Don't have an account?", - 'login.hasAccount': 'Zaten hesabınız var mı?', - 'login.register': 'Kayıt ol', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': 'Kullanıcı adı', - 'login.oidc.registrationDisabled': 'Registration is disabled. Contact your administrator.', - 'login.oidc.noEmail': 'No email received from provider.', - 'login.oidc.tokenFailed': 'Authentication failed.', - 'login.oidc.invalidState': 'Invalid session. Please try again.', - 'login.demoFailed': 'Demo login failed', - 'login.oidcSignIn': 'Sign in with {name}', - 'login.oidcOnly': 'Password authentication is disabled. Please sign in using your SSO provider.', - 'login.oidcLoggedOut': 'You have been logged out. Sign in again using your SSO provider.', - 'login.demoHint': 'Try the demo — no registration needed', - 'login.mfaTitle': 'Two-factor authentication', - 'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.', - 'login.mfaCodeLabel': 'Verification code', - 'login.mfaCodeRequired': 'Enter the code from your authenticator app.', - 'login.mfaHint': 'Open Google Authenticator, Authy, or another TOTP app.', - 'login.mfaBack': '← Back to sign in', - 'login.mfaVerify': 'Verify', - 'login.invalidInviteLink': 'Invalid or expired invite link', - 'login.oidcFailed': 'OIDC login failed', - 'login.usernameRequired': 'Username is required', - 'login.passwordMinLength': 'Password must be at least 8 characters', - 'login.forgotPassword': 'Parolamı unuttum?', - 'login.forgotPasswordTitle': 'Reset your password', - 'login.forgotPasswordBody': 'Enter the email address you signed up with. If an account exists, we\'ll send a reset link.', - 'login.forgotPasswordSubmit': 'Send reset link', - 'login.forgotPasswordSentTitle': 'Check your email', - 'login.forgotPasswordSentBody': 'If an account exists for that email, a reset link is on its way. It expires in 60 minutes.', - 'login.forgotPasswordSmtpHintOff': 'Heads up: your administrator hasn\'t configured SMTP, so the reset link will be written to the server console instead of being emailed.', - 'login.backToLogin': 'Girişe dön', - 'login.newPassword': 'New password', - 'login.confirmPassword': 'Confirm new password', - 'login.passwordsDontMatch': 'Parolalar eşleşmiyor', - 'login.mfaCode': '2FA code', - 'login.resetPasswordTitle': 'Set a new password', - 'login.resetPasswordBody': 'Pick a strong password you haven’t used here before. Minimum 8 characters.', - 'login.resetPasswordMfaBody': 'Enter your 2FA code or a backup code to complete the reset.', - 'login.resetPasswordSubmit': 'Reset password', - 'login.resetPasswordVerify': 'Verify & reset', - 'login.resetPasswordSuccessTitle': 'Password updated', - 'login.resetPasswordSuccessBody': 'You can now sign in with your new password.', - 'login.resetPasswordInvalidLink': 'Invalid reset link', - 'login.resetPasswordInvalidLinkBody': 'This link is missing or broken. Request a new one to continue.', - 'login.resetPasswordFailed': 'Reset failed. The link may have expired.', - - // Register - 'register.passwordMismatch': 'Passwords do not match', - 'register.passwordTooShort': 'Password must be at least 8 characters', - 'register.failed': 'Registration failed', - 'register.getStarted': 'Başlayın', - 'register.subtitle': 'Hesap oluşturun ve hayalinizdeki gezileri planlamaya başlayın.', - 'register.feature1': 'Unlimited trip plans', - 'register.feature2': 'Interactive map view', - 'register.feature3': 'Manage places and categories', - 'register.feature4': 'Track reservations', - 'register.feature5': 'Create packing lists', - 'register.feature6': 'Store photos and files', - 'register.createAccount': 'Hesap oluştur', - 'register.startPlanning': 'Start your trip planning', - 'register.minChars': 'Min. 6 characters', - 'register.confirmPassword': 'Confirm Password', - 'register.repeatPassword': 'Repeat password', - 'register.registering': 'Registering...', - 'register.register': 'Kayıt ol', - 'register.hasAccount': 'Zaten hesabınız var mı?', - 'register.signIn': 'Giriş yap', - - // Admin - 'admin.title': 'Yönetim', - 'admin.subtitle': 'Kullanıcı yönetimi ve sistem ayarları', - 'admin.tabs.users': 'Kullanıcılar', - 'admin.tabs.categories': 'Kategoriler', - 'admin.tabs.backup': 'Yedekleme', - 'admin.tabs.notifications': 'Bildirimler', - 'admin.tabs.audit': 'Denetim', - 'admin.stats.users': 'Kullanıcı', - 'admin.stats.trips': 'Gezi', - 'admin.stats.places': 'Yer', - 'admin.stats.photos': 'Fotoğraf', - 'admin.stats.files': 'Dosya', - 'admin.table.user': 'User', - 'admin.table.email': 'Email', - 'admin.table.role': 'Role', - 'admin.table.created': 'Created', - 'admin.table.lastLogin': 'Last Login', - 'admin.table.actions': 'Actions', - 'admin.you': '(You)', - 'admin.editUser': 'Edit User', - 'admin.newPassword': 'New Password', - 'admin.newPasswordHint': 'Leave empty to keep current password', - 'admin.deleteUser': 'Delete user "{name}"? All trips will be permanently deleted.', - 'admin.deleteUserTitle': 'Delete user', - 'admin.newPasswordPlaceholder': 'Enter new password…', - 'admin.toast.loadError': 'Failed to load admin data', - 'admin.toast.userUpdated': 'User updated', - 'admin.toast.updateError': 'Failed to update', - 'admin.toast.userDeleted': 'User deleted', - 'admin.toast.deleteError': 'Failed to delete', - 'admin.toast.cannotDeleteSelf': 'Cannot delete your own account', - 'admin.toast.userCreated': 'User created', - 'admin.toast.createError': 'Failed to create user', - 'admin.toast.fieldsRequired': 'Username, email and password are required', - 'admin.createUser': 'Create User', - 'admin.invite.title': 'Invite Links', - 'admin.invite.subtitle': 'Create one-time registration links', - 'admin.invite.create': 'Create Link', - 'admin.invite.createAndCopy': 'Create & Copy', - 'admin.invite.empty': 'No invite links created yet', - 'admin.invite.maxUses': 'Max. Uses', - 'admin.invite.expiry': 'Expires after', - 'admin.invite.uses': 'used', - 'admin.invite.expiresAt': 'expires', - 'admin.invite.createdBy': 'by', - 'admin.invite.active': 'Active', - 'admin.invite.expired': 'Expired', - 'admin.invite.usedUp': 'Used up', - 'admin.invite.copied': 'Invite link copied to clipboard', - 'admin.invite.copyLink': 'Copy link', - 'admin.invite.deleted': 'Invite link deleted', - 'admin.invite.createError': 'Failed to create invite link', - 'admin.invite.deleteError': 'Failed to delete invite link', - 'admin.tabs.settings': 'Ayarlar', - 'admin.allowRegistration': 'Allow Registration', - 'admin.allowRegistrationHint': 'New users can register themselves', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': 'Require two-factor authentication (2FA)', - 'admin.requireMfaHint': 'Users without 2FA must complete setup in Settings before using the app.', - 'admin.apiKeys': 'API Keys', - 'admin.apiKeysHint': 'Optional. Enables extended place data like photos and weather.', - 'admin.mapsKey': 'Google Maps API Key', - 'admin.mapsKeyHint': 'Required for place search. Get at console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Without an API key, OpenStreetMap is used for place search. With a Google API key, photos, ratings, and opening hours can be loaded as well. Get one at console.cloud.google.com.', - 'admin.recommended': 'Recommended', - 'admin.weatherKey': 'OpenWeatherMap API Key', - 'admin.weatherKeyHint': 'For weather data. Free at openweathermap.org', - 'admin.validateKey': 'Test', - 'admin.keyValid': 'Connected', - 'admin.keyInvalid': 'Invalid', - 'admin.keySaved': 'API keys saved', - 'admin.oidcTitle': 'Single Sign-On (OIDC)', - 'admin.oidcSubtitle': 'Allow login via external providers like Google, Apple, Authentik or Keycloak.', - 'admin.oidcDisplayName': 'Display Name', - 'admin.oidcIssuer': 'Issuer URL', - 'admin.oidcIssuerHint': 'The OpenID Connect Issuer URL of the provider. e.g. https://accounts.google.com', - 'admin.oidcSaved': 'OIDC configuration saved', - 'admin.oidcOnlyMode': 'Disable password authentication', - 'admin.oidcOnlyModeHint': 'When enabled, only SSO login is permitted. Password-based login and registration are blocked.', - - // File Types - 'admin.fileTypes': 'Allowed File Types', - 'admin.fileTypesHint': 'Configure which file types users can upload.', - 'admin.fileTypesFormat': 'Comma-separated extensions (e.g. jpg,png,pdf,doc). Use * to allow all types.', - 'admin.fileTypesSaved': 'File type settings saved', - - 'admin.placesPhotos.title': 'Place Photos', - 'admin.placesPhotos.subtitle': 'Fetch photos from the Google Places API. Disable to save API quota. Wikimedia photos are unaffected.', - 'admin.placesAutocomplete.title': 'Place Autocomplete', - 'admin.placesAutocomplete.subtitle': 'Use the Google Places API for search suggestions. Disable to save API quota.', - 'admin.placesDetails.title': 'Place Details', - 'admin.placesDetails.subtitle': 'Fetch detailed place information (hours, rating, website) from the Google Places API. Disable to save API quota.', - // Packing Templates & Bag Tracking - 'admin.bagTracking.title': 'Bag Tracking', - 'admin.bagTracking.subtitle': 'Enable weight and bag assignment for packing items', - 'admin.collab.chat.title': 'Chat', - 'admin.collab.chat.subtitle': 'Real-time messaging for trip collaboration', - 'admin.collab.notes.title': 'Notes', - 'admin.collab.notes.subtitle': 'Shared notes and documents', - 'admin.collab.polls.title': 'Polls', - 'admin.collab.polls.subtitle': 'Group polls and voting', - 'admin.collab.whatsnext.title': "What's Next", - 'admin.collab.whatsnext.subtitle': 'Activity suggestions and next steps', - 'admin.tabs.config': 'Personalization', - 'admin.tabs.defaults': 'User Defaults', - 'admin.defaultSettings.title': 'Default User Settings', - 'admin.defaultSettings.description': 'Set instance-wide defaults. Users who have not changed a setting will see these values. Their own changes always take priority.', - 'admin.defaultSettings.saved': 'Default saved', - 'admin.defaultSettings.reset': 'Reset to built-in default', - 'admin.defaultSettings.resetToBuiltIn': 'reset', - 'admin.tabs.templates': 'Packing Templates', - 'admin.packingTemplates.title': 'Packing Templates', - 'admin.packingTemplates.subtitle': 'Create reusable packing lists for your trips', - 'admin.packingTemplates.create': 'New Template', - 'admin.packingTemplates.namePlaceholder': 'Template name (e.g. Beach Holiday)', - 'admin.packingTemplates.empty': 'No templates created yet', - 'admin.packingTemplates.items': 'items', - 'admin.packingTemplates.categories': 'categories', - 'admin.packingTemplates.itemName': 'Item name', - 'admin.packingTemplates.itemCategory': 'Category', - 'admin.packingTemplates.categoryName': 'Category name (e.g. Clothing)', - 'admin.packingTemplates.addCategory': 'Add category', - 'admin.packingTemplates.created': 'Template created', - 'admin.packingTemplates.deleted': 'Template deleted', - 'admin.packingTemplates.loadError': 'Failed to load templates', - 'admin.packingTemplates.createError': 'Failed to create template', - 'admin.packingTemplates.deleteError': 'Failed to delete template', - 'admin.packingTemplates.saveError': 'Failed to save', - - // Addons - 'admin.tabs.addons': 'Addons', - 'admin.addons.title': 'Addons', - 'admin.addons.subtitle': 'Enable or disable features to customize your TREK experience.', - 'admin.addons.catalog.packing.name': 'Lists', - 'admin.addons.catalog.packing.description': 'Packing lists and to-do tasks for your trips', - 'admin.addons.catalog.budget.name': 'Budget', - 'admin.addons.catalog.budget.description': 'Track expenses and plan your trip budget', - 'admin.addons.catalog.documents.name': 'Documents', - 'admin.addons.catalog.documents.description': 'Store and manage travel documents', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Personal vacation planner with calendar view', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'World map with visited countries and travel stats', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Real-time notes, polls, and chat for trip planning', - 'admin.addons.catalog.memories.name': 'Photos (Immich)', - 'admin.addons.catalog.memories.description': 'Share trip photos via your Immich instance', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Model Context Protocol for AI assistant integration', - 'admin.addons.subtitleBefore': 'Enable or disable features to customize your ', - 'admin.addons.subtitleAfter': ' experience.', - 'admin.addons.enabled': 'Enabled', - 'admin.addons.disabled': 'Disabled', - 'admin.addons.type.trip': 'Trip', - 'admin.addons.type.global': 'Global', - 'admin.addons.type.integration': 'Integration', - 'admin.addons.tripHint': 'Available as a tab within each trip', - 'admin.addons.globalHint': 'Available as a standalone section in the main navigation', - 'admin.addons.integrationHint': 'Backend services and API integrations with no dedicated page', - 'admin.addons.toast.updated': 'Addon updated', - 'admin.addons.toast.error': 'Failed to update addon', - 'admin.addons.noAddons': 'No addons available', - // Weather info - 'admin.weather.title': 'Weather Data', - 'admin.weather.badge': 'Since March 24, 2026', - 'admin.weather.description': 'TREK uses Open-Meteo as its weather data source. Open-Meteo is a free, open-source weather service — no API key required.', - 'admin.weather.forecast': '16-day forecast', - 'admin.weather.forecastDesc': 'Previously 5 days (OpenWeatherMap)', - 'admin.weather.climate': 'Historical climate data', - 'admin.weather.climateDesc': 'Averages from the last 85 years for days beyond the 16-day forecast', - 'admin.weather.requests': '10,000 requests / day', - 'admin.weather.requestsDesc': 'Free, no API key required', - 'admin.weather.locationHint': 'Weather is based on the first place with coordinates in each day. If no place is assigned to a day, any place from the place list is used as a reference.', - - // GitHub - 'admin.tabs.mcpTokens': 'MCP Access', - 'admin.mcpTokens.title': 'MCP Access', - 'admin.mcpTokens.subtitle': 'Manage OAuth sessions and API tokens across all users', - 'admin.mcpTokens.sectionTitle': 'API Tokens', - 'admin.mcpTokens.owner': 'Owner', - 'admin.mcpTokens.tokenName': 'Token Name', - 'admin.mcpTokens.created': 'Created', - 'admin.mcpTokens.lastUsed': 'Last Used', - 'admin.mcpTokens.never': 'Never', - 'admin.mcpTokens.empty': 'No MCP tokens have been created yet', - 'admin.mcpTokens.deleteTitle': 'Delete Token', - 'admin.mcpTokens.deleteMessage': 'This will revoke the token immediately. The user will lose MCP access through this token.', - 'admin.mcpTokens.deleteSuccess': 'Token deleted', - 'admin.mcpTokens.deleteError': 'Failed to delete token', - 'admin.mcpTokens.loadError': 'Failed to load tokens', - 'admin.oauthSessions.sectionTitle': 'OAuth Sessions', - 'admin.oauthSessions.clientName': 'Client', - 'admin.oauthSessions.owner': 'Owner', - 'admin.oauthSessions.scopes': 'Scopes', - 'admin.oauthSessions.created': 'Created', - 'admin.oauthSessions.empty': 'No active OAuth sessions', - 'admin.oauthSessions.revokeTitle': 'Revoke Session', - 'admin.oauthSessions.revokeMessage': 'This will revoke the OAuth session immediately. The client will lose MCP access.', - 'admin.oauthSessions.revokeSuccess': 'Session revoked', - 'admin.oauthSessions.revokeError': 'Failed to revoke session', - 'admin.oauthSessions.loadError': 'Failed to load OAuth sessions', - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Security-sensitive and administration events (backups, users, MFA, settings).', - 'admin.audit.empty': 'No audit entries yet.', - 'admin.audit.refresh': 'Refresh', - 'admin.audit.loadMore': 'Load more', - 'admin.audit.showing': '{count} loaded · {total} total', - 'admin.audit.col.time': 'Time', - 'admin.audit.col.user': 'User', - 'admin.audit.col.action': 'Action', - 'admin.audit.col.resource': 'Resource', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Details', - 'admin.github.title': 'Release History', - 'admin.github.subtitle': 'Latest updates from {repo}', - 'admin.github.latest': 'Latest', - 'admin.github.prerelease': 'Pre-release', - 'admin.github.showDetails': 'Show details', - 'admin.github.hideDetails': 'Hide details', - 'admin.github.loadMore': 'Load more', - 'admin.github.loading': 'Loading...', - 'admin.github.error': 'Failed to load releases', - 'admin.github.by': 'by', - 'admin.github.support': 'Helps me keep building TREK', - - 'admin.update.available': 'Update available', - 'admin.update.text': 'TREK {version} is available. You are running {current}.', - 'admin.update.button': 'View on GitHub', - 'admin.update.install': 'Install Update', - 'admin.update.confirmTitle': 'Install Update?', - 'admin.update.confirmText': 'TREK will be updated from {current} to {version}. The server will restart automatically afterwards.', - 'admin.update.dataInfo': 'All your data (trips, users, API keys, uploads, Vacay, Atlas, budgets) will be preserved.', - 'admin.update.warning': 'The app will be briefly unavailable during the restart.', - 'admin.update.confirm': 'Update Now', - 'admin.update.installing': 'Updating…', - 'admin.update.success': 'Update installed! Server is restarting…', - 'admin.update.failed': 'Update failed', - 'admin.update.backupHint': 'We recommend creating a backup before updating.', - 'admin.update.backupLink': 'Go to Backup', - 'admin.update.howTo': 'How to Update', - 'admin.update.dockerText': 'Your TREK instance runs in Docker. To update to {version}, run the following commands on your server:', - 'admin.update.reloadHint': 'Please reload the page in a few seconds.', - - // Vacay addon - 'vacay.subtitle': 'Plan and manage vacation days', - 'vacay.settings': 'Settings', - 'vacay.year': 'Year', - 'vacay.addYear': 'Add next year', - 'vacay.addPrevYear': 'Add previous year', - 'vacay.removeYear': 'Remove year', - 'vacay.removeYearConfirm': 'Remove {year}?', - 'vacay.removeYearHint': 'All vacation entries and company holidays for this year will be permanently deleted.', - 'vacay.remove': 'Remove', - 'vacay.persons': 'Persons', - 'vacay.noPersons': 'No persons added', - 'vacay.addPerson': 'Add Person', - 'vacay.editPerson': 'Edit Person', - 'vacay.removePerson': 'Remove Person', - 'vacay.removePersonConfirm': 'Remove {name}?', - 'vacay.removePersonHint': 'All vacation entries for this person will be permanently deleted.', - 'vacay.personName': 'Name', - 'vacay.personNamePlaceholder': 'Enter name', - 'vacay.color': 'Color', - 'vacay.add': 'Add', - 'vacay.legend': 'Legend', - 'vacay.publicHoliday': 'Public Holiday', - 'vacay.companyHoliday': 'Company Holiday', - 'vacay.weekend': 'Weekend', - 'vacay.modeVacation': 'Vacation', - 'vacay.modeCompany': 'Company Holiday', - 'vacay.entitlement': 'Entitlement', - 'vacay.entitlementDays': 'Days', - 'vacay.used': 'Used', - 'vacay.remaining': 'Left', - 'vacay.carriedOver': 'from {year}', - 'vacay.blockWeekends': 'Block Weekends', - 'vacay.blockWeekendsHint': 'Prevent vacation entries on weekend days', - 'vacay.weekendDays': 'Weekend days', - 'vacay.mon': 'Mon', - 'vacay.tue': 'Tue', - 'vacay.wed': 'Wed', - 'vacay.thu': 'Thu', - 'vacay.fri': 'Fri', - 'vacay.sat': 'Sat', - 'vacay.sun': 'Sun', - 'vacay.publicHolidays': 'Public Holidays', - 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar', - 'vacay.selectCountry': 'Select country', - 'vacay.selectRegion': 'Select region (optional)', - 'vacay.addCalendar': 'Add calendar', - 'vacay.calendarLabel': 'Label (optional)', - 'vacay.calendarColor': 'Color', - 'vacay.noCalendars': 'No holiday calendars added yet', - 'vacay.companyHolidays': 'Company Holidays', - 'vacay.companyHolidaysHint': 'Allow marking company-wide holiday days', - 'vacay.companyHolidaysNoDeduct': 'Company holidays do not count towards vacation days.', - 'vacay.weekStart': 'Week starts on', - 'vacay.weekStartHint': 'Choose whether the calendar week starts on Monday or Sunday', - 'vacay.carryOver': 'Carry Over', - 'vacay.carryOverHint': 'Automatically carry remaining vacation days into the next year', - 'vacay.sharing': 'Sharing', - 'vacay.sharingHint': 'Share your vacation plan with other TREK users', - 'vacay.owner': 'Owner', - 'vacay.shareEmailPlaceholder': 'Email of TREK user', - 'vacay.shareSuccess': 'Plan shared successfully', - 'vacay.shareError': 'Could not share plan', - 'vacay.dissolve': 'Dissolve Fusion', - 'vacay.dissolveHint': 'Separate calendars again. Your entries will be kept.', - 'vacay.dissolveAction': 'Dissolve', - 'vacay.dissolved': 'Calendar separated', - 'vacay.fusedWith': 'Fused with', - 'vacay.you': 'you', - 'vacay.noData': 'No data', - 'vacay.changeColor': 'Change color', - 'vacay.inviteUser': 'Invite User', - 'vacay.inviteHint': 'Invite another TREK user to share a combined vacation calendar.', - 'vacay.selectUser': 'Select user', - 'vacay.sendInvite': 'Send Invite', - 'vacay.inviteSent': 'Invite sent', - 'vacay.inviteError': 'Could not send invite', - 'vacay.pending': 'pending', - 'vacay.noUsersAvailable': 'No users available', - 'vacay.accept': 'Accept', - 'vacay.decline': 'Decline', - 'vacay.acceptFusion': 'Accept & Fuse', - 'vacay.inviteTitle': 'Fusion Request', - 'vacay.inviteWantsToFuse': 'wants to share a vacation calendar with you.', - 'vacay.fuseInfo1': 'Both of you will see all vacation entries in one shared calendar.', - 'vacay.fuseInfo2': 'Both parties can create and edit entries for each other.', - 'vacay.fuseInfo3': 'Both parties can delete entries and change vacation entitlements.', - 'vacay.fuseInfo4': 'Settings like public holidays and company holidays are shared.', - 'vacay.fuseInfo5': 'The fusion can be dissolved at any time by either party. Your entries will be preserved.', - 'nav.myTrips': 'Gezilerim', - - // Atlas addon - 'atlas.subtitle': 'Your travel footprint around the world', - 'atlas.countries': 'Countries', - 'atlas.trips': 'Trips', - 'atlas.places': 'Places', - 'atlas.unmark': 'Remove', - 'atlas.confirmMark': 'Mark this country as visited?', - 'atlas.confirmUnmark': 'Remove this country from your visited list?', - 'atlas.confirmUnmarkRegion': 'Remove this region from your visited list?', - 'atlas.markVisited': 'Mark as visited', - 'atlas.markVisitedHint': 'Add this country to your visited list', - 'atlas.markRegionVisitedHint': 'Add this region to your visited list', - 'atlas.addToBucket': 'Add to bucket list', - 'atlas.addPoi': 'Add place', - 'atlas.searchCountry': 'Search a country...', - 'atlas.bucketNamePlaceholder': 'Name (country, city, place...)', - 'atlas.month': 'Month', - 'atlas.year': 'Year', - 'atlas.addToBucketHint': 'Save as a place you want to visit', - 'atlas.bucketWhen': 'When do you plan to visit?', - 'atlas.statsTab': 'Stats', - 'atlas.bucketTab': 'Bucket List', - 'atlas.addBucket': 'Add to bucket list', - 'atlas.bucketNotesPlaceholder': 'Notes (optional)', - 'atlas.bucketEmpty': 'Your bucket list is empty', - 'atlas.bucketEmptyHint': 'Add places you dream of visiting', - 'atlas.days': 'Days', - 'atlas.visitedCountries': 'Visited Countries', - 'atlas.cities': 'Cities', - 'atlas.noData': 'No travel data yet', - 'atlas.noDataHint': 'Create a trip and add places to see your world map', - 'atlas.lastTrip': 'Last trip', - 'atlas.nextTrip': 'Next trip', - 'atlas.daysLeft': 'days left', - 'atlas.streak': 'Streak', - 'atlas.years': 'years', - 'atlas.yearInRow': 'year in a row', - 'atlas.yearsInRow': 'years in a row', - 'atlas.tripIn': 'trip in', - 'atlas.tripsIn': 'trips in', - 'atlas.since': 'since', - 'atlas.europe': 'Europe', - 'atlas.asia': 'Asia', - 'atlas.northAmerica': 'N. America', - 'atlas.southAmerica': 'S. America', - 'atlas.africa': 'Africa', - 'atlas.oceania': 'Oceania', - 'atlas.other': 'Other', - 'atlas.firstVisit': 'First trip', - 'atlas.lastVisitLabel': 'Last trip', - 'atlas.tripSingular': 'Trip', - 'atlas.tripPlural': 'Trips', - 'atlas.placeVisited': 'Place visited', - 'atlas.placesVisited': 'Places visited', - - // Trip Planner - 'trip.tabs.plan': 'Plan', - 'trip.tabs.transports': 'Transports', - 'trip.tabs.reservations': 'Bookings', - 'trip.tabs.reservationsShort': 'Book', - 'trip.tabs.packing': 'Packing List', - 'trip.tabs.packingShort': 'Packing', - 'trip.tabs.lists': 'Lists', - 'trip.tabs.listsShort': 'Lists', - 'trip.tabs.budget': 'Budget', - 'trip.tabs.files': 'Files', - 'trip.loading': 'Loading trip...', - 'trip.loadingPhotos': 'Loading place photos...', - 'trip.mobilePlan': 'Plan', - 'trip.mobilePlaces': 'Places', - 'trip.toast.placeUpdated': 'Place updated', - 'trip.toast.placeAdded': 'Place added', - 'trip.toast.placeDeleted': 'Place deleted', - 'trip.toast.selectDay': 'Please select a day first', - 'trip.toast.assignedToDay': 'Place assigned to day', - 'trip.toast.reorderError': 'Failed to reorder', - 'trip.toast.reservationUpdated': 'Reservation updated', - 'trip.toast.reservationAdded': 'Reservation added', - 'trip.toast.deleted': 'Deleted', - 'trip.confirm.deletePlace': 'Are you sure you want to delete this place?', - 'trip.confirm.deletePlaces': 'Delete {count} places?', - 'trip.toast.placesDeleted': '{count} places deleted', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'No places planned for this day', - 'dayplan.cannotReorderTransport': 'Bookings with a fixed time cannot be reordered', - 'dayplan.confirmRemoveTimeTitle': 'Remove time?', - 'dayplan.confirmRemoveTimeBody': 'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.', - 'dayplan.confirmRemoveTimeAction': 'Remove time & move', - 'dayplan.cannotDropOnTimed': 'Items cannot be placed between time-bound entries', - 'dayplan.cannotBreakChronology': 'This would break the chronological order of timed items and bookings', - 'dayplan.addNote': 'Add Note', - 'dayplan.expandAll': 'Expand all days', - 'dayplan.collapseAll': 'Collapse all days', - 'dayplan.editNote': 'Edit Note', - 'dayplan.noteAdd': 'Add Note', - 'dayplan.noteEdit': 'Edit Note', - 'dayplan.noteTitle': 'Note', - 'dayplan.noteSubtitle': 'Daily Note', - 'dayplan.totalCost': 'Total Cost', - 'dayplan.days': 'Days', - 'dayplan.dayN': 'Day {n}', - 'dayplan.calculating': 'Calculating...', - 'dayplan.route': 'Route', - 'dayplan.optimize': 'Optimize', - 'dayplan.optimized': 'Route optimized', - 'dayplan.routeError': 'Failed to calculate route', - 'dayplan.toast.needTwoPlaces': 'At least two places needed for route optimization', - 'dayplan.toast.routeOptimized': 'Route optimized', - 'dayplan.toast.noGeoPlaces': 'No places with coordinates found for route calculation', - 'dayplan.confirmed': 'Confirmed', - 'dayplan.pendingRes': 'Pending', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Export day plan as PDF', - 'dayplan.pdfError': 'Failed to export PDF', - - // Places Sidebar - 'places.addPlace': 'Add Place/Activity', - 'places.importFile': 'Import file', - 'places.sidebarDrop': 'Drop to import', - 'places.importFileHint': 'Import .gpx, .kml or .kmz files from tools like Google My Maps, Google Earth, or a GPS tracker.', - 'places.importFileDropHere': 'Click to select a file or drag and drop here', - 'places.importFileDropActive': 'Drop file to select', - 'places.importFileUnsupported': 'Unsupported file type. Use .gpx, .kml or .kmz.', - 'places.importFileTooLarge': 'File is too large. Maximum upload size is {maxMb} MB.', - 'places.importFileError': 'Import failed', - 'places.importAllSkipped': 'All places were already in the trip.', - 'places.gpxImported': '{count} places imported from GPX', - 'places.gpxImportTypes': 'What do you want to import?', - 'places.gpxImportWaypoints': 'Waypoints', - 'places.gpxImportRoutes': 'Routes', - 'places.gpxImportTracks': 'Tracks (with path geometry)', - 'places.gpxImportNoneSelected': 'Select at least one type to import.', - 'places.kmlImportTypes': 'What do you want to import?', - 'places.kmlImportPoints': 'Points (Placemarks)', - 'places.kmlImportPaths': 'Paths (LineStrings)', - 'places.kmlImportNoneSelected': 'Select at least one type to import.', - 'places.selectionCount': '{count} selected', - 'places.deleteSelected': 'Delete selected', - 'places.kmlKmzImported': '{count} places imported from KMZ/KML', - 'places.urlResolved': 'Place imported from URL', - 'places.importList': 'List Import', - 'places.kmlKmzSummaryValues': 'Placemarks: {total} • Imported: {created} • Skipped: {skipped}', - 'places.importGoogleList': 'Google List', - 'places.importNaverList': 'Naver List', - 'places.googleListHint': 'Paste a shared Google Maps list link to import all places.', - 'places.googleListImported': '{count} places imported from "{list}"', - 'places.googleListError': 'Failed to import Google Maps list', - 'places.naverListHint': 'Paste a shared Naver Maps list link to import all places.', - 'places.naverListImported': '{count} places imported from "{list}"', - 'places.naverListError': 'Failed to import Naver Maps list', - 'places.viewDetails': 'View Details', - 'places.assignToDay': 'Add to which day?', - 'places.all': 'All', - 'places.unplanned': 'Unplanned', - 'places.filterTracks': 'Tracks', - 'places.search': 'Search places...', - 'places.allCategories': 'All Categories', - 'places.categoriesSelected': 'categories', - 'places.clearFilter': 'Clear filter', - 'places.count': '{count} places', - 'places.countSingular': '1 place', - 'places.allPlanned': 'All places are planned', - 'places.noneFound': 'No places found', - 'places.editPlace': 'Edit Place', - 'places.formName': 'Name', - 'places.formNamePlaceholder': 'e.g. Eiffel Tower', - 'places.formDescription': 'Description', - 'places.formDescriptionPlaceholder': 'Short description...', - 'places.formAddress': 'Address', - 'places.formAddressPlaceholder': 'Street, City, Country', - 'places.formLat': 'Latitude (e.g. 48.8566)', - 'places.formLng': 'Longitude (e.g. 2.3522)', - 'places.formCategory': 'Category', - 'places.noCategory': 'No Category', - 'places.categoryNamePlaceholder': 'Category name', - 'places.formTime': 'Time', - 'places.startTime': 'Start', - 'places.endTime': 'End', - 'places.endTimeBeforeStart': 'End time is before start time', - 'places.timeCollision': 'Time overlap with:', - 'places.formWebsite': 'Website', - 'places.formNotes': 'Notes', - 'places.formNotesPlaceholder': 'Personal notes...', - 'places.formReservation': 'Reservation', - 'places.reservationNotesPlaceholder': 'Reservation notes, confirmation number...', - 'places.mapsSearchPlaceholder': 'Search places...', - 'places.mapsSearchError': 'Place search failed.', - 'places.loadingDetails': 'Loading place details…', - 'places.osmHint': 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', - 'places.osmActive': 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.', - 'places.categoryCreateError': 'Failed to create category', - 'places.nameRequired': 'Please enter a name', - 'places.saveError': 'Failed to save', - // Place Inspector - 'inspector.opened': 'Open', - 'inspector.closed': 'Closed', - 'inspector.openingHours': 'Opening Hours', - 'inspector.showHours': 'Show opening hours', - 'inspector.files': 'Files', - 'inspector.filesCount': '{count} files', - 'inspector.remove': 'Remove', - 'inspector.removeFromDay': 'Remove from Day', - 'inspector.addToDay': 'Add to Day', - 'inspector.confirmedRes': 'Confirmed Reservation', - 'inspector.pendingRes': 'Pending Reservation', - 'inspector.google': 'Open in Google Maps', - 'inspector.website': 'Open Website', - 'inspector.addRes': 'Reservation', - 'inspector.editRes': 'Edit Reservation', - 'inspector.participants': 'Participants', - 'inspector.trackStats': 'Track Stats', - - // Reservations - 'reservations.title': 'Bookings', - 'reservations.empty': 'No reservations yet', - 'reservations.emptyHint': 'Add reservations for flights, hotels and more', - 'reservations.add': 'Add Reservation', - 'reservations.addManual': 'Manual Booking', - 'reservations.placeHint': 'Tip: Reservations are best created directly from a place to link them with your day plan.', - 'reservations.confirmed': 'Confirmed', - 'reservations.pending': 'Pending', - 'reservations.summary': '{confirmed} confirmed, {pending} pending', - 'reservations.fromPlan': 'From Plan', - 'reservations.showFiles': 'Show Files', - 'reservations.editTitle': 'Edit Reservation', - 'reservations.status': 'Status', - 'reservations.datetime': 'Date & Time', - 'reservations.startTime': 'Start time', - 'reservations.endTime': 'End time', - 'reservations.date': 'Date', - 'reservations.time': 'Time', - 'reservations.timeAlt': 'Time (alternative, e.g. 19:30)', - 'reservations.notes': 'Notes', - 'reservations.notesPlaceholder': 'Additional notes...', - 'reservations.meta.airline': 'Airline', - 'reservations.meta.flightNumber': 'Flight No.', - 'reservations.meta.from': 'From', - 'reservations.meta.to': 'To', - 'reservations.needsReview': 'Review', - 'reservations.needsReviewHint': 'Airport could not be matched automatically — please confirm the location.', - 'reservations.searchLocation': 'Search station, port, address…', - 'airport.searchPlaceholder': 'Airport code or city (e.g. FRA)', - 'map.connections': 'Connections', - 'map.showConnections': 'Show booking routes', - 'map.hideConnections': 'Hide booking routes', - 'reservations.meta.trainNumber': 'Train No.', - 'reservations.meta.platform': 'Platform', - 'reservations.meta.seat': 'Seat', - 'reservations.meta.checkIn': 'Check-in', - 'reservations.meta.checkInUntil': 'Check-in until', - 'reservations.meta.checkOut': 'Check-out', - 'reservations.meta.linkAccommodation': 'Accommodation', - 'reservations.meta.pickAccommodation': 'Link to accommodation', - 'reservations.meta.noAccommodation': 'None', - 'reservations.meta.hotelPlace': 'Accommodation', - 'reservations.meta.pickHotel': 'Select accommodation', - 'reservations.meta.fromDay': 'From', - 'reservations.meta.toDay': 'To', - 'reservations.meta.selectDay': 'Select day', - 'reservations.type.flight': 'Flight', - 'reservations.type.hotel': 'Accommodation', - 'reservations.type.restaurant': 'Restaurant', - 'reservations.type.train': 'Train', - 'reservations.type.car': 'Car', - 'reservations.type.cruise': 'Cruise', - 'reservations.type.event': 'Event', - 'reservations.type.tour': 'Tour', - 'reservations.type.other': 'Other', - 'reservations.confirm.delete': 'Are you sure you want to delete the reservation "{name}"?', - 'reservations.confirm.deleteTitle': 'Delete booking?', - 'reservations.confirm.deleteBody': '"{name}" will be permanently deleted.', - 'reservations.toast.updated': 'Reservation updated', - 'reservations.toast.removed': 'Reservation deleted', - 'reservations.toast.fileUploaded': 'File uploaded', - 'reservations.toast.uploadError': 'Failed to upload', - 'reservations.newTitle': 'New Reservation', - 'reservations.bookingType': 'Booking Type', - 'reservations.titleLabel': 'Title', - 'reservations.titlePlaceholder': 'e.g. Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Location / Address', - 'reservations.locationPlaceholder': 'Address, Airport, Hotel...', - 'reservations.confirmationCode': 'Booking Code', - 'reservations.confirmationPlaceholder': 'e.g. ABC12345', - 'reservations.day': 'Day', - 'reservations.noDay': 'No Day', - 'reservations.place': 'Place', - 'reservations.noPlace': 'No Place', - 'reservations.pendingSave': 'will be saved…', - 'reservations.uploading': 'Uploading...', - 'reservations.attachFile': 'Attach file', - 'reservations.linkExisting': 'Link existing file', - 'reservations.toast.saveError': 'Failed to save', - 'reservations.toast.updateError': 'Failed to update', - 'reservations.toast.deleteError': 'Failed to delete', - 'reservations.confirm.remove': 'Remove reservation for "{name}"?', - 'reservations.linkAssignment': 'Link to day assignment', - 'reservations.pickAssignment': 'Select an assignment from your plan...', - 'reservations.noAssignment': 'No link (standalone)', - 'reservations.price': 'Price', - 'reservations.budgetCategory': 'Budget category', - 'reservations.budgetCategoryPlaceholder': 'e.g. Transport, Accommodation', - 'reservations.budgetCategoryAuto': 'Auto (from booking type)', - 'reservations.budgetHint': 'A budget entry will be created automatically when saving.', - 'reservations.departureDate': 'Departure', - 'reservations.arrivalDate': 'Arrival', - 'reservations.departureTime': 'Dep. time', - 'reservations.arrivalTime': 'Arr. time', - 'reservations.pickupDate': 'Pickup', - 'reservations.returnDate': 'Return', - 'reservations.pickupTime': 'Pickup time', - 'reservations.returnTime': 'Return time', - 'reservations.endDate': 'End date', - 'reservations.meta.departureTimezone': 'Dep. TZ', - 'reservations.meta.arrivalTimezone': 'Arr. TZ', - 'reservations.span.departure': 'Departure', - 'reservations.span.arrival': 'Arrival', - 'reservations.span.inTransit': 'In transit', - 'reservations.span.pickup': 'Pickup', - 'reservations.span.return': 'Return', - 'reservations.span.active': 'Active', - 'reservations.span.start': 'Start', - 'reservations.span.end': 'End', - 'reservations.span.ongoing': 'Ongoing', - 'reservations.validation.endBeforeStart': 'End date/time must be after start date/time', - 'reservations.addBooking': 'Add booking', - - // Budget - 'budget.title': 'Budget', - 'budget.exportCsv': 'Export CSV', - 'budget.emptyTitle': 'No budget created yet', - 'budget.emptyText': 'Create categories and entries to plan your travel budget', - 'budget.emptyPlaceholder': 'Enter category name...', - 'budget.createCategory': 'Create Category', - 'budget.category': 'Category', - 'budget.categoryName': 'Category Name', - 'budget.table.name': 'Name', - 'budget.table.total': 'Total', - 'budget.table.persons': 'Persons', - 'budget.table.days': 'Days', - 'budget.table.perPerson': 'Per Person', - 'budget.table.perDay': 'Per Day', - 'budget.table.perPersonDay': 'P. p / Day', - 'budget.table.note': 'Note', - 'budget.table.date': 'Date', - 'budget.newEntry': 'New Entry', - 'budget.defaultEntry': 'New Entry', - 'budget.defaultCategory': 'New Category', - 'budget.total': 'Total', - 'budget.totalBudget': 'Total Budget', - 'budget.byCategory': 'By Category', - 'budget.editTooltip': 'Click to edit', - 'budget.linkedToReservation': 'Linked to a reservation — edit the name there', - 'budget.confirm.deleteCategory': 'Are you sure you want to delete the category "{name}" with {count} entries?', - 'budget.deleteCategory': 'Delete Category', - 'budget.perPerson': 'Per Person', - 'budget.paid': 'Paid', - 'budget.open': 'Open', - 'budget.noMembers': 'No members assigned', - 'budget.settlement': 'Settlement', - 'budget.settlementInfo': 'Click a member avatar on a budget item to mark them green — this means they paid. The settlement then shows who owes whom and how much.', - 'budget.netBalances': 'Net Balances', - - // Files - 'files.title': 'Files', - 'files.pageTitle': 'Files & Documents', - 'files.subtitle': '{count} files for {trip}', - 'files.download': 'Download', - 'files.openError': 'Could not open file', - 'files.downloadPdf': 'Download PDF', - 'files.count': '{count} files', - 'files.countSingular': '1 file', - 'files.uploaded': '{count} uploaded', - 'files.uploadError': 'Upload failed', - 'files.dropzone': 'Drop files here', - 'files.dropzoneHint': 'or click to browse', - 'files.allowedTypes': 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', - 'files.uploading': 'Uploading...', - 'files.filterAll': 'All', - 'files.filterPdf': 'PDFs', - 'files.filterImages': 'Images', - 'files.filterDocs': 'Documents', - 'files.filterCollab': 'Collab Notes', - 'files.sourceCollab': 'From Collab Notes', - 'files.empty': 'No files yet', - 'files.emptyHint': 'Upload files to attach them to your trip', - 'files.openTab': 'Open in new tab', - 'files.confirm.delete': 'Are you sure you want to delete this file?', - 'files.toast.deleted': 'File deleted', - 'files.toast.deleteError': 'Failed to delete file', - 'files.sourcePlan': 'Day Plan', - 'files.sourceBooking': 'Booking', - 'files.sourceTransport': 'Transport', - 'files.attach': 'Attach', - 'files.pasteHint': 'You can also paste images from clipboard (Ctrl+V)', - 'files.trash': 'Trash', - 'files.trashEmpty': 'Trash is empty', - 'files.emptyTrash': 'Empty Trash', - 'files.restore': 'Restore', - 'files.star': 'Star', - 'files.unstar': 'Unstar', - 'files.assign': 'Assign', - 'files.assignTitle': 'Assign File', - 'files.assignPlace': 'Place', - 'files.assignBooking': 'Booking', - 'files.assignTransport': 'Transport', - 'files.unassigned': 'Unassigned', - 'files.unlink': 'Remove link', - 'files.toast.trashed': 'Moved to trash', - 'files.toast.restored': 'File restored', - 'files.toast.trashEmptied': 'Trash emptied', - 'files.toast.assigned': 'File assigned', - 'files.toast.assignError': 'Assignment failed', - 'files.toast.restoreError': 'Restore failed', - 'files.confirm.permanentDelete': 'Permanently delete this file? This cannot be undone.', - 'files.confirm.emptyTrash': 'Permanently delete all trashed files? This cannot be undone.', - 'files.noteLabel': 'Note', - 'files.notePlaceholder': 'Add a note...', - - // Packing - 'packing.title': 'Packing List', - 'packing.empty': 'Packing list is empty', - 'packing.import': 'Import', - 'packing.importTitle': 'Import Packing List', - 'packing.importHint': 'One item per line. Format: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional)', - 'packing.importPlaceholder': 'Hygiene, Toothbrush\nClothing, T-Shirts, 200\nDocuments, Passport, , Carry-on\nElectronics, Charger, 50, Suitcase, checked', - 'packing.importCsv': 'Load CSV/TXT', - 'packing.importAction': 'Import {count}', - 'packing.importSuccess': '{count} items imported', - 'packing.importError': 'Import failed', - 'packing.importEmpty': 'No items to import', - 'packing.progress': '{packed} of {total} packed ({percent}%)', - 'packing.clearChecked': 'Remove {count} checked', - 'packing.clearCheckedShort': 'Remove {count}', - 'packing.suggestions': 'Suggestions', - 'packing.suggestionsTitle': 'Add Suggestions', - 'packing.allSuggested': 'All suggestions added', - 'packing.allPacked': 'All packed!', - 'packing.addPlaceholder': 'Add new item...', - 'packing.categoryPlaceholder': 'Category...', - 'packing.filterAll': 'All', - 'packing.filterOpen': 'Open', - 'packing.filterDone': 'Done', - 'packing.emptyTitle': 'Packing list is empty', - 'packing.emptyHint': 'Add items or use the suggestions', - 'packing.emptyFiltered': 'No items match this filter', - 'packing.menuRename': 'Rename', - 'packing.menuCheckAll': 'Check All', - 'packing.menuUncheckAll': 'Uncheck All', - 'packing.menuDeleteCat': 'Delete Category', - 'packing.noMembers': 'No trip members', - 'packing.addItem': 'Add item', - 'packing.addItemPlaceholder': 'Item name...', - 'packing.addCategory': 'Add category', - 'packing.newCategoryPlaceholder': 'Category name (e.g. Clothing)', - 'packing.applyTemplate': 'Apply template', - 'packing.template': 'Template', - 'packing.templateApplied': '{count} items added from template', - 'packing.templateError': 'Failed to apply template', - 'packing.saveAsTemplate': 'Save as template', - 'packing.templateName': 'Template name', - 'packing.templateSaved': 'Packing list saved as template', - 'packing.bags': 'Bags', - 'packing.noBag': 'Unassigned', - 'packing.totalWeight': 'Total weight', - 'packing.bagName': 'Bag name...', - 'packing.addBag': 'Add bag', - 'packing.changeCategory': 'Change Category', - 'packing.confirm.clearChecked': 'Are you sure you want to remove {count} checked items?', - 'packing.confirm.deleteCat': 'Are you sure you want to delete the category "{name}" with {count} items?', - 'packing.defaultCategory': 'Other', - 'packing.toast.saveError': 'Failed to save', - 'packing.toast.deleteError': 'Failed to delete', - 'packing.toast.renameError': 'Failed to rename', - 'packing.toast.addError': 'Failed to add', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Passport', category: 'Documents' }, - { name: 'ID Card', category: 'Documents' }, - { name: 'Travel Insurance', category: 'Documents' }, - { name: 'Flight Tickets', category: 'Documents' }, - { name: 'Credit Card', category: 'Finances' }, - { name: 'Cash', category: 'Finances' }, - { name: 'Visa', category: 'Documents' }, - { name: 'T-Shirts', category: 'Clothing' }, - { name: 'Pants', category: 'Clothing' }, - { name: 'Underwear', category: 'Clothing' }, - { name: 'Socks', category: 'Clothing' }, - { name: 'Jacket', category: 'Clothing' }, - { name: 'Sleepwear', category: 'Clothing' }, - { name: 'Swimwear', category: 'Clothing' }, - { name: 'Rain Jacket', category: 'Clothing' }, - { name: 'Comfortable Shoes', category: 'Clothing' }, - { name: 'Toothbrush', category: 'Toiletries' }, - { name: 'Toothpaste', category: 'Toiletries' }, - { name: 'Shampoo', category: 'Toiletries' }, - { name: 'Deodorant', category: 'Toiletries' }, - { name: 'Sunscreen', category: 'Toiletries' }, - { name: 'Razor', category: 'Toiletries' }, - { name: 'Charger', category: 'Electronics' }, - { name: 'Power Bank', category: 'Electronics' }, - { name: 'Headphones', category: 'Electronics' }, - { name: 'Travel Adapter', category: 'Electronics' }, - { name: 'Camera', category: 'Electronics' }, - { name: 'Pain Medication', category: 'Health' }, - { name: 'Band-Aids', category: 'Health' }, - { name: 'Disinfectant', category: 'Health' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Share Trip', - 'members.inviteUser': 'Invite User', - 'members.selectUser': 'Select user…', - 'members.invite': 'Invite', - 'members.allHaveAccess': 'All users already have access.', - 'members.access': 'Access', - 'members.person': 'person', - 'members.persons': 'persons', - 'members.you': 'you', - 'members.owner': 'Owner', - 'members.leaveTrip': 'Leave trip', - 'members.removeAccess': 'Remove access', - 'members.confirmLeave': 'Leave trip? You will lose access.', - 'members.confirmRemove': 'Remove access for this user?', - 'members.loadError': 'Failed to load members', - 'members.added': 'added', - 'members.addError': 'Failed to add', - 'members.removed': 'Member removed', - 'members.removeError': 'Failed to remove', - - // Categories (Admin) - 'categories.title': 'Categories', - 'categories.subtitle': 'Manage categories for places', - 'categories.new': 'New Category', - 'categories.empty': 'No categories yet', - 'categories.namePlaceholder': 'Category name', - 'categories.icon': 'Icon', - 'categories.color': 'Color', - 'categories.customColor': 'Choose custom color', - 'categories.preview': 'Preview', - 'categories.defaultName': 'Category', - 'categories.update': 'Update', - 'categories.create': 'Create', - 'categories.confirm.delete': 'Delete category? Places in this category will not be deleted.', - 'categories.toast.loadError': 'Failed to load categories', - 'categories.toast.nameRequired': 'Please enter a name', - 'categories.toast.updated': 'Category updated', - 'categories.toast.created': 'Category created', - 'categories.toast.saveError': 'Failed to save', - 'categories.toast.deleted': 'Category deleted', - 'categories.toast.deleteError': 'Failed to delete', - - // Backup (Admin) - 'backup.title': 'Data Backup', - 'backup.subtitle': 'Database and all uploaded files', - 'backup.refresh': 'Refresh', - 'backup.upload': 'Upload Backup', - 'backup.uploading': 'Uploading…', - 'backup.create': 'Create Backup', - 'backup.creating': 'Creating…', - 'backup.empty': 'No backups yet', - 'backup.createFirst': 'Create first backup', - 'backup.download': 'Download', - 'backup.restore': 'Restore', - 'backup.confirm.restore': 'Restore backup "{name}"?\n\nAll current data will be replaced with the backup.', - 'backup.confirm.uploadRestore': 'Upload and restore backup file "{name}"?\n\nAll current data will be overwritten.', - 'backup.confirm.delete': 'Delete backup "{name}"?', - 'backup.toast.loadError': 'Failed to load backups', - 'backup.toast.created': 'Backup created successfully', - 'backup.toast.createError': 'Failed to create backup', - 'backup.toast.restored': 'Backup restored. Page will reload…', - 'backup.toast.restoreError': 'Failed to restore', - 'backup.toast.uploadError': 'Failed to upload', - 'backup.toast.deleted': 'Backup deleted', - 'backup.toast.deleteError': 'Failed to delete', - 'backup.toast.downloadError': 'Download failed', - 'backup.toast.settingsSaved': 'Auto-backup settings saved', - 'backup.toast.settingsError': 'Failed to save settings', - 'backup.auto.title': 'Auto-Backup', - 'backup.auto.subtitle': 'Automatic backup on a schedule', - 'backup.auto.enable': 'Enable auto-backup', - 'backup.auto.enableHint': 'Backups will be created automatically on the chosen schedule', - 'backup.auto.interval': 'Interval', - 'backup.auto.hour': 'Run at hour', - 'backup.auto.hourHint': 'Server local time ({format} format)', - 'backup.auto.dayOfWeek': 'Day of week', - 'backup.auto.dayOfMonth': 'Day of month', - 'backup.auto.dayOfMonthHint': 'Limited to 1–28 for compatibility with all months', - 'backup.auto.scheduleSummary': 'Schedule', - 'backup.auto.summaryDaily': 'Every day at {hour}:00', - 'backup.auto.summaryWeekly': 'Every {day} at {hour}:00', - 'backup.auto.summaryMonthly': 'Day {day} of every month at {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Auto-backup is configured via Docker environment variables. To change these settings, update your docker-compose.yml and restart the container.', - 'backup.auto.copyEnv': 'Copy Docker env vars', - 'backup.auto.envCopied': 'Docker env vars copied to clipboard', - 'backup.auto.keepLabel': 'Delete old backups after', - 'backup.dow.sunday': 'Sun', - 'backup.dow.monday': 'Mon', - 'backup.dow.tuesday': 'Tue', - 'backup.dow.wednesday': 'Wed', - 'backup.dow.thursday': 'Thu', - 'backup.dow.friday': 'Fri', - 'backup.dow.saturday': 'Sat', - 'backup.interval.hourly': 'Hourly', - 'backup.interval.daily': 'Daily', - 'backup.interval.weekly': 'Weekly', - 'backup.interval.monthly': 'Monthly', - 'backup.keep.1day': '1 day', - 'backup.keep.3days': '3 days', - 'backup.keep.7days': '7 days', - 'backup.keep.14days': '14 days', - 'backup.keep.30days': '30 days', - 'backup.keep.forever': 'Keep forever', - - // Photos - 'photos.title': 'Photos', - 'photos.subtitle': '{count} photos for {trip}', - 'photos.dropHere': 'Drop photos here...', - 'photos.dropHereActive': 'Drop photos here', - 'photos.captionForAll': 'Caption (for all)', - 'photos.captionPlaceholder': 'Optional caption...', - 'photos.addCaption': 'Add caption...', - 'photos.allDays': 'All Days', - 'photos.noPhotos': 'No photos yet', - 'photos.uploadHint': 'Upload your travel photos', - 'photos.clickToSelect': 'or click to select', - 'photos.linkPlace': 'Link Place', - 'photos.noPlace': 'No Place', - 'photos.uploadN': '{n} photo(s) upload', - 'photos.linkDay': 'Link Day', - 'photos.noDay': 'No Day', - 'photos.dayLabel': 'Day {number}', - 'photos.photoSelected': 'Photo selected', - 'photos.photosSelected': 'Photos selected', - 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · up to 30 photos', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Restore Backup?', - 'backup.restoreWarning': 'All current data (trips, places, users, uploads) will be permanently replaced by the backup. This action cannot be undone.', - 'backup.restoreTip': 'Tip: Create a backup of the current state before restoring.', - 'backup.restoreConfirm': 'Yes, restore', - - // PDF - 'pdf.travelPlan': 'Travel Plan', - 'pdf.planned': 'Planned', - 'pdf.costLabel': 'Cost EUR', - 'pdf.preview': 'PDF Preview', - 'pdf.saveAsPdf': 'Save as PDF', - - // Planner - 'planner.places': 'Places', - 'planner.bookings': 'Bookings', - 'planner.packingList': 'Packing List', - 'planner.documents': 'Documents', - 'planner.dayPlan': 'Day Plan', - 'planner.reservations': 'Reservations', - 'planner.minTwoPlaces': 'At least 2 places with coordinates needed', - 'planner.noGeoPlaces': 'No places with coordinates available', - 'planner.routeCalculated': 'Route calculated', - 'planner.routeCalcFailed': 'Route could not be calculated', - 'planner.routeError': 'Error calculating route', - 'planner.icsExportFailed': 'ICS export failed', - 'planner.routeOptimized': 'Route optimized', - 'planner.reservationUpdated': 'Reservation updated', - 'planner.reservationAdded': 'Reservation added', - 'planner.confirmDeleteReservation': 'Delete reservation?', - 'planner.reservationDeleted': 'Reservation deleted', - 'planner.days': 'Days', - 'planner.allPlaces': 'All Places', - 'planner.totalPlaces': '{n} places total', - 'planner.noDaysPlanned': 'No days planned yet', - 'planner.editTrip': 'Edit trip \u2192', - 'planner.placeOne': '1 place', - 'planner.placeN': '{n} places', - 'planner.addNote': 'Add note', - 'planner.noEntries': 'No entries for this day', - 'planner.addPlace': 'Add place/activity', - 'planner.addPlaceShort': '+ Add place/activity', - 'planner.resPending': 'Reservation pending · ', - 'planner.resConfirmed': 'Reservation confirmed · ', - 'planner.notePlaceholder': 'Note\u2026', - 'planner.noteTimePlaceholder': 'Time (optional)', - 'planner.noteExamplePlaceholder': 'e.g. S3 at 14:30 from central station, ferry from pier 7, lunch break\u2026', - 'planner.totalCost': 'Total cost', - 'planner.searchPlaces': 'Search places\u2026', - 'planner.allCategories': 'All Categories', - 'planner.noPlacesFound': 'No places found', - 'planner.addFirstPlace': 'Add first place', - 'planner.noReservations': 'No reservations', - 'planner.addFirstReservation': 'Add first reservation', - 'planner.new': 'New', - 'planner.addToDay': '+ Day', - 'planner.calculating': 'Calculating\u2026', - 'planner.route': 'Route', - 'planner.optimize': 'Optimize', - 'planner.openGoogleMaps': 'Open in Google Maps', - 'planner.selectDayHint': 'Select a day from the left list to see the day plan', - 'planner.noPlacesForDay': 'No places for this day yet', - 'planner.addPlacesLink': 'Add places \u2192', - 'planner.minTotal': 'min. total', - 'planner.noReservation': 'No reservation', - 'planner.removeFromDay': 'Remove from day', - 'planner.addToThisDay': 'Add to day', - 'planner.overview': 'Overview', - 'planner.noDays': 'No days yet', - 'planner.editTripToAddDays': 'Edit trip to add days', - 'planner.dayCount': '{n} Days', - 'planner.clickToUnlock': 'Click to unlock', - 'planner.keepPosition': 'Keep position during route optimization', - 'planner.dayDetails': 'Day details', - 'planner.dayN': 'Day {n}', - - // Dashboard Stats - 'stats.countries': 'Countries', - 'stats.cities': 'Cities', - 'stats.trips': 'Trips', - 'stats.places': 'Places', - 'stats.worldProgress': 'World Progress', - 'stats.visited': 'visited', - 'stats.remaining': 'remaining', - 'stats.visitedCountries': 'Visited Countries', - - // Day Detail Panel - 'day.precipProb': 'Rain probability', - 'day.precipitation': 'Precipitation', - 'day.wind': 'Wind', - 'day.sunrise': 'Sunrise', - 'day.sunset': 'Sunset', - 'day.hourlyForecast': 'Hourly Forecast', - 'day.climateHint': 'Historical averages — real forecast available within 16 days of this date.', - 'day.noWeather': 'No weather data available. Add a place with coordinates.', - 'day.overview': 'Daily Overview', - 'day.accommodation': 'Accommodation', - 'day.addAccommodation': 'Add accommodation', - 'day.hotelDayRange': 'Apply to days', - 'day.noPlacesForHotel': 'Add places to your trip first', - 'day.allDays': 'All', - 'day.checkIn': 'Check-in', - 'day.checkInUntil': 'Until', - 'day.checkOut': 'Check-out', - 'day.confirmation': 'Confirmation', - 'day.editAccommodation': 'Edit accommodation', - 'day.reservations': 'Reservations', - - // Photos / Immich - 'memories.title': 'Photos', - 'memories.notConnected': '{provider_name} not connected', - 'memories.notConnectedHint': 'Connect your {provider_name} instance in Settings to be able add photos to this trip.', - 'memories.notConnectedMultipleHint': 'Connect any of these photo providers: {provider_names} in Settings to be able add photos to this trip.', - 'memories.noDates': 'Add dates to your trip to load photos.', - 'memories.noPhotos': 'No photos found', - 'memories.noPhotosHint': 'No photos found in {provider_name} for this trip\'s date range.', - 'memories.photosFound': 'photos', - 'memories.fromOthers': 'from others', - 'memories.sharePhotos': 'Share photos', - 'memories.sharing': 'Sharing', - 'memories.reviewTitle': 'Review your photos', - 'memories.reviewHint': 'Click photos to exclude them from sharing.', - 'memories.shareCount': 'Share {count} photos', - //------------------------- - //todo section - 'memories.providerUrl': 'Server URL', - 'memories.providerApiKey': 'API Key', - 'memories.providerUsername': 'Username', - 'memories.providerPassword': 'Password', - 'memories.providerOTP': 'MFA code (if enabled)', - 'memories.skipSSLVerification': 'Skip SSL certificate verification', - 'memories.immichAutoUpload': 'Mirror journey photos to Immich on upload', - 'memories.providerUrlHintSynology': 'Include the Photos app path in the URL, e.g. https://nas:5001/photo', - 'memories.testConnection': 'Test connection', - 'memories.testShort': 'Test', - 'memories.testFirst': 'Test connection first', - 'memories.connected': 'Connected', - 'memories.disconnected': 'Not connected', - 'memories.connectionSuccess': 'Connected to {provider_name}', - 'memories.connectionError': 'Could not connect to {provider_name}', - 'memories.saved': '{provider_name} settings saved', - 'memories.providerDisconnectedBanner': 'Your {provider_name} connection is lost. Reconnect in Settings to view photos.', - 'memories.saveError': 'Could not save {provider_name} settings', - //------------------------ - 'memories.addPhotos': 'Add photos', - 'memories.linkAlbum': 'Link Album', - 'memories.selectAlbum': 'Select {provider_name} Album', - 'memories.selectAlbumMultiple': 'Select Album', - 'memories.noAlbums': 'No albums found', - 'memories.syncAlbum': 'Sync album', - 'memories.unlinkAlbum': 'Unlink album', - 'memories.photos': 'photos', - 'memories.selectPhotos': 'Select photos from {provider_name}', - 'memories.selectPhotosMultiple': 'Select Photos', - 'memories.selectHint': 'Tap photos to select them.', - 'memories.selected': 'selected', - 'memories.addSelected': 'Add {count} photos', - 'memories.alreadyAdded': 'Added', - 'memories.private': 'Private', - 'memories.stopSharing': 'Stop sharing', - 'memories.oldest': 'Oldest first', - 'memories.newest': 'Newest first', - 'memories.allLocations': 'All locations', - 'memories.tripDates': 'Trip dates', - 'memories.allPhotos': 'All photos', - 'memories.confirmShareTitle': 'Share with trip members?', - 'memories.confirmShareHint': '{count} photos will be visible to all members of this trip. You can make individual photos private later.', - 'memories.confirmShareButton': 'Share photos', - 'memories.error.loadAlbums': 'Failed to load albums', - 'memories.error.linkAlbum': 'Failed to link album', - 'memories.error.unlinkAlbum': 'Failed to unlink album', - 'memories.error.syncAlbum': 'Failed to sync album', - 'memories.error.loadPhotos': 'Failed to load photos', - 'memories.error.addPhotos': 'Failed to add photos', - 'memories.error.removePhoto': 'Failed to remove photo', - 'memories.error.toggleSharing': 'Failed to update sharing', - 'memories.saveRouteNotConfigured': 'Save route is not configured for this provider', - 'memories.testRouteNotConfigured': 'Test route is not configured for this provider', - 'memories.fillRequiredFields': 'Please fill all required fields', - - // Collab Addon - 'collab.tabs.chat': 'Chat', - 'collab.tabs.notes': 'Notes', - 'collab.tabs.polls': 'Polls', - 'collab.whatsNext.title': "What's Next", - 'collab.whatsNext.today': 'Today', - 'collab.whatsNext.tomorrow': 'Tomorrow', - 'collab.whatsNext.empty': 'No upcoming activities', - 'collab.whatsNext.until': 'to', - 'collab.whatsNext.emptyHint': 'Activities with times will appear here', - 'collab.chat.send': 'Send', - 'collab.chat.placeholder': 'Type a message...', - 'collab.chat.empty': 'Start the conversation', - 'collab.chat.emptyHint': 'Messages are shared with all trip members', - 'collab.chat.emptyDesc': 'Share ideas, plans, and updates with your travel group', - 'collab.chat.today': 'Today', - 'collab.chat.yesterday': 'Yesterday', - 'collab.chat.deletedMessage': 'deleted a message', - 'collab.chat.reply': 'Reply', - 'collab.chat.loadMore': 'Load older messages', - 'collab.chat.justNow': 'just now', - 'collab.chat.minutesAgo': '{n}m ago', - 'collab.chat.hoursAgo': '{n}h ago', - 'collab.notes.title': 'Notes', - 'collab.notes.new': 'New Note', - 'collab.notes.empty': 'No notes yet', - 'collab.notes.emptyHint': 'Start capturing ideas and plans', - 'collab.notes.all': 'All', - 'collab.notes.titlePlaceholder': 'Note title', - 'collab.notes.contentPlaceholder': 'Write something...', - 'collab.notes.categoryPlaceholder': 'Category', - 'collab.notes.newCategory': 'New category...', - 'collab.notes.category': 'Category', - 'collab.notes.noCategory': 'No category', - 'collab.notes.color': 'Color', - 'collab.notes.save': 'Save', - 'collab.notes.cancel': 'Cancel', - 'collab.notes.edit': 'Edit', - 'collab.notes.delete': 'Delete', - 'collab.notes.pin': 'Pin', - 'collab.notes.unpin': 'Unpin', - 'collab.notes.daysAgo': '{n}d ago', - 'collab.notes.categorySettings': 'Manage Categories', - 'collab.notes.create': 'Create', - 'collab.notes.website': 'Website', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Attach files', - 'collab.notes.noCategoriesYet': 'No categories yet', - 'collab.notes.emptyDesc': 'Create a note to get started', - 'collab.polls.title': 'Polls', - 'collab.polls.new': 'New Poll', - 'collab.polls.empty': 'No polls yet', - 'collab.polls.emptyHint': 'Ask the group and vote together', - 'collab.polls.question': 'Question', - 'collab.polls.questionPlaceholder': 'What should we do?', - 'collab.polls.addOption': '+ Add option', - 'collab.polls.optionPlaceholder': 'Option {n}', - 'collab.polls.create': 'Create Poll', - 'collab.polls.close': 'Close', - 'collab.polls.closed': 'Closed', - 'collab.polls.votes': '{n} votes', - 'collab.polls.vote': '{n} vote', - 'collab.polls.multipleChoice': 'Multiple choice', - 'collab.polls.multiChoice': 'Multiple choice', - 'collab.polls.deadline': 'Deadline', - 'collab.polls.option': 'Option', - 'collab.polls.options': 'Options', - 'collab.polls.delete': 'Delete', - 'collab.polls.closedSection': 'Closed', - - // Permissions - 'admin.tabs.permissions': 'Permissions', - 'perm.title': 'Permission Settings', - 'perm.subtitle': 'Control who can perform actions across the application', - 'perm.saved': 'Permission settings saved', - 'perm.resetDefaults': 'Reset to defaults', - 'perm.customized': 'customized', - 'perm.level.admin': 'Admin only', - 'perm.level.tripOwner': 'Trip owner', - 'perm.level.tripMember': 'Trip members', - 'perm.level.everybody': 'Everyone', - 'perm.cat.trip': 'Trip Management', - 'perm.cat.members': 'Member Management', - 'perm.cat.files': 'Files', - 'perm.cat.content': 'Content & Schedule', - 'perm.cat.extras': 'Budget, Packing & Collaboration', - 'perm.action.trip_create': 'Create trips', - 'perm.action.trip_edit': 'Edit trip details', - 'perm.action.trip_delete': 'Delete trips', - 'perm.action.trip_archive': 'Archive / unarchive trips', - 'perm.action.trip_cover_upload': 'Upload cover image', - 'perm.action.member_manage': 'Add / remove members', - 'perm.action.file_upload': 'Upload files', - 'perm.action.file_edit': 'Edit file metadata', - 'perm.action.file_delete': 'Delete files', - 'perm.action.place_edit': 'Add / edit / delete places', - 'perm.action.day_edit': 'Edit days, notes & assignments', - 'perm.action.reservation_edit': 'Manage reservations', - 'perm.action.budget_edit': 'Manage budget', - 'perm.action.packing_edit': 'Manage packing lists', - 'perm.action.collab_edit': 'Collaboration (notes, polls, chat)', - 'perm.action.share_manage': 'Manage share links', - 'perm.actionHint.trip_create': 'Who can create new trips', - 'perm.actionHint.trip_edit': 'Who can change trip name, dates, description and currency', - 'perm.actionHint.trip_delete': 'Who can permanently delete a trip', - 'perm.actionHint.trip_archive': 'Who can archive or unarchive a trip', - 'perm.actionHint.trip_cover_upload': 'Who can upload or change the cover image', - 'perm.actionHint.member_manage': 'Who can invite or remove trip members', - 'perm.actionHint.file_upload': 'Who can upload files to a trip', - 'perm.actionHint.file_edit': 'Who can edit file descriptions and links', - 'perm.actionHint.file_delete': 'Who can move files to trash or permanently delete them', - 'perm.actionHint.place_edit': 'Who can add, edit or delete places', - 'perm.actionHint.day_edit': 'Who can edit days, day notes and place assignments', - 'perm.actionHint.reservation_edit': 'Who can create, edit or delete reservations', - 'perm.actionHint.budget_edit': 'Who can create, edit or delete budget items', - 'perm.actionHint.packing_edit': 'Who can manage packing items and bags', - 'perm.actionHint.collab_edit': 'Who can create notes, polls and send messages', - 'perm.actionHint.share_manage': 'Who can create or delete public share links', - - // Undo - 'undo.button': 'Undo', - 'undo.tooltip': 'Undo: {action}', - 'undo.assignPlace': 'Place assigned to day', - 'undo.removeAssignment': 'Place removed from day', - 'undo.reorder': 'Places reordered', - 'undo.optimize': 'Route optimized', - 'undo.deletePlace': 'Place deleted', - 'undo.deletePlaces': 'Places deleted', - 'undo.moveDay': 'Place moved to another day', - 'undo.lock': 'Place lock toggled', - 'undo.importGpx': 'GPX import', - 'undo.importKeyholeMarkup': 'KMZ/KML import', - 'undo.importGoogleList': 'Google Maps import', - 'undo.importNaverList': 'Naver Maps import', - 'undo.addPlace': 'Place added', - 'undo.done': 'Undone: {action}', - - // Notifications - 'notifications.title': 'Notifications', - 'notifications.markAllRead': 'Mark all read', - 'notifications.deleteAll': 'Delete all', - 'notifications.showAll': 'Show all notifications', - 'notifications.empty': 'No notifications', - 'notifications.emptyDescription': "You're all caught up!", - 'notifications.all': 'All', - 'notifications.unreadOnly': 'Unread', - 'notifications.markRead': 'Mark as read', - 'notifications.markUnread': 'Mark as unread', - 'notifications.delete': 'Delete', - 'notifications.system': 'System', - 'notifications.synologySessionCleared.title': 'Synology Photos disconnected', - 'notifications.synologySessionCleared.text': 'Your server or account changed — go to Settings to test your connection again.', - - // Notification test keys (dev only) - 'notifications.versionAvailable.title': 'Update Available', - 'notifications.versionAvailable.text': 'TREK {version} is now available.', - 'notifications.versionAvailable.button': 'View Details', - 'notifications.test.title': 'Test notification from {actor}', - 'notifications.test.text': 'This is a simple test notification.', - 'notifications.test.booleanTitle': '{actor} asks for your approval', - 'notifications.test.booleanText': 'This is a test boolean notification. Choose an action below.', - 'notifications.test.accept': 'Approve', - 'notifications.test.decline': 'Decline', - 'notifications.test.navigateTitle': 'Check something out', - 'notifications.test.navigateText': 'This is a test navigate notification.', - 'notifications.test.goThere': 'Go there', - 'notifications.test.adminTitle': 'Admin broadcast', - 'notifications.test.adminText': '{actor} sent a test notification to all admins.', - 'notifications.test.tripTitle': '{actor} posted in your trip', - 'notifications.test.tripText': 'Test notification for trip "{trip}".', - - // Todo - 'todo.subtab.packing': 'Packing List', - 'todo.subtab.todo': 'To-Do', - 'todo.completed': 'completed', - 'todo.filter.all': 'All', - 'todo.filter.open': 'Open', - 'todo.filter.done': 'Done', - 'todo.uncategorized': 'Uncategorized', - 'todo.namePlaceholder': 'Task name', - 'todo.descriptionPlaceholder': 'Description (optional)', - 'todo.unassigned': 'Unassigned', - 'todo.noCategory': 'No category', - 'todo.hasDescription': 'Has description', - 'todo.addItem': 'Add new task', - 'todo.sidebar.sortBy': 'Sort by', - 'todo.priority': 'Priority', - 'todo.newCategoryLabel': 'new', - 'budget.categoriesLabel': 'categories', - 'todo.newCategory': 'Category name', - 'todo.addCategory': 'Add category', - 'todo.newItem': 'New task', - 'todo.empty': 'No tasks yet. Add a task to get started!', - 'todo.filter.my': 'My Tasks', - 'todo.filter.overdue': 'Overdue', - 'todo.sidebar.tasks': 'Tasks', - 'todo.sidebar.categories': 'Categories', - 'todo.detail.title': 'Task', - 'todo.detail.description': 'Description', - 'todo.detail.category': 'Category', - 'todo.detail.dueDate': 'Due date', - 'todo.detail.assignedTo': 'Assigned to', - 'todo.detail.delete': 'Delete', - 'todo.detail.save': 'Save changes', - 'todo.sortByPrio': 'Priority', - 'todo.detail.priority': 'Priority', - 'todo.detail.noPriority': 'None', - 'todo.detail.create': 'Create task', - - // Notifications — dev test events - 'notif.test.title': '[Test] Notification', - 'notif.test.simple.text': 'This is a simple test notification.', - 'notif.test.boolean.text': 'Do you accept this test notification?', - 'notif.test.navigate.text': 'Click below to navigate to the dashboard.', - - // Notifications - 'notif.trip_invite.title': 'Trip Invitation', - 'notif.trip_invite.text': '{actor} invited you to {trip}', - 'notif.booking_change.title': 'Booking Updated', - 'notif.booking_change.text': '{actor} updated a booking in {trip}', - 'notif.trip_reminder.title': 'Trip Reminder', - 'notif.trip_reminder.text': 'Your trip {trip} is coming up soon!', - 'notif.todo_due.title': 'To-do due', - 'notif.todo_due.text': '{todo} in {trip} is due on {due}', - 'notif.vacay_invite.title': 'Vacay Fusion Invite', - 'notif.vacay_invite.text': '{actor} invited you to fuse vacation plans', - 'notif.photos_shared.title': 'Photos Shared', - 'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}', - 'notif.collab_message.title': 'New Message', - 'notif.collab_message.text': '{actor} sent a message in {trip}', - 'notif.packing_tagged.title': 'Packing Assignment', - 'notif.packing_tagged.text': '{actor} assigned you to {category} in {trip}', - 'notif.version_available.title': 'New Version Available', - 'notif.version_available.text': 'TREK {version} is now available', - 'notif.action.view_trip': 'View Trip', - 'notif.action.view_collab': 'View Messages', - 'notif.action.view_packing': 'View Packing', - 'notif.action.view_photos': 'View Photos', - 'notif.action.view_vacay': 'View Vacay', - 'notif.action.view_admin': 'Go to Admin', - 'notif.action.view': 'View', - 'notif.action.accept': 'Accept', - 'notif.action.decline': 'Decline', - 'notif.generic.title': 'Notification', - 'notif.generic.text': 'You have a new notification', - 'notif.dev.unknown_event.title': '[DEV] Unknown Event', - 'notif.dev.unknown_event.text': 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG', - - // Journey addon - 'journey.search.placeholder': 'Search journeys…', - 'journey.search.noResults': 'No journeys match "{query}"', - 'journey.title': 'Journey', - 'journey.subtitle': 'Track your travels as they happen', - 'journey.new': 'New Journey', - 'journey.create': 'Create', - 'journey.titlePlaceholder': 'Where are you going?', - 'journey.empty': 'No journeys yet', - 'journey.emptyHint': 'Start documenting your next trip', - 'journey.deleted': 'Journey deleted', - 'journey.createError': 'Could not create journey', - 'journey.deleteError': 'Could not delete journey', - 'journey.deleteConfirmTitle': 'Delete', - 'journey.deleteConfirmMessage': 'Delete "{title}"? This cannot be undone.', - 'journey.deleteConfirmGeneric': 'Are you sure you want to delete this?', - 'journey.notFound': 'Journey not found', - 'journey.photos': 'Photos', - 'journey.timelineEmpty': 'No stops yet', - 'journey.timelineEmptyHint': 'Add a check-in or write a journal entry to get started', - 'journey.status.draft': 'Draft', - 'journey.status.active': 'Active', - 'journey.status.completed': 'Completed', - 'journey.status.upcoming': 'Upcoming', - 'journey.status.archived': 'Archived', - 'journey.checkin.add': 'Check in', - 'journey.checkin.namePlaceholder': 'Location name', - 'journey.checkin.notesPlaceholder': 'Notes (optional)', - 'journey.checkin.save': 'Save', - 'journey.checkin.error': 'Could not save check-in', - 'journey.entry.add': 'Journal', - 'journey.entry.edit': 'Edit entry', - 'journey.entry.titlePlaceholder': 'Title (optional)', - 'journey.entry.bodyPlaceholder': 'What happened today?', - 'journey.entry.save': 'Save', - 'journey.entry.error': 'Could not save entry', - 'journey.photo.add': 'Photo', - 'journey.photo.uploadError': 'Upload failed', - 'journey.share.share': 'Share', - 'journey.share.public': 'Public', - 'journey.share.linkCopied': 'Public link copied', - 'journey.share.disabled': 'Public sharing disabled', - 'journey.editor.titlePlaceholder': 'Give this moment a name...', - 'journey.editor.bodyPlaceholder': 'Tell the story of this day...', - 'journey.editor.placePlaceholder': 'Location (optional)', - 'journey.editor.tagsPlaceholder': 'Tags: hidden gem, best meal, must revisit...', - 'journey.visibility.private': 'Private', - 'journey.visibility.shared': 'Shared', - 'journey.visibility.public': 'Public', - 'journey.emptyState.title': 'Your story starts here', - 'journey.emptyState.subtitle': 'Check in at a place or write your first journal entry', - - // Journey Frontpage - 'journey.frontpage.subtitle': 'Turn your trips into stories you\'ll never forget', - 'journey.frontpage.createJourney': 'Create Journey', - 'journey.frontpage.activeJourney': 'Active Journey', - 'journey.frontpage.allJourneys': 'All Journeys', - 'journey.frontpage.journeys': 'journeys', - 'journey.frontpage.createNew': 'Create a new Journey', - 'journey.frontpage.createNewSub': 'Pick trips, write stories, share your adventures', - 'journey.frontpage.live': 'Live', - 'journey.frontpage.synced': 'Synced', - 'journey.frontpage.continueWriting': 'Continue writing', - 'journey.frontpage.updated': 'Updated {time}', - 'journey.frontpage.suggestionLabel': 'Trip just ended', - 'journey.frontpage.suggestionText': 'Turn {title} into a Journey', - 'journey.frontpage.dismiss': 'Dismiss', - 'journey.frontpage.journeyName': 'Journey Name', - 'journey.frontpage.namePlaceholder': 'e.g. Southeast Asia 2026', - 'journey.frontpage.selectTrips': 'Select Trips', - 'journey.frontpage.tripsSelected': 'trips selected', - 'journey.frontpage.trips': 'trips', - 'journey.frontpage.placesImported': 'places will be imported', - 'journey.frontpage.places': 'places', - - // Journey Detail - 'journey.detail.backToJourney': 'Back to Journey', - 'journey.detail.syncedWithTrips': 'Synced with Trips', - 'journey.detail.addEntry': 'Add Entry', - 'journey.detail.newEntry': 'New Entry', - 'journey.detail.editEntry': 'Edit Entry', - 'journey.detail.noEntries': 'No entries yet', - 'journey.detail.noEntriesHint': 'Add a trip to get started with skeleton entries', - 'journey.detail.noPhotos': 'No photos yet', - 'journey.detail.noPhotosHint': 'Upload photos to entries or browse your Immich/Synology library', - 'journey.detail.journeyTab': 'Journey', - 'journey.detail.journeyStats': 'Journey Stats', - 'journey.detail.syncedTrips': 'Synced Trips', - 'journey.detail.noTripsLinked': 'No trips linked yet', - 'journey.detail.contributors': 'Contributors', - 'journey.detail.readMore': 'Read more', - 'journey.detail.prosCons': 'Pros & Cons', - 'journey.detail.photos': 'photos', - 'journey.detail.day': 'Day {number}', - 'journey.detail.places': 'places', - - // Journey Detail — Stats - 'journey.stats.days': 'Days', - 'journey.stats.cities': 'Cities', - 'journey.stats.entries': 'Entries', - 'journey.stats.photos': 'Photos', - 'journey.stats.places': 'Places', - 'journey.skeletons.show': 'Show suggestions', - 'journey.skeletons.hide': 'Hide suggestions', - - // Journey Detail — Verdict - 'journey.verdict.lovedIt': 'Loved it', - 'journey.verdict.couldBeBetter': 'Could be better', - - // Journey Detail — Synced badge - 'journey.synced.places': 'places', - 'journey.synced.synced': 'synced', - - // Journey Entry Editor - 'journey.editor.discardChangesConfirm': 'You have unsaved changes. Discard them?', - 'journey.editor.uploadPhotos': 'Upload photos', - 'journey.editor.uploading': 'Uploading...', - 'journey.editor.fromGallery': 'From Gallery', - 'journey.editor.allPhotosAdded': 'All photos already added', - 'journey.editor.writeStory': 'Write your story...', - 'journey.editor.prosCons': 'Pros & Cons', - 'journey.editor.pros': 'Pros', - 'journey.editor.cons': 'Cons', - 'journey.editor.proPlaceholder': 'Something great...', - 'journey.editor.conPlaceholder': 'Not so great...', - 'journey.editor.addAnother': 'Add another', - 'journey.editor.date': 'Date', - 'journey.editor.location': 'Location', - 'journey.editor.searchLocation': 'Search location...', - 'journey.editor.mood': 'Mood', - 'journey.editor.weather': 'Weather', - 'journey.editor.photoFirst': '1st', - 'journey.editor.makeFirst': 'Make 1st', - 'journey.editor.searching': 'Searching...', - - // Journey Entry — Moods - 'journey.mood.amazing': 'Amazing', - 'journey.mood.good': 'Good', - 'journey.mood.neutral': 'Neutral', - 'journey.mood.rough': 'Rough', - - // Journey Entry — Weather - 'journey.weather.sunny': 'Sunny', - 'journey.weather.partly': 'Partly cloudy', - 'journey.weather.cloudy': 'Cloudy', - 'journey.weather.rainy': 'Rainy', - 'journey.weather.stormy': 'Stormy', - 'journey.weather.cold': 'Snowy', - - // Journey — Trip Linking - 'journey.trips.linkTrip': 'Link Trip', - 'journey.trips.searchTrip': 'Search Trip', - 'journey.trips.searchPlaceholder': 'Trip name or destination...', - 'journey.trips.noTripsAvailable': 'No trips available', - 'journey.trips.link': 'Link', - 'journey.trips.tripLinked': 'Trip linked', - 'journey.trips.linkFailed': 'Failed to link trip', - 'journey.trips.addTrip': 'Add Trip', - 'journey.trips.unlinkTrip': 'Unlink Trip', - 'journey.trips.unlinkMessage': 'Unlink "{title}"? All synced entries and photos from this trip will be permanently deleted. This cannot be undone.', - 'journey.trips.unlink': 'Unlink', - 'journey.trips.tripUnlinked': 'Trip unlinked', - 'journey.trips.unlinkFailed': 'Failed to unlink trip', - 'journey.trips.noTripsLinkedSettings': 'No trips linked', - - // Journey — Contributors - 'journey.contributors.invite': 'Invite Contributor', - 'journey.contributors.searchUser': 'Search User', - 'journey.contributors.searchPlaceholder': 'Username or email...', - 'journey.contributors.noUsers': 'No users found', - 'journey.contributors.role': 'Role', - 'journey.contributors.added': 'Contributor added', - 'journey.contributors.addFailed': 'Failed to add contributor', - 'journey.contributors.remove': 'Remove contributor', - 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', - 'journey.contributors.removed': 'Contributor removed', - 'journey.contributors.removeFailed': 'Failed to remove contributor', - - // Journey — Share - 'journey.share.publicShare': 'Public Share', - 'journey.share.createLink': 'Create share link', - 'journey.share.linkCreated': 'Share link created', - 'journey.share.createFailed': 'Failed to create link', - 'journey.share.copy': 'Copy', - 'journey.share.copied': 'Copied!', - 'journey.share.timeline': 'Timeline', - 'journey.share.gallery': 'Gallery', - 'journey.share.map': 'Map', - 'journey.share.removeLink': 'Remove share link', - 'journey.share.linkDeleted': 'Share link deleted', - 'journey.share.deleteFailed': 'Failed to delete', - 'journey.share.updateFailed': 'Failed to update', - - // Journey — Invite - 'journey.invite.role': 'Role', - 'journey.invite.viewer': 'Viewer', - 'journey.invite.editor': 'Editor', - 'journey.invite.invite': 'Invite', - 'journey.invite.inviting': 'Inviting...', - - // Journey — Settings Dialog - 'journey.settings.title': 'Journey Settings', - 'journey.settings.coverImage': 'Cover Image', - 'journey.settings.changeCover': 'Change cover', - 'journey.settings.addCover': 'Add cover image', - 'journey.settings.name': 'Name', - 'journey.settings.subtitle': 'Subtitle', - 'journey.settings.subtitlePlaceholder': 'e.g. Thailand, Vietnam & Cambodia', - 'journey.settings.endJourney': 'Archive Journey', - 'journey.settings.reopenJourney': 'Restore Journey', - 'journey.settings.archived': 'Journey archived', - 'journey.settings.reopened': 'Journey reopened', - 'journey.settings.endDescription': 'Hides the Live badge. You can reopen anytime.', - 'journey.settings.delete': 'Delete', - 'journey.settings.deleteJourney': 'Delete Journey', - 'journey.settings.deleteMessage': 'Delete "{title}"? All entries and photos will be lost.', - 'journey.settings.saved': 'Settings saved', - 'journey.settings.saveFailed': 'Failed to save', - 'journey.settings.coverUpdated': 'Cover updated', - 'journey.settings.coverFailed': 'Upload failed', - 'journey.settings.failedToDelete': 'Failed to delete', - 'journey.entries.deleteTitle': 'Delete Entry', - 'journey.photosUploaded': '{count} photos uploaded', - 'journey.photosAdded': '{count} photos added', - - // Journey — Public Page - 'journey.public.notFound': 'Not Found', - 'journey.public.notFoundMessage': 'This journey doesn\'t exist or the link has expired.', - 'journey.public.readOnly': 'Read-only · Public Journey', - 'journey.public.tagline': 'Travel Resource & Exploration Kit', - 'journey.public.sharedVia': 'Shared via', - 'journey.public.madeWith': 'Made with', - - // Journey — PDF Export - 'journey.pdf.journeyBook': 'Journey Book', - 'journey.pdf.madeWith': 'Made with TREK', - 'journey.pdf.day': 'Day', - 'journey.pdf.theEnd': 'The End', - 'journey.pdf.saveAsPdf': 'Save as PDF', - 'journey.pdf.pages': 'pages', - 'journey.picker.tripPeriod': 'Trip Period', - 'journey.picker.dateRange': 'Date Range', - 'journey.picker.allPhotos': 'All Photos', - 'journey.picker.albums': 'Albums', - 'journey.picker.selected': 'selected', - 'journey.picker.addTo': 'Add to', - 'journey.picker.newGallery': 'New Gallery', - 'journey.picker.selectAll': 'Select all', - 'journey.picker.deselectAll': 'Deselect all', - 'journey.picker.noAlbums': 'No albums found', - 'journey.picker.selectDate': 'Select date', - 'journey.picker.search': 'Search', - - // Dashboard Mobile - 'dashboard.greeting.morning': 'Good morning,', - 'dashboard.greeting.afternoon': 'Good afternoon,', - 'dashboard.greeting.evening': 'Good evening,', - 'dashboard.mobile.liveNow': 'Live Now', - 'dashboard.mobile.tripProgress': 'Trip progress', - 'dashboard.mobile.daysLeft': '{count} days left', - 'dashboard.mobile.places': 'Places', - 'dashboard.mobile.buddies': 'Buddies', - 'dashboard.mobile.newTrip': 'New Trip', - 'dashboard.mobile.currency': 'Currency', - 'dashboard.mobile.timezone': 'Timezone', - 'dashboard.mobile.upcomingTrips': 'Upcoming Trips', - 'dashboard.mobile.yourTrips': 'Your Trips', - 'dashboard.mobile.trips': 'trips', - 'dashboard.mobile.starts': 'Starts', - 'dashboard.mobile.duration': 'Duration', - 'dashboard.mobile.day': 'day', - 'dashboard.mobile.days': 'days', - 'dashboard.mobile.ongoing': 'Ongoing', - 'dashboard.mobile.startsToday': 'Starts today', - 'dashboard.mobile.tomorrow': 'Tomorrow', - 'dashboard.mobile.inDays': 'In {count} days', - 'dashboard.mobile.inMonths': 'In {count} months', - 'dashboard.mobile.completed': 'Completed', - 'dashboard.mobile.currencyConverter': 'Currency Converter', - - // BottomNav & Profile - 'nav.profile': 'Profil', - 'nav.bottomSettings': 'Ayarlar', - 'nav.bottomAdmin': 'Yönetim ayarları', - 'nav.bottomLogout': 'Çıkış', - 'nav.bottomAdminBadge': 'Yönetici', - - // DayPlan Mobile - 'dayplan.mobile.addPlace': 'Add Place', - 'dayplan.mobile.searchPlaces': 'Search places...', - 'dayplan.mobile.allAssigned': 'All places assigned', - 'dayplan.mobile.noMatch': 'No match', - 'dayplan.mobile.createNew': 'Create new place', - - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': 'Trip tracking & travel journal with check-ins, photos, and daily stories', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Trips', - 'oauth.scope.group.places': 'Places', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Packing', - 'oauth.scope.group.todos': 'To-dos', - 'oauth.scope.group.budget': 'Budget', - 'oauth.scope.group.reservations': 'Reservations', - 'oauth.scope.group.collab': 'Collaboration', - 'oauth.scope.group.notifications': 'Notifications', - 'oauth.scope.group.vacay': 'Vacation', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Weather', - 'oauth.scope.group.journey': 'Journey', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'View trips & itineraries', - 'oauth.scope.trips:read.description': 'Read trips, days, day notes, and members', - 'oauth.scope.trips:write.label': 'Edit trips & itineraries', - 'oauth.scope.trips:write.description': 'Create and update trips, days, notes, and manage members', - 'oauth.scope.trips:delete.label': 'Delete trips', - 'oauth.scope.trips:delete.description': 'Permanently delete entire trips — this action is irreversible', - 'oauth.scope.trips:share.label': 'Manage share links', - 'oauth.scope.trips:share.description': 'Create, update, and revoke public share links for trips', - 'oauth.scope.places:read.label': 'View places & map data', - 'oauth.scope.places:read.description': 'Read places, day assignments, tags, and categories', - 'oauth.scope.places:write.label': 'Manage places', - 'oauth.scope.places:write.description': 'Create, update, and delete places, assignments, and tags', - 'oauth.scope.atlas:read.label': 'View Atlas', - 'oauth.scope.atlas:read.description': 'Read visited countries, regions, and bucket list', - 'oauth.scope.atlas:write.label': 'Manage Atlas', - 'oauth.scope.atlas:write.description': 'Mark countries and regions visited, manage bucket list', - 'oauth.scope.packing:read.label': 'View packing lists', - 'oauth.scope.packing:read.description': 'Read packing items, bags, and category assignees', - 'oauth.scope.packing:write.label': 'Manage packing lists', - 'oauth.scope.packing:write.description': 'Add, update, delete, toggle, and reorder packing items and bags', - 'oauth.scope.todos:read.label': 'View to-do lists', - 'oauth.scope.todos:read.description': 'Read trip to-do items and category assignees', - 'oauth.scope.todos:write.label': 'Manage to-do lists', - 'oauth.scope.todos:write.description': 'Create, update, toggle, delete, and reorder to-do items', - 'oauth.scope.budget:read.label': 'View budget', - 'oauth.scope.budget:read.description': 'Read budget items and expense breakdown', - 'oauth.scope.budget:write.label': 'Manage budget', - 'oauth.scope.budget:write.description': 'Create, update, and delete budget items', - 'oauth.scope.reservations:read.label': 'View reservations', - 'oauth.scope.reservations:read.description': 'Read reservations and accommodation details', - 'oauth.scope.reservations:write.label': 'Manage reservations', - 'oauth.scope.reservations:write.description': 'Create, update, delete, and reorder reservations', - 'oauth.scope.collab:read.label': 'View collaboration', - 'oauth.scope.collab:read.description': 'Read collab notes, polls, and messages', - 'oauth.scope.collab:write.label': 'Manage collaboration', - 'oauth.scope.collab:write.description': 'Create, update, and delete collab notes, polls, and messages', - 'oauth.scope.notifications:read.label': 'View notifications', - 'oauth.scope.notifications:read.description': 'Read in-app notifications and unread counts', - 'oauth.scope.notifications:write.label': 'Manage notifications', - 'oauth.scope.notifications:write.description': 'Mark notifications as read and respond to them', - 'oauth.scope.vacay:read.label': 'View vacation plans', - 'oauth.scope.vacay:read.description': 'Read vacation planning data, entries, and stats', - 'oauth.scope.vacay:write.label': 'Manage vacation plans', - 'oauth.scope.vacay:write.description': 'Create and manage vacation entries, holidays, and team plans', - 'oauth.scope.geo:read.label': 'Maps & geocoding', - 'oauth.scope.geo:read.description': 'Search locations, resolve map URLs, and reverse geocode coordinates', - 'oauth.scope.weather:read.label': 'Weather forecasts', - 'oauth.scope.weather:read.description': 'Fetch weather forecasts for trip locations and dates', - 'oauth.scope.journey:read.label': 'View journeys', - 'oauth.scope.journey:read.description': 'Read journeys, entries, and contributor list', - 'oauth.scope.journey:write.label': 'Manage journeys', - 'oauth.scope.journey:write.description': 'Create, update, and delete journeys and their entries', - 'oauth.scope.journey:share.label': 'Manage journey links', - 'oauth.scope.journey:share.description': 'Create, update, and revoke public share links for journeys', - - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Photos have moved in 3.0', - 'system_notice.v3_photos.body': '**Photos** in the Trip Planner have been removed. Your photos are safe — TREK never modified your Immich or Synology library.\n\nPhotos now live in the **Journey** addon. Journey is optional — if it is not yet available, ask your admin to enable it under Admin → Addons.', - 'system_notice.v3_journey.title': 'Meet Journey — travel journal', - 'system_notice.v3_journey.body': 'Document your trips as rich travel stories with timelines, photo galleries, and interactive maps.', - 'system_notice.v3_journey.cta_label': 'Open Journey', - 'system_notice.v3_journey.highlight_timeline': 'Day-by-day timeline & gallery', - 'system_notice.v3_journey.highlight_photos': 'Import from Immich or Synology', - 'system_notice.v3_journey.highlight_share': 'Share publicly — no login needed', - 'system_notice.v3_journey.highlight_export': 'Export as a PDF photo book', - 'system_notice.v3_features.title': 'More highlights in 3.0', - 'system_notice.v3_features.body': 'A few more things worth knowing about this release.', - 'system_notice.v3_features.highlight_dashboard': 'Mobile-first dashboard redesign', - 'system_notice.v3_features.highlight_offline': 'Full offline mode as a PWA', - 'system_notice.v3_features.highlight_search': 'Real-time place search autocomplete', - 'system_notice.v3_features.highlight_import': 'Import places from KMZ/KML files', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 upgrade', - 'system_notice.v3_mcp.body': 'The MCP integration has been fully overhauled. OAuth 2.1 is now the recommended auth method. Legacy static tokens (trek_\u2026) are deprecated and will be removed in a future release.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommended (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 fine-grained permission scopes', - 'system_notice.v3_mcp.highlight_deprecated': 'Static trek_ tokens deprecated', - 'system_notice.v3_mcp.highlight_tools': 'Expanded toolset & prompts', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'A personal note from me', - 'system_notice.v3_thankyou.body': 'Before you go — I want to take a moment.\n\nTREK started as a side project I built for my own trips. I never imagined it would grow into something that 4,000 of you now trust to plan your adventures. Every star, every issue, every feature request — I read them all, and they keep me going through late nights between a full-time job and university.\n\nI want you to know: TREK will always be open source, always self-hosted, always yours. No tracking, no subscriptions, no strings attached. Just a tool built by someone who loves traveling as much as you do.\n\nSpecial thanks to [jubnl](https://github.com/jubnl) — you have become an incredible collaborator. So much of what makes 3.0 great carries your fingerprints. Thank you for believing in this project when it was still rough around the edges.\n\nAnd to every single one of you who filed a bug, translated a string, shared TREK with a friend, or simply used it to plan a trip — **thank you**. You are the reason this exists.\n\nHere\'s to many more adventures together.\n\n— Maurice\n\n---\n\n[Join the community on Discord](https://discord.gg/7Q6M6jDwzf)\n\nIf TREK makes your travels better, a [small coffee](https://ko-fi.com/mauriceboe) always keeps the lights on.', - - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': 'Action required: user account conflict', - 'system_notice.v3014_whitespace_collision.body': 'The 3.0.14 upgrade detected one or more username or email collisions caused by leading/trailing whitespace in stored accounts. Affected accounts were renamed automatically. Check the server logs for lines starting with **[migration] WHITESPACE COLLISION** to identify which accounts need review.', - - // System notices — onboarding - 'system_notice.welcome_v1.title': 'Welcome to TREK', - 'system_notice.welcome_v1.body': 'Your all-in-one travel planner. Build itineraries, share trips with friends, and stay organized — online or offline.', - 'system_notice.welcome_v1.cta_label': 'Plan a trip', - 'system_notice.welcome_v1.hero_alt': 'A scenic travel destination with TREK planning UI overlay', - 'system_notice.welcome_v1.highlight_plan': 'Day-by-day itineraries for any trip', - 'system_notice.welcome_v1.highlight_share': 'Collaborate with travel partners', - 'system_notice.welcome_v1.highlight_offline': 'Works offline on mobile', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Previous notice', - 'system_notice.pager.next': 'Next notice', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Go to notice {n}', - 'system_notice.pager.position': 'Notice {current} of {total}', - 'transport.addTransport': 'Add transport', - 'transport.modalTitle.create': 'Add transport', - 'transport.modalTitle.edit': 'Edit transport', - 'transport.title': 'Transports', - 'transport.addManual': 'Manual Transport', - - // Added to match EN keys - 'journey.editor.uploadingProgress': 'Yükleniyor {done}/{total}…', - 'journey.editor.uploadFailed': 'Fotoğraf yüklenemedi', - 'journey.editor.uploadPartialFailed': '{total} fotoğraftan {failed} tanesi yüklenemedi — yeniden denemek için tekrar kaydedin', - 'journey.photosUploadFailed': 'Bazı fotoğraflar yüklenemedi', - 'settings.oauth.modal.machineClient': 'Makine istemcisi (tarayıcı girişi yok)', - 'settings.oauth.modal.machineClientHint': 'client_credentials iznini kullanın — yönlendirme URI\'lerine gerek yoktur. Belirteç doğrudan client_id + client_secret ile verilir ve seçilen kapsamlar dahilinde sizin adınıza hareket eder.', - 'settings.oauth.modal.machineClientUsage': 'Belirteç alın: grant_type=client_credentials, client_id ve client_secret ile POST /oauth/token gönderin. Tarayıcı yok, yenileme belirteci yok.', - 'settings.oauth.badge.machine': 'makine', -} - -export default tr diff --git a/client/src/i18n/translations/uk.ts b/client/src/i18n/translations/uk.ts deleted file mode 100644 index 01eb0609..00000000 --- a/client/src/i18n/translations/uk.ts +++ /dev/null @@ -1,2390 +0,0 @@ -const uk: Record = { - // Common - 'common.save': 'Зберегти', - 'common.showMore': 'Показати більше', - 'common.showLess': 'Показати менше', - 'common.cancel': 'Скасувати', - 'common.clear': 'Очистити', - 'common.delete': 'Видалити', - 'common.edit': 'Редагувати', - 'common.add': 'Додати', - 'common.loading': 'Завантаження...', - 'common.import': 'Імпорт', - 'common.select': 'Вибрати', - 'common.selectAll': 'Вибрати все', - 'common.deselectAll': 'Зняти виділення з усіх', - 'common.error': 'Помилка', - 'common.unknownError': 'Невідома помилка', - 'common.tooManyAttempts': 'Занадто багато спроб. Спробуйте пізніше.', - 'common.back': 'Назад', - 'common.all': 'Все', - 'common.close': 'Закрити', - 'common.open': 'Відкрити', - 'common.upload': 'Завантажити', - 'common.search': 'Пошук', - 'common.confirm': 'Підтвердити', - 'common.ok': 'ОК', - 'common.yes': 'Так', - 'common.no': 'Ні', - 'common.or': 'або', - 'common.none': 'Немає', - 'common.date': 'Дата', - 'common.rename': 'Перейменувати', - 'common.discardChanges': 'Скасувати зміни', - 'common.discard': 'Скасувати', - 'common.name': 'Ім\'я', - 'common.email': 'Ел. пошта', - 'common.password': 'Пароль', - 'common.saving': 'Збереження...', - 'common.saved': 'Збережено', - 'common.expand': 'Розгорнути', - 'common.collapse': 'Згорнути', - 'trips.memberRemoved': '{username} видалений', - 'trips.memberRemoveError': 'Не вдалося видалити', - 'trips.memberAdded': '{username} доданий', - 'trips.memberAddError': 'Не вдалося додати', - 'trips.reminder': 'Нагадування', - 'trips.reminderNone': 'Немає', - 'trips.reminderDay': 'день', - 'trips.reminderDays': 'днів', - 'trips.reminderCustom': 'Інше', - 'trips.reminderDaysBefore': 'днів до від\'їзду', - 'trips.reminderDisabledHint': 'Нагадування про поїздки вимкнено. Увімкніть їх в Адмін > Налаштування > Сповіщення.', - 'common.update': 'Оновити', - 'common.change': 'Змінити', - 'common.uploading': 'Завантаження…', - 'common.backToPlanning': 'Повернутися до планування', - 'common.reset': 'Скинути', - - // Navbar - 'nav.trip': 'Поїздка', - 'nav.share': 'Поділитися', - 'nav.settings': 'Налаштування', - 'nav.admin': 'Адмін', - 'nav.logout': 'Вийти', - 'nav.lightMode': 'Світла тема', - 'nav.darkMode': 'Темна тема', - 'nav.autoMode': 'Авто', - 'nav.administrator': 'Адміністратор', - - // Dashboard - 'dashboard.title': 'Мої поїздки', - 'dashboard.subtitle.loading': 'Завантаження поїздок...', - 'dashboard.subtitle.trips': '{count} поїздок ({archived} в архіві)', - 'dashboard.subtitle.empty': 'Почніть свою першу поїздку', - 'dashboard.subtitle.activeOne': '{count} активна поїздка', - 'dashboard.subtitle.activeMany': '{count} активних поїздок', - 'dashboard.subtitle.archivedSuffix': ' · {count} в архіві', - 'dashboard.newTrip': 'Нова поїздка', - 'dashboard.gridView': 'Плитка', - 'dashboard.listView': 'Список', - 'dashboard.currency': 'Валюта', - 'dashboard.timezone': 'Часові пояси', - 'dashboard.localTime': 'Місцеве', - 'dashboard.timezoneCustomTitle': 'Свій часовий пояс', - 'dashboard.timezoneCustomLabelPlaceholder': 'Назва (необов\'язково)', - 'dashboard.timezoneCustomTzPlaceholder': 'напр. America/New_York', - 'dashboard.timezoneCustomAdd': 'Додати', - 'dashboard.timezoneCustomErrorEmpty': 'Введіть ідентифікатор часового поясу', - 'dashboard.timezoneCustomErrorInvalid': 'Невірний часовий пояс. Використовуйте формат Europe/Berlin', - 'dashboard.timezoneCustomErrorDuplicate': 'Вже додано', - 'dashboard.emptyTitle': 'Немає поїздок', - 'dashboard.emptyText': 'Створіть свою першу поїздку і почніть планувати!', - 'dashboard.emptyButton': 'Створити першу поїздку', - 'dashboard.nextTrip': 'Наступна поїздка', - 'dashboard.shared': 'Спільна', - 'dashboard.sharedBy': 'Поділився {name}', - 'dashboard.days': 'Дні', - 'dashboard.places': 'Місця', - 'dashboard.members': 'Попутники', - 'dashboard.archive': 'Архівувати', - 'dashboard.copyTrip': 'Копіювати', - 'dashboard.copySuffix': 'копія', - 'dashboard.restore': 'Відновити', - 'dashboard.archived': 'В архіві', - 'dashboard.status.ongoing': 'Триває', - 'dashboard.status.today': 'Сьогодні', - 'dashboard.status.tomorrow': 'Завтра', - 'dashboard.status.past': 'Минуло', - 'dashboard.status.daysLeft': 'залишилось {count} дн.', - 'dashboard.toast.loadError': 'Не вдалося завантажити поїздки', - 'dashboard.toast.created': 'Поїздка створена!', - 'dashboard.toast.createError': 'Не вдалося створити поїздку', - 'dashboard.toast.updated': 'Поїздка оновлена!', - 'dashboard.toast.updateError': 'Не вдалося оновити поїздку', - 'dashboard.toast.deleted': 'Поїздка видалена', - 'dashboard.toast.deleteError': 'Не вдалося видалити поїздку', - 'dashboard.toast.archived': 'Поїздка архівована', - 'dashboard.toast.archiveError': 'Не вдалося архівувати поїздку', - 'dashboard.toast.restored': 'Поїздка відновлена', - 'dashboard.toast.restoreError': 'Не вдалося відновити поїздку', - 'dashboard.toast.copied': 'Поїздка скопійована!', - 'dashboard.toast.copyError': 'Не вдалося скопіювати поїздку', - 'dashboard.confirm.copy.confirm': 'Скопіювати поїздку', - 'dashboard.confirm.copy.title': 'Скопіювати цю поїздку?', - 'dashboard.confirm.copy.will1': 'Дні, місця та призначення за днями', - 'dashboard.confirm.copy.will2': 'Проживання та бронювання', - 'dashboard.confirm.copy.will3': 'Статті бюджету та порядок категорій', - 'dashboard.confirm.copy.will4': 'Списки речей (непозначені)', - 'dashboard.confirm.copy.will5': 'Задачі (непризначені та непозначені)', - 'dashboard.confirm.copy.will6': 'Нотатки дня', - 'dashboard.confirm.copy.willCopy': 'Буде скопійовано', - 'dashboard.confirm.copy.wont1': 'Учасники та їх призначення', - 'dashboard.confirm.copy.wont2': 'Спільні нотатки, опитування та повідомлення', - 'dashboard.confirm.copy.wont3': 'Файли та фото', - 'dashboard.confirm.copy.wont4': 'Токени доступу', - 'dashboard.confirm.copy.wontCopy': 'Не буде скопійовано', - 'dashboard.confirm.delete': 'Видалити поїздку «{title}»? Всі місця та плани будуть безповоротно видалені.', - 'dashboard.editTrip': 'Редагувати поїздку', - 'dashboard.createTrip': 'Створити нову поїздку', - 'dashboard.tripTitle': 'Назва', - 'dashboard.tripTitlePlaceholder': 'напр. Літо в Японії', - 'dashboard.tripDescription': 'Опис', - 'dashboard.tripDescriptionPlaceholder': 'Про що ця поїздка?', - 'dashboard.startDate': 'Дата початку', - 'dashboard.endDate': 'Дата закінчення', - 'dashboard.dayCount': 'Кількість днів', - 'dashboard.dayCountHint': 'Скільки днів планувати, якщо дати поїздки не вказані.', - 'dashboard.noDateHint': 'Дата не вказана — буде створено 7 днів за замовчуванням. Ви можете змінити це в будь-який час.', - 'dashboard.coverImage': 'Обкладинка', - 'dashboard.addCoverImage': 'Додати обкладинку', - 'dashboard.addMembers': 'Учасники', - 'dashboard.addMember': 'Додати учасника', - 'dashboard.coverSaved': 'Обкладинка збережена', - 'dashboard.coverUploadError': 'Помилка завантаження', - 'dashboard.coverRemoveError': 'Помилка видалення', - 'dashboard.titleRequired': 'Назва обов\'язкова', - 'dashboard.endDateError': 'Дата закінчення повинна бути пізніше дати початку', - - // Settings - 'settings.title': 'Налаштування', - 'settings.subtitle': 'Налаштуйте свої персональні параметри', - 'settings.tabs.display': 'Відображення', - 'settings.tabs.map': 'Карта', - 'settings.tabs.notifications': 'Сповіщення', - 'settings.tabs.integrations': 'Інтеграції', - 'settings.tabs.account': 'Обліковий запис', - 'settings.tabs.offline': 'Офлайн', - 'settings.tabs.about': 'Про застосунок', - 'settings.map': 'Карта', - 'settings.mapTemplate': 'Шаблон карти', - 'settings.mapTemplatePlaceholder.select': 'Виберіть шаблон...', - 'settings.mapDefaultHint': 'Залиште порожнім для OpenStreetMap (за замовчуванням)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': 'URL-шаблон для тайлів карти', - 'settings.mapProvider': 'Провайдер карти', - 'settings.mapProviderHint': 'Застосовується до Trip Planner та Journey. Atlas завжди використовує Leaflet.', - 'settings.mapLeafletSubtitle': 'Класичні 2D, будь-які растрові тайли', - 'settings.mapMapboxSubtitle': 'Векторні тайли, 3D-будинки та рельєф', - 'settings.mapExperimental': 'Експериментально', - 'settings.mapMapboxToken': 'Токен доступу Mapbox', - 'settings.mapMapboxTokenHint': 'Публічний токен (pk.*) з', - 'settings.mapMapboxTokenLink': 'mapbox.com → Токени доступу', - 'settings.mapStyle': 'Стиль карти', - 'settings.mapStylePlaceholder': 'Виберіть стиль Mapbox', - 'settings.mapStyleHint': 'Preset або власний URL mapbox://styles/USER/ID', - 'settings.map3dBuildings': '3D-будинки та рельєф', - 'settings.map3dHint': 'Нахил + справжні 3D-будинки — працює з усіма стилями, включаючи супутник.', - 'settings.mapHighQuality': 'Режим високої якості', - 'settings.mapHighQualityHint': 'Згладжування + проекція глобуса для більш чітких країв та реалістичного вигляду світу.', - 'settings.mapHighQualityWarning': 'Може вплинути на продуктивність на слабких пристроях.', - 'settings.mapTipLabel': 'Порада:', - 'settings.mapTip': 'Затисніть праву кнопку миші та перетягніть, щоб повернути/нахилити карту. Клік середньою кнопкою — додати місце (права кнопка зарезервована для обертання).', - 'settings.latitude': 'Широта', - 'settings.longitude': 'Довгота', - 'settings.saveMap': 'Зберегти карту', - 'settings.apiKeys': 'API-ключі', - 'settings.mapsKey': 'API-ключ Google Maps', - 'settings.mapsKeyHint': 'Для пошуку місць. Потрібен Places API (New). Отримайте на console.cloud.google.com', - 'settings.weatherKey': 'API-ключ OpenWeatherMap', - 'settings.weatherKeyHint': 'Для даних про погоду. Безкоштовно на openweathermap.org/api', - 'settings.keyPlaceholder': 'Введіть ключ...', - 'settings.configured': 'Налаштовано', - 'settings.saveKeys': 'Зберегти ключі', - 'settings.display': 'Відображення', - 'settings.colorMode': 'Кольорова схема', - 'settings.light': 'Світла', - 'settings.dark': 'Темна', - 'settings.auto': 'Авто', - 'settings.language': 'Мова', - 'settings.temperature': 'Одиниця температури', - 'settings.timeFormat': 'Формат часу', - 'settings.blurBookingCodes': 'Приховати коди бронювання', - 'settings.notifications': 'Сповіщення', - 'settings.notifyTripInvite': 'Запрошення до поїздки', - 'settings.notifyBookingChange': 'Зміни бронювань', - 'settings.notifyTripReminder': 'Нагадування про поїздку', - 'settings.notifyTodoDue': 'Завдання до терміну', - 'settings.notifyVacayInvite': 'Запрошення об\'єднання Vacay', - 'settings.notifyPhotosShared': 'Спільні фото (Immich)', - 'settings.notifyCollabMessage': 'Повідомлення чату (Collab)', - 'settings.notifyPackingTagged': 'Список речей: призначення', - 'settings.notifyWebhook': 'Webhook-сповіщення', - 'settings.notificationsDisabled': 'Сповіщення не налаштовані. Попросіть адміністратора ввімкнути сповіщення електронною поштою або webhook.', - 'settings.notificationsActive': 'Активний канал', - 'settings.notificationsManagedByAdmin': 'Події сповіщень налаштовуються адміністратором.', - 'admin.notifications.title': 'n', - 'admin.notifications.hint': 'Виберіть канал сповіщень. Одночасно може бути активним лише один.', - 'admin.notifications.none': 'Вимкнено', - 'admin.notifications.email': 'Ел. пошта (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': 'Зберегти налаштування сповіщень', - 'admin.notifications.saved': 'Налаштування сповіщень збережені', - 'admin.notifications.testWebhook': 'Відправити тестовий webhook', - 'admin.notifications.testWebhookSuccess': 'Тестовий webhook успішно відправлено', - 'admin.notifications.testWebhookFailed': 'Помилка відправки тестового webhook\'а', - 'admin.smtp.title': 'Пошта та сповіщення', - 'admin.smtp.hint': 'Налаштування SMTP для відправки сповіщень електронною поштою.', - 'admin.smtp.testButton': 'Відправити тестовий лист', - 'admin.webhook.hint': 'Відправляти сповіщення через зовнішній webhook (Discord, Slack тощо).', - 'admin.smtp.testSuccess': 'Тестовий лист успішно відправлено', - 'admin.smtp.testFailed': 'Помилка відправки тестового листа', - 'dayplan.icsTooltip': 'Експорт календаря (ICS)', - 'share.linkTitle': 'Публічне посилання', - 'share.linkHint': 'Створіть посилання, за яким будь-хто зможе переглянути цю поїздку без входу. Тільки читання — редагувати не можна.', - 'share.createLink': 'Створити посилання', - 'share.deleteLink': 'Видалити посилання', - 'share.createError': 'Не вдалося створити посилання', - 'common.copy': 'Копіювати', - 'common.copied': 'Скопійовано', - 'share.permMap': 'Карта і план', - 'share.permBookings': 'Бронювання', - 'share.permPacking': 'Речі', - 'shared.expired': 'Посилання прострочено або недійсне', - 'shared.expiredHint': 'Це посилання на поїздку більше не активне.', - 'shared.readOnly': 'Тільки для читання', - 'shared.tabPlan': 'План', - 'shared.tabBookings': 'Бронювання', - 'shared.tabPacking': 'Багаж', - 'shared.tabBudget': 'Бюджет', - 'shared.tabChat': 'Чат', - 'shared.days': 'днів', - 'shared.places': 'місць', - 'shared.other': 'Інше', - 'shared.totalBudget': 'Загальний бюджет', - 'shared.messages': 'повідомлень', - 'shared.sharedVia': 'Поділено через', - 'shared.confirmed': 'Підтверджено', - 'shared.pending': 'Очікує', - 'share.permBudget': 'Бюджет', - 'share.permCollab': 'Чат', - 'settings.on': 'Увімк.', - 'settings.off': 'Вимк.', - 'settings.mcp.title': 'Налаштування MCP', - 'settings.mcp.endpoint': 'MCP-ендпоінт', - 'settings.mcp.clientConfig': 'Конфігурація клієнта', - 'settings.mcp.clientConfigHint': 'Замініть на API-токен зі списку нижче. Шлях до npx може потребувати налаштування для вашої системи (наприклад, C:\\PROGRA~1\\nodejs\\npx.cmd у Windows).', - 'settings.mcp.clientConfigHintOAuth': 'Замініть і на облікові дані з вищезазначеного клієнта OAuth 2.1. При першому підключенні mcp-remote відкриє браузер для завершення авторизації. Шлях до npx може потребувати налаштування для вашої системи (наприклад, C:\\PROGRA~1\\nodejs\\npx.cmd у Windows).', - 'settings.mcp.copy': 'Копіювати', - 'settings.mcp.copied': 'Скопійовано!', - 'settings.mcp.apiTokens': 'API-токени', - 'settings.mcp.createToken': 'Створити токен', - 'settings.mcp.noTokens': 'Токенів поки немає. Створіть один для підключення MCP-клієнтів.', - 'settings.mcp.tokenCreatedAt': 'Створено', - 'settings.mcp.tokenUsedAt': 'Використано', - 'settings.mcp.deleteTokenTitle': 'Видалити токен', - 'settings.mcp.deleteTokenMessage': 'Цей токен перестане працювати одразу. Будь-який MCP-клієнт, що його використовує, втратить доступ.', - 'settings.mcp.modal.createTitle': 'Створити API-токен', - 'settings.mcp.modal.tokenName': 'Назва токена', - 'settings.mcp.modal.tokenNamePlaceholder': 'наприклад Claude Desktop, Робочий ноутбук', - 'settings.mcp.modal.creating': 'Створення…', - 'settings.mcp.modal.create': 'Створити токен', - 'settings.mcp.modal.createdTitle': 'Токен створено', - 'settings.mcp.modal.createdWarning': 'Цей токен буде показано лише один раз. Скопіюйте і збережіть його зараз — відновити його не можна.', - 'settings.mcp.modal.done': 'Готово', - 'settings.mcp.toast.created': 'Токен створено', - 'settings.mcp.toast.createError': 'Не вдалося створити токен', - 'settings.mcp.toast.deleted': 'Токен видалено', - 'settings.mcp.toast.deleteError': 'Не вдалося видалити токен', - 'settings.mcp.apiTokensDeprecated': 'API-токени застаріли і будуть видалені в наступній версії. Будь ласка, використовуйте клієнти OAuth 2.1.', - 'settings.oauth.clients': 'Клієнти OAuth 2.1', - 'settings.oauth.clientsHint': 'Зареєструйте клієнтів OAuth 2.1, щоб сторонні MCP-застосунки (Claude Web, Cursor тощо) могли підключатися без статичних токенів.', - 'settings.oauth.createClient': 'Новий клієнт', - 'settings.oauth.noClients': 'Немає зареєстрованих клієнтів OAuth.', - 'settings.oauth.clientId': 'ID клієнта', - 'settings.oauth.clientSecret': 'Секрет клієнта', - 'settings.oauth.deleteClient': 'Видалити клієнта', - 'settings.oauth.deleteClientMessage': 'Цей клієнт і всі активні сесії будуть видалені назавжди. Будь-який застосунок, що його використовує, одразу втратить доступ.', - 'settings.oauth.rotateSecret': 'Оновити секрет', - 'settings.oauth.rotateSecretMessage': 'Буде згенеровано новий секрет клієнта, а всі існуючі сесії будуть негайно анульовані. Оновіть застосунок перед закриттям цього діалогу.', - 'settings.oauth.rotateSecretConfirm': 'Оновити', - 'settings.oauth.rotateSecretConfirming': 'Оновлення…', - 'settings.oauth.rotateSecretDoneTitle': 'Новий секрет згенеровано', - 'settings.oauth.rotateSecretDoneWarning': 'Цей секрет відображається лише один раз. Скопіюйте його зараз і оновіть застосунок — всі попередні сесії були анульовані.', - 'settings.oauth.activeSessions': 'Активні сесії OAuth', - 'settings.oauth.sessionScopes': 'Області доступу', - 'settings.oauth.sessionExpires': 'Закінчується', - 'settings.oauth.revoke': 'Відкликати', - 'settings.oauth.revokeSession': 'Відкликати сесію', - 'settings.oauth.revokeSessionMessage': 'Це негайно відкличе доступ для даної сесії OAuth.', - 'settings.oauth.modal.createTitle': 'Зареєструвати клієнта OAuth', - 'settings.oauth.modal.presets': 'Швидкі налаштування', - 'settings.oauth.modal.clientName': 'Назва застосунку', - 'settings.oauth.modal.clientNamePlaceholder': 'напр. Claude Web, Мій MCP-застосунок', - 'settings.oauth.modal.redirectUris': 'URI перенаправлення', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': 'Один URI на рядок. Потрібен HTTPS (localhost виключено). Потрібне точне співпадіння.', - 'settings.oauth.modal.scopes': 'Дозволені області доступу', - 'settings.oauth.modal.scopesHint': 'list_trips і get_trip_summary завжди доступні — область не потрібна. Вони допомагають ШІ знаходити потрібні ID поїздок.', - 'settings.oauth.modal.selectAll': 'Вибрати все', - 'settings.oauth.modal.deselectAll': 'Зняти вибір', - 'settings.oauth.modal.creating': 'Реєстрація…', - 'settings.oauth.modal.create': 'Зареєструвати клієнта', - 'settings.oauth.modal.createdTitle': 'Клієнт зареєстровано', - 'settings.oauth.modal.createdWarning': 'Секрет клієнта показується лише один раз. Скопіюйте його зараз — його не можна буде відновити.', - 'settings.oauth.toast.createError': 'Не вдалося зареєструвати клієнта OAuth', - 'settings.oauth.toast.deleted': 'Клієнт OAuth видалено', - 'settings.oauth.toast.deleteError': 'Не вдалося видалити клієнта OAuth', - 'settings.oauth.toast.revoked': 'Сесію відкликано', - 'settings.oauth.toast.revokeError': 'Не вдалося відкликати сесію', - 'settings.oauth.toast.rotateError': 'Не вдалося оновити секрет клієнта', - 'settings.account': 'Обліковий запис', - 'settings.about': 'Про застосунок', - 'settings.about.reportBug': 'Повідомити про помилку', - 'settings.about.reportBugHint': 'Знайшли проблему? Повідомте нам', - 'settings.about.featureRequest': 'Запропонувати функцію', - 'settings.about.featureRequestHint': 'Запропонуйте нову функцію', - 'settings.about.wikiHint': 'Документація та керівництва', - 'settings.about.supporters.badge': 'Щомісячні спонсори', - 'settings.about.supporters.title': 'Спутники TREK', - 'settings.about.supporters.subtitle': 'Поки ти плануєш наступний маршрут, ці люди разом зі мною планують майбутнє TREK. Їхній щомісячний внесок йде безпосередньо в розробку та реальні витрачені години — щоб TREK залишався Open Source.', - 'settings.about.supporters.since': 'спонсор з {date}', - 'settings.about.supporters.tierEmpty': 'Стань першим', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK — це self-hosted планувальник подорожей, який допомагає організувати поїздки від першої ідеї до останнього спогаду. Планування по днях, бюджет, списки речей, фото та багато іншого — все в одному місці, на вашому власному сервері.', - 'settings.about.madeWith': 'Зроблено з', - 'settings.about.madeBy': 'Морісом і зростаючою open-source спільнотою.', - 'settings.username': 'Ім\'я користувача', - 'settings.email': 'Ел. пошта', - 'settings.role': 'Роль', - 'settings.roleAdmin': 'Адміністратор', - 'settings.oidcLinked': 'Пов’язаний з', - 'settings.changePassword': 'Змінити пароль', - 'settings.mustChangePassword': 'Ви повинні змінити пароль перед продовженням. Будь ласка, встановіть новий пароль нижче.', - 'settings.currentPassword': 'Поточний пароль', - 'settings.currentPasswordRequired': 'Поточний пароль обов’язковий', - 'settings.newPassword': 'Новий пароль', - 'settings.confirmPassword': 'Підтвердіть новий пароль', - 'settings.updatePassword': 'Оновити пароль', - 'settings.passwordRequired': 'Введіть поточний та новий паролі', - 'settings.passwordTooShort': 'Пароль повинен містити не менше 8 символів', - 'settings.passwordMismatch': 'Паролі не збігаються', - 'settings.passwordWeak': 'Пароль повинен містити великі й малі літери, цифру та спеціальний символ', - 'settings.passwordChanged': 'Пароль успішно змінено', - 'settings.deleteAccount': 'Видалити акаунт', - 'settings.deleteAccountTitle': 'Видалити ваш акаунт?', - 'settings.deleteAccountWarning': 'Ваш акаунт і всі поїздки, місця та файли будуть безповоротно видалені. Цю дію неможливо скасувати.', - 'settings.deleteAccountConfirm': 'Видалити безповоротно', - 'settings.deleteBlockedTitle': 'Видалення неможливе', - 'settings.deleteBlockedMessage': 'Ви єдиний адміністратор. Призначте іншого користувача адміністратором перед видаленням свого акаунта.', - 'settings.roleUser': 'Користувач', - 'settings.saveProfile': 'Зберегти профіль', - 'settings.mfa.title': 'Двофакторна аутентифікація (2FA)', - 'settings.mfa.description': 'Додає другий крок при вході. Використовуйте додаток-аутентифікатор (Google Authenticator, Authy та ін.).', - 'settings.mfa.requiredByPolicy': 'Адміністратор вимагає двофакторної аутентифікації. Налаштуйте додаток-аутентифікатор нижче, перш ніж продовжити.', - 'settings.mfa.backupTitle': 'Резервні коди', - 'settings.mfa.backupDescription': 'Використовуйте ці одноразові коди, якщо втратите доступ до додатку-аутентифікатора.', - 'settings.mfa.backupWarning': 'Збережіть їх зараз. Кожен код можна використати лише один раз.', - 'settings.mfa.backupCopy': 'Скопіювати коди', - 'settings.mfa.backupDownload': 'Завантажити TXT', - 'settings.mfa.backupPrint': 'Друк / PDF', - 'settings.mfa.backupCopied': 'Резервні коди скопійовано', - 'settings.mfa.enabled': '2FA увімкнено для вашого акаунта.', - 'settings.mfa.disabled': '2FA не увімкнено.', - 'settings.mfa.setup': 'Налаштувати автентифікатор', - 'settings.mfa.scanQr': 'Відскануйте QR-код додатком або введіть ключ вручну.', - 'settings.mfa.secretLabel': 'Секретний ключ (ручний ввід)', - 'settings.mfa.codePlaceholder': '6-значний код', - 'settings.mfa.enable': 'Увімкнути 2FA', - 'settings.mfa.cancelSetup': 'Скасувати', - 'settings.mfa.disableTitle': 'Вимкнути 2FA', - 'settings.mfa.disableHint': 'Введіть пароль акаунта та поточний код з автентифікатора.', - 'settings.mfa.disable': 'Вимкнути 2FA', - 'settings.mfa.toastEnabled': 'Двофакторна автентифікація увімкнена', - 'settings.mfa.toastDisabled': 'Двофакторна автентифікація вимкнена', - 'settings.mfa.demoBlocked': 'Недоступно в демо-режимі', - 'settings.toast.mapSaved': 'Налаштування карти збережено', - 'settings.toast.keysSaved': 'API-ключі збережено', - 'settings.toast.displaySaved': 'Налаштування відображення збережено', - 'settings.toast.profileSaved': 'Профіль збережено', - 'settings.uploadAvatar': 'Завантажити фото профілю', - 'settings.removeAvatar': 'Видалити фото профілю', - 'settings.avatarUploaded': 'Фото профілю оновлено', - 'settings.avatarRemoved': 'Фото профілю видалено', - 'settings.avatarError': 'Помилка завантаження', - - // Login - 'login.error': 'Помилка входу. Перевірте свої облікові дані.', - 'login.tagline': 'Ваші поїздки.\nВаш план.', - 'login.description': 'Плануйте поїздки разом з інтерактивними картами, бюджетами та синхронізацією в реальному часі.', - 'login.features.maps': 'Інтерактивні карти', - 'login.features.mapsDesc': 'Google Places, маршрути та кластеризація', - 'login.features.realtime': 'Синхронізація в реальному часі', - 'login.features.realtimeDesc': 'Плануйте разом через WebSocket', - 'login.features.budget': 'Контроль бюджету', - 'login.features.budgetDesc': 'Категорії, графіки та витрати на особу', - 'login.features.collab': 'Спільна робота', - 'login.features.collabDesc': 'Багатокористувацький режим зі спільними поїздками', - 'login.features.packing': 'Списки речей', - 'login.features.packingDesc': 'Категорії, прогрес та підказки', - 'login.features.bookings': 'Бронювання', - 'login.features.bookingsDesc': 'Авіаквитки, готелі, ресторани та багато іншого', - 'login.features.files': 'Документи', - 'login.features.filesDesc': 'Завантажуйте та керуйте документами', - 'login.features.routes': 'Розумні маршрути', - 'login.features.routesDesc': 'Автооптимізація та експорт з Google Maps', - 'login.selfHosted': 'Self-hosted · Відкритий код · Ваші дані залишаються у вас', - 'login.title': 'Вхід', - 'login.subtitle': 'З поверненням', - 'login.signingIn': 'Вхід…', - 'login.signIn': 'Увійти', - 'login.createAdmin': 'Створити акаунт адміністратора', - 'login.createAdminHint': 'Налаштуйте перший акаунт адміністратора для TREK.', - 'login.setNewPassword': 'Встановити новий пароль', - 'login.setNewPasswordHint': 'Ви повинні змінити пароль, перш ніж продовжити.', - 'login.createAccount': 'Створити акаунт', - 'login.createAccountHint': 'Зареєструйте новий акаунт.', - 'login.creating': 'Створення…', - 'login.noAccount': 'Немає акаунту?', - 'login.hasAccount': 'Вже є акаунт?', - 'login.register': 'Реєстрація', - 'login.emailPlaceholder': 'ваш@email.com', - 'login.username': 'Ім’я користувача', - 'login.oidc.registrationDisabled': 'Реєстрацію вимкнено. Зверніться до адміністратора.', - 'login.oidc.noEmail': 'Провайдер не надав адресу електронної пошти.', - 'login.mfaTitle': 'Двофакторна автентифікація', - 'login.mfaSubtitle': 'Введіть 6-значний код з додатка-автентифікатора.', - 'login.mfaCodeLabel': 'Код підтвердження', - 'login.mfaCodeRequired': 'Введіть код із додатка-автентифікатора.', - 'login.mfaHint': 'Відкрийте Google Authenticator, Authy або інший TOTP-додаток.', - 'login.mfaBack': '← Назад до входу', - 'login.mfaVerify': 'Підтвердити', - 'login.invalidInviteLink': 'Недійсне або прострочене посилання-запрошення', - 'login.oidcFailed': 'Помилка входу через OIDC', - 'login.usernameRequired': 'Ім’я користувача обов’язкове', - 'login.passwordMinLength': 'Пароль має містити щонайменше 8 символів', - 'login.forgotPassword': 'Забули пароль?', - 'login.forgotPasswordTitle': 'Скидання пароля', - 'login.forgotPasswordBody': 'Введіть електронну пошту, з якою ви реєструвалися. Якщо акаунт існує — буде надіслано посилання для скидання.', - 'login.forgotPasswordSubmit': 'Надіслати посилання', - 'login.forgotPasswordSentTitle': 'Перевірте пошту', - 'login.forgotPasswordSentBody': 'Якщо акаунт існує, посилання для скидання має бути вже відправлено. Воно дійсне 60 хвилин.', - 'login.forgotPasswordSmtpHintOff': 'Зверніть увагу: адміністратор не налаштував SMTP, тому посилання для скидання буде записано в консоль сервера замість відправки електронною поштою.', - 'login.backToLogin': 'Повернутись до входу', - 'login.newPassword': 'Новий пароль', - 'login.confirmPassword': 'Підтвердіть новий пароль', - 'login.passwordsDontMatch': 'Паролі не співпадають', - 'login.mfaCode': 'Код 2FA', - 'login.resetPasswordTitle': 'Встановіть новий пароль', - 'login.resetPasswordBody': 'Виберіть надійний пароль, який ви ще не використовували. Мінімум 8 символів.', - 'login.resetPasswordMfaBody': 'Введіть код 2FA або резервний код, щоб завершити скидання.', - 'login.resetPasswordSubmit': 'Скинути пароль', - 'login.resetPasswordVerify': 'Перевірити та скинути', - 'login.resetPasswordSuccessTitle': 'Пароль оновлено', - 'login.resetPasswordSuccessBody': 'Тепер ви можете увійти з новим паролем.', - 'login.resetPasswordInvalidLink': 'Неправильне посилання для скидання', - 'login.resetPasswordInvalidLinkBody': 'Посилання відсутнє або пошкоджене. Запитайте нове, щоб продовжити.', - 'login.resetPasswordFailed': 'Скидання не вдалося. Можливо, термін дії посилання минув.', - 'login.oidc.tokenFailed': 'Автентифікація не вдалася.', - 'login.oidc.invalidState': 'Недійсна сесія. Спробуйте знову.', - 'login.demoFailed': 'Помилка демо-входу', - 'login.oidcSignIn': 'Увійти через {name}', - 'login.oidcOnly': 'Вхід за паролем вимкнено. Використайте вашого SSO-провайдера для входу.', - 'login.oidcLoggedOut': 'Ви вийшли з системи. Увійдіть знову через вашого SSO-провайдера.', - 'login.demoHint': 'Спробуйте демо — реєстрація не потрібна', - - // Register - 'register.passwordMismatch': 'Паролі не співпадають', - 'register.passwordTooShort': 'Пароль має містити щонайменше 8 символів', - 'register.failed': 'Помилка реєстрації', - 'register.getStarted': 'Почати', - 'register.subtitle': 'Створіть акаунт та почніть планувати подорожі мрії.', - 'register.feature1': 'Необмежена кількість планів подорожей', - 'register.feature2': 'Інтерактивна карта', - 'register.feature3': 'Керування місцями та категоріями', - 'register.feature4': 'Відстеження бронювань', - 'register.feature5': 'Створення списків речей', - 'register.feature6': 'Зберігання фото та файлів', - 'register.createAccount': 'Створити акаунт', - 'register.startPlanning': 'Почніть планувати свої поїздки', - 'register.minChars': 'Мін. 6 символів', - 'register.confirmPassword': 'Підтвердіть пароль', - 'register.repeatPassword': 'Повторіть пароль', - 'register.registering': 'Реєстрація...', - 'register.register': 'Зареєструватися', - 'register.hasAccount': 'Вже є акаунт?', - 'register.signIn': 'Увійти', - - // Admin - 'admin.title': 'Адміністрування', - 'admin.subtitle': 'Управління користувачами та системні налаштування', - 'admin.tabs.users': 'Користувачі', - 'admin.tabs.categories': 'Категорії', - 'admin.tabs.backup': 'Резервна копія', - 'admin.tabs.audit': 'Аудит', - 'admin.stats.users': 'Користувачі', - 'admin.stats.trips': 'Подорожі', - 'admin.stats.places': 'Місця', - 'admin.stats.photos': 'Фото', - 'admin.stats.files': 'Файли', - 'admin.table.user': 'Користувач', - 'admin.table.email': 'Ел. пошта', - 'admin.table.role': 'Роль', - 'admin.table.created': 'Створено', - 'admin.table.lastLogin': 'Останній вхід', - 'admin.table.actions': 'Дії', - 'admin.you': '(Ви)', - 'admin.editUser': 'Редагувати користувача', - 'admin.newPassword': 'Новий пароль', - 'admin.newPasswordHint': 'Залиште порожнім, щоб зберегти поточний пароль', - 'admin.deleteUser': 'Видалити користувача «{name}»? Усі подорожі будуть безповоротно видалені.', - 'admin.deleteUserTitle': 'Видалити користувача', - 'admin.newPasswordPlaceholder': 'Введіть новий пароль…', - 'admin.toast.loadError': 'Не вдалося завантажити дані адміністрування', - 'admin.toast.userUpdated': 'Користувача оновлено', - 'admin.toast.updateError': 'Помилка оновлення', - 'admin.toast.userDeleted': 'Користувача видалено', - 'admin.toast.deleteError': 'Помилка видалення', - 'admin.toast.cannotDeleteSelf': 'Неможливо видалити власний акаунт', - 'admin.toast.userCreated': 'Користувача створено', - 'admin.toast.createError': 'Помилка створення користувача', - 'admin.toast.fieldsRequired': 'Ім’я користувача, ел. пошта та пароль обов’язкові', - 'admin.createUser': 'Створити користувача', - 'admin.invite.title': 'Запрошення за посиланням', - 'admin.invite.subtitle': 'Створення одноразових посилань для реєстрації', - 'admin.invite.create': 'Створити посилання', - 'admin.invite.createAndCopy': 'Створити та скопіювати', - 'admin.invite.empty': 'Посилання-запрошення ще не створені', - 'admin.invite.maxUses': 'Макс. використань', - 'admin.invite.expiry': 'Дійсне', - 'admin.invite.uses': 'використано', - 'admin.invite.expiresAt': 'закінчується', - 'admin.invite.createdBy': 'від', - 'admin.invite.active': 'Активне', - 'admin.invite.expired': 'Закінчилося', - 'admin.invite.usedUp': 'Вичерпано', - 'admin.invite.copied': 'Посилання-запрошення скопійовано', - 'admin.invite.copyLink': 'Скопіювати посилання', - 'admin.invite.deleted': 'Посилання-запрошення видалено', - 'admin.invite.createError': 'Помилка створення посилання', - 'admin.invite.deleteError': 'Помилка видалення посилання', - 'admin.tabs.settings': 'Налаштування', - 'admin.allowRegistration': 'Дозволити реєстрацію', - 'admin.allowRegistrationHint': 'Нові користувачі можуть реєструватися самостійно', - 'admin.authMethods': 'Методи автентифікації', - 'admin.passwordLogin': 'Вхід за паролем', - 'admin.passwordLoginHint': 'Дозволити користувачам входити за ел. поштою та паролем', - 'admin.passwordRegistration': 'Реєстрація за паролем', - 'admin.passwordRegistrationHint': 'Дозволити новим користувачам реєструватися за ел. поштою та паролем', - 'admin.oidcLogin': 'Вхід через SSO', - 'admin.oidcLoginHint': 'Дозволити користувачам входити через SSO', - 'admin.oidcRegistration': 'Автоматичне створення SSO-акаунтів', - 'admin.oidcRegistrationHint': 'Автоматично створювати акаунти для нових SSO-користувачів', - 'admin.envOverrideHint': 'Налаштування входу за паролем контролюються змінною середовища OIDC_ONLY і не можуть бути змінені тут.', - 'admin.lockoutWarning': 'Потрібно залишити увімкненим принаймні один метод входу', - 'admin.requireMfa': 'Вимагати двофакторну автентифікацію (2FA)', - 'admin.requireMfaHint': 'Користувачі без 2FA повинні завершити налаштування в розділі «Налаштування» перед використанням програми.', - 'admin.apiKeys': 'API-ключі', - 'admin.apiKeysHint': 'Необов’язково. Включає розширені дані про місця, такі як фото та погода.', - 'admin.mapsKey': 'API-ключ Google Maps', - 'admin.mapsKeyHint': 'Необхідний для пошуку місць. Отримайте на console.cloud.google.com', - 'admin.mapsKeyHintLong': 'Без API-ключа для пошуку місць використовується OpenStreetMap. З ключем Google API можна завантажувати фото, рейтинги та години роботи. Отримайте ключ на console.cloud.google.com.', - 'admin.recommended': 'Рекомендується', - 'admin.weatherKey': 'API-ключ OpenWeatherMap', - 'admin.weatherKeyHint': 'Для даних про погоду. Безкоштовно на openweathermap.org', - 'admin.validateKey': 'Перевірити', - 'admin.keyValid': 'Підключено', - 'admin.keyInvalid': 'Недійсний', - 'admin.keySaved': 'API-ключі збережено', - 'admin.oidcTitle': 'Єдиний вхід (OIDC)', - 'admin.oidcSubtitle': 'Дозволити вхід через зовнішніх провайдерів, таких як Google, Apple, Authentik або Keycloak.', - 'admin.oidcDisplayName': 'Відображуване ім’я', - 'admin.oidcIssuer': 'URL видавця', - 'admin.oidcIssuerHint': 'URL видавця OpenID Connect провайдера. Напр. https://accounts.google.com', - 'admin.oidcSaved': 'Конфігурацію OIDC збережено', - 'admin.oidcOnlyMode': 'Вимкнути вхід за паролем', - 'admin.oidcOnlyModeHint': 'При ввімкненні дозволено тільки вхід через SSO. Вхід та реєстрація за паролем будуть заблоковані.', - - // File Types - 'admin.fileTypes': 'Дозволені типи файлів', - 'admin.fileTypesHint': 'Налаштуйте, які типи файлів можуть завантажувати користувачі.', - 'admin.fileTypesFormat': 'Розширення через кому (напр. jpg,png,pdf,doc). Використовуйте * для дозволу всіх типів.', - 'admin.fileTypesSaved': 'Налаштування типів файлів збережено', - - 'admin.placesPhotos.title': 'Фотографії місць', - 'admin.placesPhotos.subtitle': 'Завантаження фотографій з Google Places API. Вимкніть для економії квоти API. Фотографії Wikimedia не зачіпаються.', - 'admin.placesAutocomplete.title': 'Автодоповнення місць', - 'admin.placesAutocomplete.subtitle': 'Використання Google Places API для підказок пошуку. Вимкніть для економії квоти API.', - 'admin.placesDetails.title': 'Відомості про місце', - 'admin.placesDetails.subtitle': 'Завантаження детальної інформації про місце (години роботи, рейтинг, веб-сайт) з Google Places API. Вимкніть для економії квоти API.', - 'admin.bagTracking.title': 'Відстеження багажу', - 'admin.bagTracking.subtitle': 'Увімкнути вагу та прив’язку до багажу для речей', - 'admin.collab.chat.title': 'Чат', - 'admin.collab.chat.subtitle': 'Обмін повідомленнями для спільної роботи', - 'admin.collab.notes.title': 'Нотатки', - 'admin.collab.notes.subtitle': 'Спільні нотатки та документи', - 'admin.collab.polls.title': 'Опитування', - 'admin.collab.polls.subtitle': 'Групові опитування та голосування', - 'admin.collab.whatsnext.title': 'Що далі', - 'admin.collab.whatsnext.subtitle': 'Пропозиції активностей та наступні кроки', - 'admin.tabs.config': 'Персоналізація', - 'admin.tabs.defaults': 'Налаштування за замовчуванням', - 'admin.defaultSettings.title': 'Налаштування користувачів за замовчуванням', - 'admin.defaultSettings.description': 'Визначте значення за замовчуванням для всієї інстанції. Користувачі, які не змінювали параметр, побачать ці значення. Їхні власні зміни завжди мають пріоритет.', - 'admin.defaultSettings.saved': 'Значення за замовчуванням збережено', - 'admin.defaultSettings.reset': 'Скинути до вбудованого значення', - 'admin.defaultSettings.resetToBuiltIn': 'скинути', - 'admin.tabs.templates': 'Шаблони пакування', - 'admin.packingTemplates.title': 'Шаблони пакування', - 'admin.packingTemplates.subtitle': 'Створюйте багаторазові списки речей для подорожей', - 'admin.packingTemplates.create': 'Новий шаблон', - 'admin.packingTemplates.namePlaceholder': 'Назва шаблону (наприклад, Відпочинок на пляжі)', - 'admin.packingTemplates.empty': 'Шаблони ще не створені', - 'admin.packingTemplates.items': 'речей', - 'admin.packingTemplates.categories': 'категорії(й)', - 'admin.packingTemplates.itemName': 'Назва речі', - 'admin.packingTemplates.itemCategory': 'Категорія', - 'admin.packingTemplates.categoryName': 'Назва категорії (наприклад, Одяг)', - 'admin.packingTemplates.addCategory': 'Додати категорію', - 'admin.packingTemplates.created': 'Шаблон створено', - 'admin.packingTemplates.deleted': 'Шаблон видалено', - 'admin.packingTemplates.loadError': 'Помилка завантаження шаблонів', - 'admin.packingTemplates.createError': 'Помилка створення шаблону', - 'admin.packingTemplates.deleteError': 'Помилка видалення шаблону', - 'admin.packingTemplates.saveError': 'Помилка збереження', - - // Addons - 'admin.tabs.addons': 'Дополнения', - 'admin.addons.title': 'Доповнення', - 'admin.addons.subtitle': 'Увімкніть або вимкніть функції, щоб налаштувати TREK під себе.', - 'admin.addons.catalog.memories.name': 'Фото (Immich)', - 'admin.addons.catalog.memories.description': 'Діліться фотографіями з подорожей через Immich', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': 'Протокол контексту моделі для інтеграції з ІІ-асистентами', - 'admin.addons.catalog.packing.name': 'Списки', - 'admin.addons.catalog.packing.description': 'Списки речей та завдання для ваших подорожей', - 'admin.addons.catalog.budget.name': 'Бюджет', - 'admin.addons.catalog.budget.description': 'Відстежуйте витрати та плануйте бюджет подорожі', - 'admin.addons.catalog.documents.name': 'Документи', - 'admin.addons.catalog.documents.description': 'Зберігайте та керуйте документами для подорожей', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': 'Особистий планувальник відпусток з календарем', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': 'Карта світу з відвіданими країнами та статистикою подорожей', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': 'Нотатки в реальному часі, опитування та чат для планування подорожей', - 'admin.addons.subtitleBefore': 'Увімкніть або вимкніть функції для налаштування ', - 'admin.addons.subtitleAfter': ' під себе.', - 'admin.addons.enabled': 'Увімкнено', - 'admin.addons.disabled': 'Вимкнено', - 'admin.addons.type.trip': 'Поїздка', - 'admin.addons.type.global': 'Глобально', - 'admin.addons.type.integration': 'Інтеграція', - 'admin.addons.tripHint': 'Доступно як вкладка всередині кожної подорожі', - 'admin.addons.globalHint': 'Доступно як окремий розділ у головній навігації', - 'admin.addons.integrationHint': 'Фонові сервіси та API-інтеграції без окремої сторінки', - 'admin.addons.toast.updated': 'Доповнення оновлено', - 'admin.addons.toast.error': 'Не вдалося оновити доповнення', - 'admin.addons.noAddons': 'Немає доступних доповнень', - // Weather info - 'admin.weather.title': 'Дані про погоду', - 'admin.weather.badge': 'З 24 березня 2026', - 'admin.weather.description': 'TREK використовує Open-Meteo як джерело даних про погоду. Open-Meteo — безкоштовний сервіс з відкритим кодом, API-ключ не потрібен.', - 'admin.weather.forecast': 'Прогноз на 16 днів', - 'admin.weather.forecastDesc': 'Раніше 5 днів (OpenWeatherMap)', - 'admin.weather.climate': 'Історичні кліматичні дані', - 'admin.weather.climateDesc': 'Середні значення за останні 85 років для днів поза межами 16-денного прогнозу', - 'admin.weather.requests': '10 000 запитів / день', - 'admin.weather.requestsDesc': 'Безкоштовно, API-ключ не потрібен', - 'admin.weather.locationHint': 'Погода базується на першому місці з координатами в кожному дні. Якщо жодне місце не призначено на день, як орієнтир використовується будь-яке місце зі списку.', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP-доступ', - 'admin.mcpTokens.title': 'MCP-доступ', - 'admin.mcpTokens.subtitle': 'Управління OAuth-сеансами та API-токенами всіх користувачів', - 'admin.mcpTokens.sectionTitle': 'API-токени', - 'admin.mcpTokens.owner': 'Власник', - 'admin.mcpTokens.tokenName': 'Назва токена', - 'admin.mcpTokens.created': 'Створено', - 'admin.mcpTokens.lastUsed': 'Останнє використання', - 'admin.mcpTokens.never': 'Ніколи', - 'admin.mcpTokens.empty': 'MCP-токени ще не створені', - 'admin.mcpTokens.deleteTitle': 'Видалити токен', - 'admin.mcpTokens.deleteMessage': 'Токен буде негайно відкликано. Користувач втратить доступ до MCP через цей токен.', - 'admin.mcpTokens.deleteSuccess': 'Токен видалено', - 'admin.mcpTokens.deleteError': 'Не вдалося видалити токен', - 'admin.mcpTokens.loadError': 'Не вдалося завантажити токени', - 'admin.oauthSessions.sectionTitle': 'OAuth-сеанси', - 'admin.oauthSessions.clientName': 'Клієнт', - 'admin.oauthSessions.owner': 'Власник', - 'admin.oauthSessions.scopes': 'Права доступу', - 'admin.oauthSessions.created': 'Створено', - 'admin.oauthSessions.empty': 'Немає активних OAuth-сеансів', - 'admin.oauthSessions.revokeTitle': 'Відкликати сеанс', - 'admin.oauthSessions.revokeMessage': 'Цей OAuth-сеанс буде негайно відкликаний. Клієнт втратить доступ до MCP.', - 'admin.oauthSessions.revokeSuccess': 'Сеанс відкликано', - 'admin.oauthSessions.revokeError': 'Не вдалося відкликати сеанс', - 'admin.oauthSessions.loadError': 'Не вдалося завантажити OAuth-сеанси', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': 'Події, пов’язані з безпекою та адмініструванням (резервні копії, користувачі, MFA, налаштування).', - 'admin.audit.empty': 'Записів аудиту поки немає.', - 'admin.audit.refresh': 'Оновити', - 'admin.audit.loadMore': 'Завантажити ще', - 'admin.audit.showing': 'Завантажено: {count} · всього {total}', - 'admin.audit.col.time': 'Час', - 'admin.audit.col.user': 'Користувач', - 'admin.audit.col.action': 'Дія', - 'admin.audit.col.resource': 'Об’єкт', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': 'Деталі', - - 'admin.github.title': 'Історія релізів', - 'admin.github.subtitle': 'Останні оновлення з {repo}', - 'admin.github.latest': 'Останній', - 'admin.github.prerelease': 'Пререліз', - 'admin.github.showDetails': 'Показати деталі', - 'admin.github.hideDetails': 'Сховати деталі', - 'admin.github.loadMore': 'Завантажити ще', - 'admin.github.loading': 'Завантаження...', - 'admin.github.support': 'Допомагає продовжувати розробку TREK', - 'admin.github.error': 'Не вдалося завантажити релізи', - 'admin.github.by': 'від', - - 'admin.update.available': 'Доступне оновлення', - 'admin.update.text': 'Доступна версія TREK {version}. У вас встановлено {current}.', - 'admin.update.button': 'Переглянути на GitHub', - 'admin.update.install': 'Встановити оновлення', - 'admin.update.confirmTitle': 'Встановити оновлення?', - 'admin.update.confirmText': 'TREK буде оновлено з {current} до {version}. Сервер перезапуститься автоматично.', - 'admin.update.dataInfo': 'Усі ваші дані (поїздки, користувачі, API-ключі, завантаження, Vacay, Atlas, бюджети) будуть збережені.', - 'admin.update.warning': 'Застосунок буде тимчасово недоступний під час перезапуску.', - 'admin.update.confirm': 'Оновити зараз', - 'admin.update.installing': 'Оновлення…', - 'admin.update.success': 'Оновлення встановлено! Сервер перезапускається…', - 'admin.update.failed': 'Помилка оновлення', - 'admin.update.backupHint': 'Рекомендуємо створити резервну копію перед оновленням.', - 'admin.update.backupLink': 'Перейти до резервних копій', - 'admin.update.howTo': 'Як оновити', - 'admin.update.dockerText': 'Ваш екземпляр TREK працює в Docker. Для оновлення до {version} виконайте ці команди на сервері:', - 'admin.update.reloadHint': 'Перезавантажте сторінку через кілька секунд.', - - // Vacay addon - 'vacay.subtitle': 'Плануйте й керуйте днями відпустки', - 'vacay.settings': 'Налаштування', - 'vacay.year': 'Рік', - 'vacay.addYear': 'Додати наступний рік', - 'vacay.addPrevYear': 'Додати попередній рік', - 'vacay.removeYear': 'Видалити рік', - 'vacay.removeYearConfirm': 'Видалити {year}?', - 'vacay.removeYearHint': 'Усі записи про відпустку і корпоративні вихідні за цей рік будуть безповоротно видалені.', - 'vacay.remove': 'Видалити', - 'vacay.persons': 'Люди', - 'vacay.noPersons': 'Ніхто не доданий', - 'vacay.addPerson': 'Додати людину', - 'vacay.editPerson': 'Редагувати', - 'vacay.removePerson': 'Видалити людину', - 'vacay.removePersonConfirm': 'Видалити {name}?', - 'vacay.removePersonHint': 'Усі записи про відпустку цієї людини будуть безповоротно видалені.', - 'vacay.personName': 'Ім’я', - 'vacay.personNamePlaceholder': 'Введіть ім’я', - 'vacay.color': 'Колір', - 'vacay.add': 'Додати', - 'vacay.legend': 'Легенда', - 'vacay.publicHoliday': 'Державне свято', - 'vacay.companyHoliday': 'Корпоративний вихідний', - 'vacay.weekend': 'Вихідні', - 'vacay.modeVacation': 'Відпустка', - 'vacay.modeCompany': 'Корпоративний вихідний', - 'vacay.entitlement': 'Право на відпустку', - 'vacay.entitlementDays': 'Дні', - 'vacay.used': 'Використано', - 'vacay.remaining': 'Залишилося', - 'vacay.carriedOver': 'із {year}', - 'vacay.blockWeekends': 'Блокувати вихідні', - 'vacay.blockWeekendsHint': 'Заборонити записи про відпустку в суботу та неділю', - 'vacay.weekendDays': 'Вихідні дні', - 'vacay.mon': 'Пн', - 'vacay.tue': 'Вт', - 'vacay.wed': 'Ср', - 'vacay.thu': 'Чт', - 'vacay.fri': 'Пт', - 'vacay.sat': 'Сб', - 'vacay.sun': 'Нд', - 'vacay.publicHolidays': 'Державні свята', - 'vacay.publicHolidaysHint': 'Позначати державні свята в календарі', - 'vacay.selectCountry': 'Виберіть країну', - 'vacay.selectRegion': 'Виберіть регіон (необов’язково)', - 'vacay.companyHolidays': 'Корпоративні вихідні', - 'vacay.companyHolidaysHint': 'Дозволити позначати корпоративні вихідні', - 'vacay.companyHolidaysNoDeduct': 'Корпоративні вихідні не віднімаються від днів відпустки.', - 'vacay.weekStart': 'Тиждень починається з', - 'vacay.weekStartHint': 'Виберіть, чи тиждень починається з понеділка чи неділі', - 'vacay.carryOver': 'Переносити', - 'vacay.carryOverHint': 'Автоматично переносити залишкові дні відпустки на наступний рік', - 'vacay.sharing': 'Спільний доступ', - 'vacay.sharingHint': 'Поділіться планом відпустки з іншими користувачами TREK', - 'vacay.owner': 'Власник', - 'vacay.shareEmailPlaceholder': 'Ел. пошта користувача TREK', - 'vacay.shareSuccess': 'План успішно надано', - 'vacay.shareError': 'Не вдалося поділитися планом', - 'vacay.dissolve': 'Розділити об’єднання', - 'vacay.dissolveHint': 'Знову розділити календарі. Ваші записи будуть збережені.', - 'vacay.dissolveAction': 'Розділити', - 'vacay.dissolved': 'Календар розділено', - 'vacay.fusedWith': 'Об’єднано з', - 'vacay.you': 'ви', - 'vacay.noData': 'Немає даних', - 'vacay.changeColor': 'Змінити колір', - 'vacay.inviteUser': 'Запросити користувача', - 'vacay.inviteHint': 'Запросіть іншого користувача TREK для спільного календаря відпусток.', - 'vacay.selectUser': 'Виберіть користувача', - 'vacay.sendInvite': 'Надіслати запрошення', - 'vacay.inviteSent': 'Запрошення надіслано', - 'vacay.inviteError': 'Не вдалося надіслати запрошення', - 'vacay.pending': 'очікування', - 'vacay.noUsersAvailable': 'Немає доступних користувачів', - 'vacay.accept': 'Прийняти', - 'vacay.decline': 'Відхилити', - 'vacay.acceptFusion': 'Прийняти й об’єднати', - 'vacay.inviteTitle': 'Запит на об’єднання', - 'vacay.inviteWantsToFuse': 'хоче об’єднати календар відпусток з вами.', - 'vacay.fuseInfo1': 'Ви обидва бачитимете всі записи про відпустки в одному спільному календарі.', - 'vacay.fuseInfo2': 'Обидві сторони можуть створювати та редагувати записи одна для одної.', - 'vacay.fuseInfo3': 'Обидві сторони можуть видаляти записи та змінювати право на відпустку.', - 'vacay.fuseInfo4': 'Налаштування, такі як свята та корпоративні вихідні, стають спільними.', - 'vacay.fuseInfo5': 'Об’єднання можна скасувати будь-коли з будь-якого боку. Ваші записи будуть збережені.', - 'vacay.addCalendar': 'Добавить календарь', - 'vacay.calendarColor': 'Колір', - 'vacay.calendarLabel': 'Назва', - 'vacay.noCalendars': 'Немає календарів', - 'nav.myTrips': 'Мої поїздки', - - // Atlas addon - 'atlas.subtitle': 'Ваш слід подорожей по всьому світу', - 'atlas.countries': 'Країни', - 'atlas.trips': 'Подорожі', - 'atlas.places': 'Місця', - 'atlas.days': 'Дні', - 'atlas.visitedCountries': 'Відвідані країни', - 'atlas.cities': 'Міста', - 'atlas.noData': 'Даних про подорожі поки немає', - 'atlas.noDataHint': 'Створіть подорож і додайте місця, щоб побачити карту світу', - 'atlas.lastTrip': 'Остання подорож', - 'atlas.nextTrip': 'Наступна подорож', - 'atlas.daysLeft': 'днів залишилося', - 'atlas.streak': 'Серія', - 'atlas.year': 'рік', - 'atlas.years': 'років', - 'atlas.yearInRow': 'рік підряд', - 'atlas.yearsInRow': 'років підряд', - 'atlas.tripIn': 'подорожі до', - 'atlas.tripsIn': 'подорожей до', - 'atlas.since': 'з', - 'atlas.europe': 'Європа', - 'atlas.asia': 'Азія', - 'atlas.northAmerica': 'Пн. Америка', - 'atlas.southAmerica': 'Пд. Америка', - 'atlas.africa': 'Африка', - 'atlas.oceania': 'Океанія', - 'atlas.other': 'Інше', - 'atlas.firstVisit': 'Перша поїздка', - 'atlas.lastVisitLabel': 'Остання поїздка', - 'atlas.tripSingular': 'Поїздка', - 'atlas.tripPlural': 'Поїздки', - 'atlas.placeVisited': 'Відвідане місце', - 'atlas.placesVisited': 'Відвідані місця', - 'atlas.statsTab': 'Статистика', - 'atlas.bucketTab': 'Список бажань', - 'atlas.addBucket': 'Додати до списку бажань', - 'atlas.bucketNamePlaceholder': 'Місце або напрямок...', - 'atlas.bucketNotesPlaceholder': 'Нотатки (необов’язково)', - 'atlas.bucketEmpty': 'Ваш список бажань порожній', - 'atlas.bucketEmptyHint': 'Додайте місця, які мрієте відвідати', - 'atlas.unmark': 'Видалити', - 'atlas.confirmMark': 'Позначити цю країну як відвідану?', - 'atlas.confirmUnmark': 'Видалити цю країну зі списку відвіданих?', - 'atlas.confirmUnmarkRegion': 'Видалити цей регіон зі списку відвіданих?', - 'atlas.markVisited': 'Позначити як відвідану', - 'atlas.markVisitedHint': 'Додати цю країну до списку відвіданих', - 'atlas.markRegionVisitedHint': 'Додати цей регіон до списку відвіданих', - 'atlas.addToBucket': 'До списку бажань', - 'atlas.addPoi': 'Додати місце', - 'atlas.searchCountry': 'Пошук країни...', - 'atlas.month': 'Місяць', - 'atlas.addToBucketHint': 'Зберегти як місце для відвідування', - 'atlas.bucketWhen': 'Коли ви плануєте поїхати?', - - // Trip Planner - 'trip.tabs.plan': 'План', - 'trip.tabs.transports': 'Транспорт', - 'trip.tabs.reservations': 'Бронювання', - 'trip.tabs.reservationsShort': 'Броні', - 'trip.tabs.packing': 'Список речей', - 'trip.tabs.packingShort': 'Речі', - 'trip.tabs.lists': 'Списки', - 'trip.tabs.listsShort': 'Списки', - 'trip.tabs.budget': 'Бюджет', - 'trip.tabs.files': 'Файли', - 'trip.loading': 'Завантаження поїздки...', - 'trip.loadingPhotos': 'Завантаження фото місць...', - 'trip.mobilePlan': 'План', - 'trip.mobilePlaces': 'Місця', - 'trip.toast.placeUpdated': 'Місце оновлено', - 'trip.toast.placeAdded': 'Місце додано', - 'trip.toast.placeDeleted': 'Місце видалено', - 'trip.toast.selectDay': 'Спочатку виберіть день', - 'trip.toast.assignedToDay': 'Місце призначено на день', - 'trip.toast.reorderError': 'Помилка зміни порядку', - 'trip.toast.reservationUpdated': 'Бронювання оновлено', - 'trip.toast.reservationAdded': 'Бронювання додано', - 'trip.toast.deleted': 'Видалено', - 'trip.confirm.deletePlace': 'Ви впевнені, що хочете видалити це місце?', - 'trip.confirm.deletePlaces': 'Видалити {count} місць?', - 'trip.toast.placesDeleted': '{count} місць видалено', - - // Day Plan Sidebar - 'dayplan.emptyDay': 'На цей день місць не заплановано', - 'dayplan.addNote': 'Додати нотатку', - 'dayplan.editNote': 'Редагувати нотатку', - 'dayplan.noteAdd': 'Додати нотатку', - 'dayplan.noteEdit': 'Редагувати нотатку', - 'dayplan.noteTitle': 'Нотатка', - 'dayplan.noteSubtitle': 'Нотатка на день', - 'dayplan.collapseAll': 'Згорнути всі дні', - 'dayplan.expandAll': 'Розгорнути всі дні', - 'dayplan.totalCost': 'Загальна вартість', - 'dayplan.days': 'Дни', - 'dayplan.dayN': 'День {n}', - 'dayplan.calculating': 'Розрахунок...', - 'dayplan.route': 'Маршрут', - 'dayplan.optimize': 'Оптимізувати', - 'dayplan.optimized': 'Маршрут оптимізовано', - 'dayplan.routeError': 'Не вдалося розрахувати маршрут', - 'dayplan.toast.needTwoPlaces': 'Для оптимизации маршрута нужно минимум два места', - 'dayplan.toast.routeOptimized': 'Маршрут оптимизирован', - 'dayplan.toast.noGeoPlaces': 'Не знайдено місць з координатами для розрахунку маршруту', - 'dayplan.confirmed': 'Подтверждено', - 'dayplan.pendingRes': 'Ожидание', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': 'Експортувати план дня в PDF', - 'dayplan.pdfError': 'Помилка експорту PDF', - 'dayplan.cannotReorderTransport': 'Бронювання з фіксованим часом не можна переміщувати', - 'dayplan.confirmRemoveTimeTitle': 'Видалити час?', - 'dayplan.confirmRemoveTimeBody': 'У цього місця фіксований час ({time}). При переміщенні час буде видалено, і стане доступне вільне сортування.', - 'dayplan.confirmRemoveTimeAction': 'Видалити час і перемістити', - 'dayplan.cannotDropOnTimed': 'Елементи не можна розміщувати між записами з фіксованим часом', - 'dayplan.cannotBreakChronology': 'Це порушить хронологічний порядок запланованих елементів і бронювань', - - // Places Sidebar - 'places.addPlace': 'Додати місце/активність', - 'places.importFile': 'Імпортувати файл', - 'places.sidebarDrop': 'Відпустіть для імпорту', - 'places.importFileHint': 'Імпортуйте файли .gpx, .kml або .kmz із інструментів, таких як Google My Maps, Google Earth або GPS-трекер.', - 'places.importFileDropHere': 'Натисніть, щоб вибрати файл, або перетягніть його сюди', - 'places.importFileDropActive': 'Відпустіть файл для вибору', - 'places.importFileUnsupported': 'Непідтримуваний тип файлу. Використовуйте .gpx, .kml або .kmz.', - 'places.importFileTooLarge': 'Файл занадто великий. Максимальний розмір завантаження — {maxMb} МБ.', - 'places.importFileError': 'Помилка імпорту', - 'places.importAllSkipped': 'Усі місця вже були в подорожі.', - 'places.gpxImported': '{count} місць імпортовано з GPX', - 'places.gpxImportTypes': 'Що імпортувати?', - 'places.gpxImportWaypoints': 'Путеві точки', - 'places.gpxImportRoutes': 'Маршрути', - 'places.gpxImportTracks': 'Треки (з геометрією маршруту)', - 'places.gpxImportNoneSelected': 'Оберіть щонайменше один тип для імпорту.', - 'places.kmlImportTypes': 'Що ви хочете імпортувати?', - 'places.kmlImportPoints': 'Точки (Placemark)', - 'places.kmlImportPaths': 'Маршрути (LineStrings)', - 'places.kmlImportNoneSelected': 'Виберіть щонайменше один тип.', - 'places.selectionCount': '{count} вибрано', - 'places.deleteSelected': 'Видалити вибране', - 'places.kmlKmzImported': '{count} місць імпортовано з KMZ/KML', - 'places.urlResolved': 'Місце імпортовано з URL', - 'places.importList': 'Імпорт списку', - 'places.kmlKmzSummaryValues': 'Позначки: {total} • Імпортовано: {created} • Пропущено: {skipped}', - 'places.importGoogleList': 'Список Google', - 'places.importNaverList': 'Список Naver', - 'places.googleListHint': 'Вставте посилання на спільний список Google Maps для імпорту всіх місць.', - 'places.googleListImported': '{count} місць імпортовано з "{list}"', - 'places.googleListError': 'Не вдалося імпортувати список Google Maps', - 'places.naverListHint': 'Вставте посилання на спільний список Naver Maps для імпорту всіх місць.', - 'places.naverListImported': '{count} місць імпортовано з "{list}"', - 'places.naverListError': 'Не вдалося імпортувати список Naver Maps', - 'places.viewDetails': 'Деталі', - 'places.assignToDay': 'Додати на який день?', - 'places.all': 'Усі', - 'places.unplanned': 'Незаплановані', - 'places.filterTracks': 'Треки', - 'places.search': 'Пошук місць...', - 'places.allCategories': 'Всі категорії', - 'places.categoriesSelected': 'категорій', - 'places.clearFilter': 'Скинути фільтр', - 'places.count': '{count} місць', - 'places.countSingular': '1 місце', - 'places.allPlanned': 'Усі місця заплановані', - 'places.noneFound': 'Місця не знайдені', - 'places.editPlace': 'Редагувати місце', - 'places.formName': 'Назва', - 'places.formNamePlaceholder': 'наприклад, Ейфелева вежа', - 'places.formDescription': 'Опис', - 'places.formDescriptionPlaceholder': 'Короткий опис...', - 'places.formAddress': 'Адреса', - 'places.formAddressPlaceholder': 'Вулиця, місто, країна', - 'places.formLat': 'Широта (наприклад, 48.8566)', - 'places.formLng': 'Довгота (наприклад, 2.3522)', - 'places.formCategory': 'Категорія', - 'places.noCategory': 'Без категорії', - 'places.categoryNamePlaceholder': 'Назва категорії', - 'places.formTime': 'Час', - 'places.startTime': 'Початок', - 'places.endTime': 'Кінець', - 'places.endTimeBeforeStart': 'Час закінчення раніше за час початку', - 'places.timeCollision': 'Перетин за часом із:', - 'places.formWebsite': 'Веб-сайт', - 'places.formNotes': 'Нотатки', - 'places.formNotesPlaceholder': 'Особисті нотатки...', - 'places.formReservation': 'Бронювання', - 'places.reservationNotesPlaceholder': 'Нотатки про бронювання, номер підтвердження...', - 'places.mapsSearchPlaceholder': 'Пошук місць...', - 'places.mapsSearchError': 'Помилка пошуку місць.', - 'places.loadingDetails': 'Завантаження даних про місце…', - 'places.osmHint': 'Пошук через OpenStreetMap (без фото, годин роботи та рейтингів). Додайте API-ключ Google у налаштуваннях для повної інформації.', - 'places.osmActive': 'Пошук через OpenStreetMap (без фото, рейтингів та годин роботи). Додайте API-ключ Google у налаштуваннях для розширених даних.', - 'places.categoryCreateError': 'Не вдалося створити категорію', - 'places.nameRequired': 'Введіть назву', - 'places.saveError': 'Помилка збереження', - // Place Inspector - 'inspector.opened': 'Відкрито', - 'inspector.closed': 'Закрито', - 'inspector.openingHours': 'Години роботи', - 'inspector.showHours': 'Показати години роботи', - 'inspector.files': 'Файли', - 'inspector.filesCount': '{count} файлів', - 'inspector.removeFromDay': 'Прибрати з дня', - 'inspector.remove': 'Видалити', - 'inspector.addToDay': 'Додати до дня', - 'inspector.confirmedRes': 'Підтверджене бронювання', - 'inspector.pendingRes': 'Очікуване бронювання', - 'inspector.google': 'Відкрити в Google Maps', - 'inspector.website': 'Відкрити сайт', - 'inspector.addRes': 'Бронювання', - 'inspector.editRes': 'Редагувати бронювання', - 'inspector.participants': 'Учасники', - 'inspector.trackStats': 'Дані маршруту', - - // Reservations - 'reservations.title': 'Бронювання', - 'reservations.empty': 'Поки немає бронювань', - 'reservations.emptyHint': 'Додайте бронювання на авіаквитки, готелі та інше', - 'reservations.add': 'Додати бронювання', - 'reservations.addManual': 'Ручне бронювання', - 'reservations.placeHint': 'Порада: бронювання краще створювати безпосередньо з місця, щоб пов’язати їх з планом дня.', - 'reservations.confirmed': 'Підтверджено', - 'reservations.pending': 'Очікування', - 'reservations.summary': '{confirmed} підтвр., {pending} очікувань', - 'reservations.fromPlan': 'З плану', - 'reservations.showFiles': 'Показати файли', - 'reservations.editTitle': 'Редагувати бронювання', - 'reservations.status': 'Статус', - 'reservations.datetime': 'Дата і час', - 'reservations.startTime': 'Час початку', - 'reservations.endTime': 'Час закінчення', - 'reservations.date': 'Дата', - 'reservations.time': 'Час', - 'reservations.timeAlt': 'Час (альтернативний, напр. 19:30)', - 'reservations.notes': 'Нотатки', - 'reservations.notesPlaceholder': 'Додаткові нотатки...', - 'reservations.meta.airline': 'Авіакомпанія', - 'reservations.meta.flightNumber': 'Номер рейсу', - 'reservations.meta.from': 'Звідки', - 'reservations.meta.to': 'Куди', - 'reservations.needsReview': 'Перевірити', - 'reservations.needsReviewHint': 'Аеропорт не вдалося визначити автоматично — підтвердіть місцезнаходження.', - 'reservations.searchLocation': 'Шукати станцію, порт, адресу...', - 'airport.searchPlaceholder': 'Код аеропорту або місто (наприклад, FRA)', - 'map.connections': 'Сполучення', - 'map.showConnections': 'Показати маршрути бронювань', - 'map.hideConnections': 'Приховати маршрути бронювань', - 'settings.bookingLabels': 'Підписи маршрутів бронювань', - 'settings.bookingLabelsHint': 'Показує назви станцій / аеропортів на карті. Якщо вимкнено, показується лише значок.', - 'reservations.meta.trainNumber': 'Номер поїзда', - 'reservations.meta.platform': 'Платформа', - 'reservations.meta.seat': 'Місце', - 'reservations.meta.checkIn': 'Заїзд', - 'reservations.meta.checkInUntil': 'Заселення до', - 'reservations.meta.checkOut': 'Виїзд', - 'reservations.meta.linkAccommodation': 'Житло', - 'reservations.meta.pickAccommodation': 'Прив’язати до житла', - 'reservations.meta.noAccommodation': 'Ні', - 'reservations.meta.hotelPlace': 'Житло', - 'reservations.meta.pickHotel': 'Оберіть житло', - 'reservations.meta.fromDay': 'З', - 'reservations.meta.toDay': 'По', - 'reservations.meta.selectDay': 'Оберіть день', - 'reservations.type.flight': 'Авіаквиток', - 'reservations.type.hotel': 'Житло', - 'reservations.type.restaurant': 'Ресторан', - 'reservations.type.train': 'Поїзд', - 'reservations.type.car': 'Автомобіль', - 'reservations.type.cruise': 'Круїз', - 'reservations.type.event': 'Заходи', - 'reservations.type.tour': 'Екскурсія', - 'reservations.type.other': 'Інше', - 'reservations.confirm.delete': 'Ви впевнені, що хочете видалити бронювання «{name}»?', - 'reservations.confirm.deleteTitle': 'Видалити бронювання?', - 'reservations.confirm.deleteBody': '«{name}» буде видалено назавжди.', - 'reservations.toast.updated': 'Бронювання оновлено', - 'reservations.toast.removed': 'Бронювання видалено', - 'reservations.toast.fileUploaded': 'Файл завантажено', - 'reservations.toast.uploadError': 'Помилка завантаження', - 'reservations.newTitle': 'Нове бронювання', - 'reservations.bookingType': 'Тип бронювання', - 'reservations.titleLabel': 'Назва', - 'reservations.titlePlaceholder': 'наприклад, Lufthansa LH123, Hotel Adlon, ...', - 'reservations.locationAddress': 'Місцезнаходження / Адреса', - 'reservations.locationPlaceholder': 'Адреса, аеропорт, готель...', - 'reservations.confirmationCode': 'Код бронювання', - 'reservations.confirmationPlaceholder': 'наприклад, ABC12345', - 'reservations.day': 'День', - 'reservations.noDay': 'Без дня', - 'reservations.place': 'Місце', - 'reservations.noPlace': 'Без місця', - 'reservations.pendingSave': 'буде збережено…', - 'reservations.uploading': 'Завантаження...', - 'reservations.attachFile': 'Прикріпити файл', - 'reservations.linkExisting': 'Прив’язати існуючий файл', - 'reservations.toast.saveError': 'Помилка збереження', - 'reservations.toast.updateError': 'Помилка оновлення', - 'reservations.toast.deleteError': 'Помилка видалення', - 'reservations.confirm.remove': 'Видалити бронювання для «{name}»?', - 'reservations.linkAssignment': 'Прив’язати до призначення дня', - 'reservations.pickAssignment': 'Оберіть призначення з вашого плану...', - 'reservations.noAssignment': 'Без прив’язки (самостійно)', - 'reservations.price': 'Ціна', - 'reservations.budgetCategory': 'Категорія бюджету', - 'reservations.budgetCategoryPlaceholder': 'наприклад, Транспорт, Проживання', - 'reservations.budgetCategoryAuto': 'Авто (за типом бронювання)', - 'reservations.budgetHint': 'При збереженні буде автоматично створено запис бюджету.', - 'reservations.departureDate': 'Виліт', - 'reservations.arrivalDate': 'Приліт', - 'reservations.departureTime': 'Час вильоту', - 'reservations.arrivalTime': 'Час прильоту', - 'reservations.pickupDate': 'Отримання', - 'reservations.returnDate': 'Повернення', - 'reservations.pickupTime': 'Час отримання', - 'reservations.returnTime': 'Час повернення', - 'reservations.endDate': 'Дата закінчення', - 'reservations.meta.departureTimezone': 'TZ вильоту', - 'reservations.meta.arrivalTimezone': 'TZ прильоту', - 'reservations.span.departure': 'Виліт', - 'reservations.span.arrival': 'Приліт', - 'reservations.span.inTransit': 'У дорозі', - 'reservations.span.pickup': 'Отримання', - 'reservations.span.return': 'Повернення', - 'reservations.span.active': 'Активно', - 'reservations.span.start': 'Початок', - 'reservations.span.end': 'Кінець', - 'reservations.span.ongoing': 'Триває', - 'reservations.validation.endBeforeStart': 'Дата/час закінчення повинен бути пізніше дати/часу початку', - 'reservations.addBooking': 'Добавить бронирование', - - // Budget - 'budget.title': 'Бюджет', - 'budget.exportCsv': 'Експорт CSV', - 'budget.emptyTitle': 'Бюджет ще не створено', - 'budget.emptyText': 'Створіть категорії й записи для планування бюджету поїздки', - 'budget.emptyPlaceholder': 'Введіть назву категорії...', - 'budget.createCategory': 'Створити категорію', - 'budget.category': 'Категорія', - 'budget.categoryName': 'Назва категорії', - 'budget.table.name': 'Назва', - 'budget.table.total': 'Всього', - 'budget.table.persons': 'Осіб', - 'budget.table.days': 'Днів', - 'budget.table.perPerson': 'На особу', - 'budget.table.perDay': 'На день', - 'budget.table.perPersonDay': 'Ос./день', - 'budget.table.note': 'Нотатка', - 'budget.table.date': 'Дата', - 'budget.newEntry': 'Новий запис', - 'budget.defaultEntry': 'Новий запис', - 'budget.defaultCategory': 'Нова категорія', - 'budget.total': 'Всього', - 'budget.totalBudget': 'Загальний бюджет', - 'budget.byCategory': 'За категоріями', - 'budget.editTooltip': 'Натисніть для редагування', - 'budget.linkedToReservation': 'Пов’язано з бронюванням — редагуйте назву там', - 'budget.confirm.deleteCategory': 'Ви впевнені, що хочете видалити категорію «{name}» з {count} записами?', - 'budget.deleteCategory': 'Видалити категорію', - 'budget.perPerson': 'На особу', - 'budget.paid': 'Оплачено', - 'budget.open': 'Не оплачено', - 'budget.noMembers': 'Учасники не призначені', - 'budget.settlement': 'Взаєморозрахунок', - 'budget.settlementInfo': 'Натисніть на аватар учасника в рядку бюджету, щоб відзначити його зеленим — це означає, що він заплатив. Взаєморозрахунок покаже, хто кому і скільки винен.', - 'budget.netBalances': 'Чисті баланси', - - // Files - 'files.title': 'Файли', - 'files.pageTitle': 'Файли та документи', - 'files.subtitle': '{count} файлів для {trip}', - 'files.download': 'Завантажити', - 'files.openError': 'Не вдалося відкрити файл', - 'files.downloadPdf': 'Завантажити PDF', - 'files.count': '{count} файлів', - 'files.countSingular': '1 файл', - 'files.uploaded': '{count} завантажено', - 'files.uploadError': 'Помилка завантаження', - 'files.dropzone': 'Перетягніть файли сюди', - 'files.dropzoneHint': 'або натисніть для вибору', - 'files.allowedTypes': 'Зображення, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Макс. 50 МБ', - 'files.uploading': 'Завантаження...', - 'files.filterAll': 'Усі', - 'files.filterPdf': 'PDF', - 'files.filterImages': 'Зображення', - 'files.filterDocs': 'Документи', - 'files.filterCollab': 'Нотатки Collab', - 'files.sourceCollab': 'З нотаток Collab', - 'files.empty': 'Файлів поки немає', - 'files.emptyHint': 'Завантажте файли, щоб прикріпити їх до поїздки', - 'files.openTab': 'Відкрити в новій вкладці', - 'files.confirm.delete': 'Ви впевнені, що хочете видалити цей файл?', - 'files.toast.deleted': 'Файл видалено', - 'files.toast.deleteError': 'Не вдалося видалити файл', - 'files.sourcePlan': 'План дня', - 'files.sourceBooking': 'Бронювання', - 'files.attach': 'Прикріпити', - 'files.pasteHint': 'Також можна вставити зображення з буфера обміну (Ctrl+V)', - 'files.trash': 'Кошик', - 'files.trashEmpty': 'Кошик порожній', - 'files.emptyTrash': 'Очистити кошик', - 'files.restore': 'Відновити', - 'files.star': 'У фаворити', - 'files.unstar': 'Зняти з фаворитів', - 'files.assign': 'Призначити', - 'files.assignTitle': 'Призначити файл', - 'files.assignPlace': 'Місце', - 'files.assignBooking': 'Бронювання', - 'files.unassigned': 'Не призначено', - 'files.unlink': 'Видалити зв’язок', - 'files.toast.trashed': 'Переміщено до кошика', - 'files.toast.restored': 'Файл відновлено', - 'files.toast.trashEmptied': 'Кошик очищено', - 'files.toast.assigned': 'Файл призначено', - 'files.toast.assignError': 'Помилка призначення', - 'files.toast.restoreError': 'Помилка відновлення', - 'files.confirm.permanentDelete': 'Безповоротно видалити цей файл? Цю дію неможливо скасувати.', - 'files.confirm.emptyTrash': 'Безповоротно видалити всі файли з кошика? Цю дію неможливо скасувати.', - 'files.noteLabel': 'Нотатка', - 'files.notePlaceholder': 'Додати нотатку...', - - // Packing - 'packing.title': 'Список речей', - 'packing.empty': 'Список речей порожній', - 'packing.import': 'Імпорт', - 'packing.importTitle': 'Імпорт списку речей', - 'packing.importHint': 'Один предмет у рядок. Категорію та кількість — через кому, крапку з комою або табуляцію: Назва, Категорія, Кількість', - 'packing.importPlaceholder': 'Зубна щітка\nСонцезахисний крем, Гігієна\nФутболки, Одяг, 5\nПаспорт, Документи', - 'packing.importCsv': 'Завантажити CSV/TXT', - 'packing.importAction': 'Імпортувати {count}', - 'packing.importSuccess': '{count} предметів імпортовано', - 'packing.importError': 'Помилка імпорту', - 'packing.importEmpty': 'Немає предметів для імпорту', - 'packing.progress': '{packed} з {total} зібрано ({percent}%)', - 'packing.clearChecked': 'Видалити {count} позначених', - 'packing.clearCheckedShort': 'Видалити {count}', - 'packing.suggestions': 'Підказки', - 'packing.suggestionsTitle': 'Додати підказки', - 'packing.allSuggested': 'Усі підказки додано', - 'packing.allPacked': 'Усе зібрано!', - 'packing.addPlaceholder': 'Додати річ...', - 'packing.categoryPlaceholder': 'Категорія...', - 'packing.filterAll': 'Усі', - 'packing.filterOpen': 'Не зібрано', - 'packing.filterDone': 'Зібрано', - 'packing.emptyTitle': 'Список речей порожній', - 'packing.emptyHint': 'Додайте речі або використайте підказки', - 'packing.emptyFiltered': 'Немає речей, що відповідають фільтру', - 'packing.menuRename': 'Перейменувати', - 'packing.menuCheckAll': 'Позначити все', - 'packing.menuUncheckAll': 'Зняти позначки', - 'packing.menuDeleteCat': 'Видалити категорію', - 'packing.addItem': 'Додати річ', - 'packing.addItemPlaceholder': 'Назва...', - 'packing.addCategory': 'Додати категорію', - 'packing.newCategoryPlaceholder': 'Назва категорії (наприклад, Одяг)', - 'packing.applyTemplate': 'Застосувати шаблон', - 'packing.template': 'Шаблон', - 'packing.templateApplied': '{count} речей додано з шаблону', - 'packing.templateError': 'Помилка застосування шаблону', - 'packing.saveAsTemplate': 'Зберегти як шаблон', - 'packing.templateName': 'Назва шаблону', - 'packing.templateSaved': 'Список речей збережено як шаблон', - 'packing.noMembers': 'Немає учасників', - 'packing.bags': 'Багаж', - 'packing.noBag': 'Не призначено', - 'packing.totalWeight': 'Загальна вага', - 'packing.bagName': 'Назва...', - 'packing.addBag': 'Додати багаж', - 'packing.changeCategory': 'Змінити категорію', - 'packing.confirm.clearChecked': 'Ви впевнені, що хочете видалити {count} позначених речей?', - 'packing.confirm.deleteCat': 'Ви впевнені, що хочете видалити категорію «{name}» з {count} речами?', - 'packing.defaultCategory': 'Інше', - 'packing.toast.saveError': 'Помилка збереження', - 'packing.toast.deleteError': 'Помилка видалення', - 'packing.toast.renameError': 'Помилка перейменування', - 'packing.toast.addError': 'Помилка додавання', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: 'Паспорт', category: 'Документи' }, - { name: 'Посвідчення особи', category: 'Документи' }, - { name: 'Страхування', category: 'Документи' }, - { name: 'Авіаквитки', category: 'Документи' }, - { name: 'Банківська картка', category: 'Фінанси' }, - { name: 'Готівка', category: 'Фінанси' }, - { name: 'Віза', category: 'Документи' }, - { name: 'Футболки', category: 'Одяг' }, - { name: 'Штани', category: 'Одяг' }, - { name: 'Нижня білизна', category: 'Одяг' }, - { name: 'Шкарпетки', category: 'Одяг' }, - { name: 'Куртка', category: 'Одяг' }, - { name: 'Піжама', category: 'Одяг' }, - { name: 'Купальник', category: 'Одяг' }, - { name: 'Дощовик', category: 'Одяг' }, - { name: 'Зручне взуття', category: 'Одяг' }, - { name: 'Зубна щітка', category: 'Гігієна' }, - { name: 'Зубна паста', category: 'Гігієна' }, - { name: 'Шампунь', category: 'Гігієна' }, - { name: 'Дезодорант', category: 'Гігієна' }, - { name: 'Сонцезахисний крем', category: 'Гігієна' }, - { name: 'Пасок', category: 'Гігієна' }, - { name: 'Зарядний пристрій', category: 'Електроніка' }, - { name: 'Зовнішній акумулятор', category: 'Електроніка' }, - { name: 'Навушники', category: 'Електроніка' }, - { name: 'Адаптер для розеток', category: 'Електроніка' }, - { name: 'Фотоапарат', category: 'Електроніка' }, - { name: 'Знеболювальні', category: 'Здоров’я' }, - { name: 'Пластирі', category: 'Здоров’я' }, - { name: 'Антисептик', category: 'Здоров’я' }, - ], - - // Members / Sharing - 'members.shareTrip': 'Поділитися поїздкою', - 'members.inviteUser': 'Запросити користувача', - 'members.selectUser': 'Оберіть користувача…', - 'members.invite': 'Запросити', - 'members.allHaveAccess': 'У всіх користувачів вже є доступ.', - 'members.access': 'Доступ', - 'members.person': 'особа', - 'members.persons': 'осіб', - 'members.you': 'ви', - 'members.owner': 'Власник', - 'members.leaveTrip': 'Покинути поїздку', - 'members.removeAccess': 'Відкликати доступ', - 'members.confirmLeave': 'Покинути поїздку? Ви втратите доступ.', - 'members.confirmRemove': 'Відкликати доступ у цього користувача?', - 'members.loadError': 'Не вдалося завантажити учасників', - 'members.added': 'додано', - 'members.addError': 'Помилка додавання', - 'members.removed': 'Учасник видалений', - 'members.removeError': 'Помилка видалення', - - // Categories (Admin) - 'categories.title': 'Категорії', - 'categories.subtitle': 'Управління категоріями місць', - 'categories.new': 'Нова категорія', - 'categories.empty': 'Категорій поки немає', - 'categories.namePlaceholder': 'Назва категорії', - 'categories.icon': 'Іконка', - 'categories.color': 'Колір', - 'categories.customColor': 'Вибрати свій колір', - 'categories.preview': 'Попередній перегляд', - 'categories.defaultName': 'Категорія', - 'categories.update': 'Оновити', - 'categories.create': 'Створити', - 'categories.confirm.delete': 'Видалити категорію? Місця в цій категорії не будуть видалені.', - 'categories.toast.loadError': 'Не вдалося завантажити категорії', - 'categories.toast.nameRequired': 'Введіть назву', - 'categories.toast.updated': 'Категорію оновлено', - 'categories.toast.created': 'Категорію створено', - 'categories.toast.saveError': 'Помилка збереження', - 'categories.toast.deleted': 'Категорію видалено', - 'categories.toast.deleteError': 'Помилка видалення', - - // Backup (Admin) - 'backup.title': 'Резервна копія', - 'backup.subtitle': 'База даних та всі завантажені файли', - 'backup.refresh': 'Оновити', - 'backup.upload': 'Завантажити копію', - 'backup.uploading': 'Завантаження…', - 'backup.create': 'Створити копію', - 'backup.creating': 'Створення…', - 'backup.empty': 'Резервних копій немає', - 'backup.createFirst': 'Створити першу копію', - 'backup.download': 'Завантажити', - 'backup.restore': 'Відновити', - 'backup.confirm.restore': 'Відновити копію «{name}»?\n\nУсі поточні дані будуть замінені даними з копії.', - 'backup.confirm.uploadRestore': 'Завантажити та відновити файл копії «{name}»?\n\nУсі поточні дані будуть перезаписані.', - 'backup.confirm.delete': 'Видалити копію «{name}»?', - 'backup.toast.loadError': 'Не вдалося завантажити резервні копії', - 'backup.toast.created': 'Резервну копію створено', - 'backup.toast.createError': 'Не вдалося створити резервну копію', - 'backup.toast.restored': 'Копію відновлено. Сторінка перезавантажиться…', - 'backup.toast.restoreError': 'Помилка відновлення', - 'backup.toast.uploadError': 'Помилка завантаження', - 'backup.toast.deleted': 'Резервну копію видалено', - 'backup.toast.deleteError': 'Помилка видалення', - 'backup.toast.downloadError': 'Помилка завантаження', - 'backup.toast.settingsSaved': 'Налаштування автокопіювання збережено', - 'backup.toast.settingsError': 'Не вдалося зберегти налаштування', - 'backup.auto.title': 'Автокопіювання', - 'backup.auto.subtitle': 'Автоматичне резервне копіювання за розкладом', - 'backup.auto.enable': 'Увімкнути автокопіювання', - 'backup.auto.enableHint': 'Резервні копії створюватимуться автоматично за обраним графіком', - 'backup.auto.interval': 'Інтервал', - 'backup.auto.hour': 'Запуск о', - 'backup.auto.hourHint': 'Місцевий час сервера (формат {format})', - 'backup.auto.dayOfWeek': 'День тижня', - 'backup.auto.dayOfMonth': 'День місяця', - 'backup.auto.dayOfMonthHint': 'Обмежено 1–28 для сумісності з усіма місяцями', - 'backup.auto.scheduleSummary': 'Графік', - 'backup.auto.summaryDaily': 'Щодня о {hour}:00', - 'backup.auto.summaryWeekly': 'Щотижня в {day} о {hour}:00', - 'backup.auto.summaryMonthly': '{day}-го числа кожного місяця о {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': 'Автокопіювання налаштовано через змінні оточення Docker. Щоб змінити параметри, оновіть docker-compose.yml і перезапустіть контейнер.', - 'backup.auto.copyEnv': 'Скопіювати змінні оточення Docker', - 'backup.auto.envCopied': 'Змінні оточення Docker скопійовано в буфер обміну', - 'backup.auto.keepLabel': 'Видаляти старі копії через', - 'backup.dow.sunday': 'Нд', - 'backup.dow.monday': 'Пн', - 'backup.dow.tuesday': 'Вт', - 'backup.dow.wednesday': 'Ср', - 'backup.dow.thursday': 'Чт', - 'backup.dow.friday': 'Пт', - 'backup.dow.saturday': 'Сб', - 'backup.interval.hourly': 'Кожну годину', - 'backup.interval.daily': 'Щодня', - 'backup.interval.weekly': 'Щотижня', - 'backup.interval.monthly': 'Щомісяця', - 'backup.keep.1day': '1 день', - 'backup.keep.3days': '3 дні', - 'backup.keep.7days': '7 днів', - 'backup.keep.14days': '14 днів', - 'backup.keep.30days': '30 днів', - 'backup.keep.forever': 'Зберігати вічно', - - // Photos - 'photos.title': 'Фотографії', - 'photos.subtitle': '{count} фото для {trip}', - 'photos.dropHere': 'Перетягніть фото сюди...', - 'photos.dropHereActive': 'Перетягніть фото сюди', - 'photos.captionForAll': 'Підпис (для всіх)', - 'photos.captionPlaceholder': 'Необов’язковий підпис...', - 'photos.addCaption': 'Додати підпис...', - 'photos.allDays': 'Усі дні', - 'photos.noPhotos': 'Фото поки немає', - 'photos.uploadHint': 'Завантажте фото з поїздки', - 'photos.clickToSelect': 'або натисніть для вибору', - 'photos.linkPlace': 'Прив’язати місце', - 'photos.noPlace': 'Без місця', - 'photos.uploadN': '{n} фото завантажено', - 'photos.linkDay': 'Прив’язати день', - 'photos.noDay': 'Немає дня', - 'photos.dayLabel': 'День {number}', - 'photos.photoSelected': 'Фото вибрано', - 'photos.photosSelected': 'Фото вибрано', - 'photos.fileTypeHint': 'JPG, PNG, WebP · макс. 10 МБ · до 30 фото', - - // Backup restore modal - 'backup.restoreConfirmTitle': 'Відновити копію?', - 'backup.restoreWarning': 'Усі поточні дані (поїздки, місця, користувачі, завантаження) будуть безповоротно замінені даними з копії. Цю дію неможливо скасувати.', - 'backup.restoreTip': 'Порада: створіть резервну копію поточного стану перед відновленням.', - 'backup.restoreConfirm': 'Так, відновити', - - // PDF - 'pdf.travelPlan': 'План поїздки', - 'pdf.planned': 'Заплановано', - 'pdf.costLabel': 'Вартість EUR', - 'pdf.preview': 'Попередній перегляд PDF', - 'pdf.saveAsPdf': 'Зберегти як PDF', - - // Planner - 'planner.places': 'Місця', - 'planner.bookings': 'Бронювання', - 'planner.packingList': 'Список речей', - 'planner.documents': 'Документи', - 'planner.dayPlan': 'План дня', - 'planner.reservations': 'Бронювання', - 'planner.minTwoPlaces': 'Потрібно мінімум 2 місця з координатами', - 'planner.noGeoPlaces': 'Немає місць з координатами', - 'planner.routeCalculated': 'Маршрут розраховано', - 'planner.routeCalcFailed': 'Не вдалося розрахувати маршрут', - 'planner.routeError': 'Помилка розрахунку маршруту', - 'planner.icsExportFailed': 'Не вдалося експортувати ICS', - 'planner.routeOptimized': 'Маршрут оптимізовано', - 'planner.reservationUpdated': 'Бронювання оновлено', - 'planner.reservationAdded': 'Бронювання додано', - 'planner.confirmDeleteReservation': 'Видалити бронювання?', - 'planner.reservationDeleted': 'Бронювання видалено', - 'planner.days': 'Дні', - 'planner.allPlaces': 'Усі місця', - 'planner.totalPlaces': 'Усього {n} місць', - 'planner.noDaysPlanned': 'Дні ще не заплановано', - 'planner.editTrip': 'Редагувати поїздку →', - 'planner.placeOne': '1 місце', - 'planner.placeN': '{n} місць', - 'planner.addNote': 'Додати нотатку', - 'planner.noEntries': 'На цей день записів немає', - 'planner.addPlace': 'Додати місце/активність', - 'planner.addPlaceShort': '+ Додати місце/активність', - 'planner.resPending': 'Бронювання очікує · ', - 'planner.resConfirmed': 'Бронювання підтверджено · ', - 'planner.notePlaceholder': 'Нотатка…', - 'planner.noteTimePlaceholder': 'Час (необов’язково)', - 'planner.noteExamplePlaceholder': 'наприклад, S3 о 14:30 з вокзалу, пором з причалу 7, обідня перерва…', - 'planner.totalCost': 'Загальна вартість', - 'planner.searchPlaces': 'Пошук місць…', - 'planner.allCategories': 'Усі категорії', - 'planner.noPlacesFound': 'Місця не знайдені', - 'planner.addFirstPlace': 'Додати перше місце', - 'planner.noReservations': 'Немає бронювань', - 'planner.addFirstReservation': 'Додати перше бронювання', - 'planner.new': 'Нове', - 'planner.addToDay': '+ День', - 'planner.calculating': 'Розрахунок…', - 'planner.route': 'Маршрут', - 'planner.optimize': 'Оптимізувати', - 'planner.openGoogleMaps': 'Відкрити в Google Maps', - 'planner.selectDayHint': 'Оберіть день зі списку ліворуч для перегляду плану дня', - 'planner.noPlacesForDay': 'На цей день місць поки немає', - 'planner.addPlacesLink': 'Додати місця →', - 'planner.minTotal': 'мін. всього', - 'planner.noReservation': 'Немає бронювання', - 'planner.removeFromDay': 'Прибрати з дня', - 'planner.addToThisDay': 'Додати до дня', - 'planner.overview': 'Огляд', - 'planner.noDays': 'Днів немає', - 'planner.editTripToAddDays': 'Відредагуйте поїздку, щоб додати дні', - 'planner.dayCount': '{n} днів', - 'planner.clickToUnlock': 'Натисніть, щоб розблокувати', - 'planner.keepPosition': 'Зберегти позицію при оптимізації маршруту', - 'planner.dayDetails': 'Подробиці дня', - 'planner.dayN': 'День {n}', - - // Dashboard Stats - 'stats.countries': 'Країни', - 'stats.cities': 'Міста', - 'stats.trips': 'Поїздки', - 'stats.places': 'Місця', - 'stats.worldProgress': 'Прогрес по світу', - 'stats.visited': 'відвідано', - 'stats.remaining': 'залишилось', - 'stats.visitedCountries': 'Відвідані країни', - - // Day Detail Panel - 'day.precipProb': 'Ймовірність опадів', - 'day.precipitation': 'Опади', - 'day.wind': 'Вітер', - 'day.sunrise': 'Схід', - 'day.sunset': 'Захід', - 'day.hourlyForecast': 'Погодний прогноз по годинах', - 'day.climateHint': 'Історичні середні — реальний прогноз доступний за 16 днів до цієї дати.', - 'day.noWeather': 'Дані про погоду недоступні. Додайте місце з координатами.', - 'day.overview': 'Огляд дня', - 'day.accommodation': 'Проживання', - 'day.addAccommodation': 'Додати проживання', - 'day.hotelDayRange': 'Застосувати до днів', - 'day.noPlacesForHotel': 'Спочатку додайте місця до поїздки', - 'day.allDays': 'Усі', - 'day.checkIn': 'Заїзд', - 'day.checkInUntil': 'До', - 'day.checkOut': 'Виїзд', - 'day.confirmation': 'Підтвердження', - 'day.editAccommodation': 'Редагувати проживання', - 'day.reservations': 'Бронювання', - - // Memories / Immich - 'memories.title': 'Фото', - 'memories.notConnected': 'Immich не підключено', - 'memories.notConnectedHint': 'Підключіть Immich у налаштуваннях, щоб бачити фотографії з поїздок.', - 'memories.notConnectedMultipleHint': 'Підключіть одного з цих фотопровайдерів: {provider_names} у Налаштуваннях, щоб додавати фотографії до цієї подорожі.', - 'memories.noDates': 'Додайте дати поїздки, щоб завантажити фотографії.', - 'memories.noPhotos': 'Фотографій не знайдено', - 'memories.noPhotosHint': 'В Immich немає фотографій за період цієї поїздки.', - 'memories.photosFound': 'фото', - 'memories.fromOthers': 'від інших', - 'memories.sharePhotos': 'Поділитися фото', - 'memories.sharing': 'Спільний доступ', - 'memories.reviewTitle': 'Перевірте свої фото', - 'memories.reviewHint': 'Натисніть на фото, щоб виключити його зі спільного доступу.', - 'memories.shareCount': 'Поділитися ({count} фото)', - 'memories.providerUrl': 'URL сервера', - 'memories.providerApiKey': 'API-ключ', - 'memories.providerUsername': 'Ім’я користувача', - 'memories.providerPassword': 'Пароль', - 'memories.providerOTP': 'Код MFA (якщо увімкнено)', - 'memories.skipSSLVerification': 'Пропустити перевірку SSL-сертифіката', - 'memories.immichAutoUpload': 'Дублювати фото journey в Immich при завантаженні', - 'memories.providerUrlHintSynology': 'Включіть шлях додатку Photos в URL, наприклад https://nas:5001/photo', - 'memories.testConnection': 'Перевірити підключення', - 'memories.testShort': 'Перевірити', - 'memories.testFirst': 'Спершу перевірте підключення', - 'memories.connected': 'Підключено', - 'memories.disconnected': 'Не підключено', - 'memories.connectionSuccess': 'Підключення до Immich встановлено', - 'memories.connectionError': 'Не вдалося підключитися до Immich', - 'memories.saved': 'Налаштування {provider_name} збережено', - 'memories.providerDisconnectedBanner': 'З’єднання з {provider_name} втрачено. Перепідключіться в Налаштуваннях для перегляду фото.', - 'memories.saveError': 'Не вдалося зберегти налаштування {provider_name}', - 'memories.oldest': 'Спочатку старі', - 'memories.newest': 'Спочатку нові', - 'memories.allLocations': 'Усі місця', - 'memories.addPhotos': 'Додати фото', - 'memories.linkAlbum': 'Прив’язати альбом', - 'memories.selectAlbum': 'Вибрати альбом Immich', - 'memories.selectAlbumMultiple': 'Вибрати альбом', - 'memories.noAlbums': 'Альбомів не знайдено', - 'memories.syncAlbum': 'Синхронізувати', - 'memories.unlinkAlbum': 'Відв’язати', - 'memories.photos': 'фото', - 'memories.selectPhotos': 'Вибрати фото з Immich', - 'memories.selectPhotosMultiple': 'Вибрати фотографії', - 'memories.selectHint': 'Натисніть на фото, щоб вибрати їх.', - 'memories.selected': 'вибрано', - 'memories.addSelected': 'Додати {count} фото', - 'memories.alreadyAdded': 'Додано', - 'memories.private': 'Приватне', - 'memories.stopSharing': 'Припинити доступ', - 'memories.tripDates': 'Дати поїздки', - 'memories.allPhotos': 'Усі фото', - 'memories.confirmShareTitle': 'Поділитися з учасниками поїздки?', - 'memories.confirmShareHint': '{count} фото стануть видимі всім учасникам цієї поїздки. Ви зможете зробити окремі фото приватними пізніше.', - 'memories.confirmShareButton': 'Поділитися фото', - - // Collab Addon - 'collab.tabs.chat': 'Чат', - 'collab.tabs.notes': 'Нотатки', - 'collab.tabs.polls': 'Опитування', - 'collab.whatsNext.title': 'Що далі', - 'collab.whatsNext.today': 'Сьогодні', - 'collab.whatsNext.tomorrow': 'Завтра', - 'collab.whatsNext.empty': 'Немає майбутніх активностей', - 'collab.whatsNext.until': 'до', - 'collab.whatsNext.emptyHint': 'Активності з часом відображатимуться тут', - 'collab.chat.send': 'Відправити', - 'collab.chat.placeholder': 'Введіть повідомлення...', - 'collab.chat.empty': 'Розпочніть розмову', - 'collab.chat.emptyHint': 'Повідомлення видимі всім учасникам поїздки', - 'collab.chat.emptyDesc': 'Діліться ідеями, планами та новинами з вашою групою', - 'collab.chat.today': 'Сьогодні', - 'collab.chat.yesterday': 'Вчора', - 'collab.chat.deletedMessage': 'видалив(ла) повідомлення', - 'collab.chat.reply': 'Відповісти', - 'collab.chat.loadMore': 'Завантажити старі повідомлення', - 'collab.chat.justNow': 'щойно', - 'collab.chat.minutesAgo': '{n} хв. тому', - 'collab.chat.hoursAgo': '{n} год. тому', - 'collab.notes.title': 'Нотатки', - 'collab.notes.new': 'Нова нотатка', - 'collab.notes.empty': 'Нотаток поки що немає', - 'collab.notes.emptyHint': 'Почніть записувати ідеї та плани', - 'collab.notes.all': 'Усі', - 'collab.notes.titlePlaceholder': 'Назва нотатки', - 'collab.notes.contentPlaceholder': 'Напишіть щось...', - 'collab.notes.categoryPlaceholder': 'Категорія', - 'collab.notes.newCategory': 'Нова категорія...', - 'collab.notes.category': 'Категорія', - 'collab.notes.noCategory': 'Без категорії', - 'collab.notes.color': 'Колір', - 'collab.notes.save': 'Зберегти', - 'collab.notes.cancel': 'Скасувати', - 'collab.notes.edit': 'Редагувати', - 'collab.notes.delete': 'Видалити', - 'collab.notes.pin': 'Закріпити', - 'collab.notes.unpin': 'Відкріпити', - 'collab.notes.daysAgo': '{n} дн. тому', - 'collab.notes.categorySettings': 'Керування категоріями', - 'collab.notes.create': 'Створити', - 'collab.notes.website': 'Сайт', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': 'Прикріпити файли', - 'collab.notes.noCategoriesYet': 'Категорій поки що немає', - 'collab.notes.emptyDesc': 'Створіть нотатку, щоб почати', - 'collab.polls.title': 'Опитування', - 'collab.polls.new': 'Нове опитування', - 'collab.polls.empty': 'Опитувань поки що немає', - 'collab.polls.emptyHint': 'Поставте питання групі і голосуйте разом', - 'collab.polls.question': 'Питання', - 'collab.polls.questionPlaceholder': 'Що нам робити?', - 'collab.polls.addOption': '+ Додати варіант', - 'collab.polls.optionPlaceholder': 'Варіант {n}', - 'collab.polls.create': 'Створити опитування', - 'collab.polls.close': 'Закрити', - 'collab.polls.closed': 'Закрите', - 'collab.polls.votes': '{n} голосів', - 'collab.polls.vote': '{n} голос', - 'collab.polls.multipleChoice': 'Багатоваріантний вибір', - 'collab.polls.multiChoice': 'Багатоваріантний вибір', - 'collab.polls.deadline': 'Термін', - 'collab.polls.option': 'Варіант', - 'collab.polls.options': 'Варіанти', - 'collab.polls.delete': 'Видалити', - 'collab.polls.closedSection': 'Закриті', - - // Permissions - 'admin.tabs.permissions': 'Дозволи', - 'perm.title': 'Налаштування дозволів', - 'perm.subtitle': 'Керуйте тим, хто може виконувати дії в додатку', - 'perm.saved': 'Налаштування дозволів збережено', - 'perm.resetDefaults': 'Скинути за замовчуванням', - 'perm.customized': 'змінено', - 'perm.level.admin': 'Тільки адміністратор', - 'perm.level.tripOwner': 'Власник поїздки', - 'perm.level.tripMember': 'Учасники поїздки', - 'perm.level.everybody': 'Усі', - 'perm.cat.trip': 'Керування поїздками', - 'perm.cat.members': 'Керування учасниками', - 'perm.cat.files': 'Файли', - 'perm.cat.content': 'Контент та розклад', - 'perm.cat.extras': 'Бюджет, збори та співпраця', - 'perm.action.trip_create': 'Створювати поїздки', - 'perm.action.trip_edit': 'Редагувати деталі поїздки', - 'perm.action.trip_delete': 'Видаляти поїздки', - 'perm.action.trip_archive': 'Архівувати / розархівувати поїздки', - 'perm.action.trip_cover_upload': 'Завантажувати обкладинку', - 'perm.action.member_manage': 'Додавати / видаляти учасників', - 'perm.action.file_upload': 'Завантажувати файли', - 'perm.action.file_edit': 'Редагувати метадані файлів', - 'perm.action.file_delete': 'Видаляти файли', - 'perm.action.place_edit': 'Додавати / редагувати / видаляти місця', - 'perm.action.day_edit': 'Редагувати дні, нотатки та призначення', - 'perm.action.reservation_edit': 'Керувати бронюваннями', - 'perm.action.budget_edit': 'Керувати бюджетом', - 'perm.action.packing_edit': 'Керувати списками речей', - 'perm.action.collab_edit': 'Співпраця (нотатки, опитування, чат)', - 'perm.action.share_manage': 'Керувати посиланнями для обміну', - 'perm.actionHint.trip_create': 'Хто може створювати нові поїздки', - 'perm.actionHint.trip_edit': 'Хто може змінювати назву, дати, опис і валюту поїздки', - 'perm.actionHint.trip_delete': 'Хто може безповоротно видалити поїздку', - 'perm.actionHint.trip_archive': 'Хто може архівувати або розархівувати поїздку', - 'perm.actionHint.trip_cover_upload': 'Хто може завантажувати або змінювати обкладинку', - 'perm.actionHint.member_manage': 'Хто може запрошувати або видаляти учасників поїздки', - 'perm.actionHint.file_upload': 'Хто може завантажувати файли до поїздки', - 'perm.actionHint.file_edit': 'Хто може редагувати описи та посилання файлів', - 'perm.actionHint.file_delete': 'Хто може переміщувати файли до кошика або безповоротно видаляти', - 'perm.actionHint.place_edit': 'Хто може додавати, редагувати або видаляти місця', - 'perm.actionHint.day_edit': 'Хто може редагувати дні, нотатки до днів та призначення місць', - 'perm.actionHint.reservation_edit': 'Хто може створювати, редагувати або видаляти бронювання', - 'perm.actionHint.budget_edit': 'Хто може створювати, редагувати або видаляти статті бюджету', - 'perm.actionHint.packing_edit': 'Хто може керувати речами для зборів і сумками', - 'perm.actionHint.collab_edit': 'Хто може створювати нотатки, опитування та надсилати повідомлення', - 'perm.actionHint.share_manage': 'Хто може створювати або видаляти публічні посилання для обміну', - // Undo - 'undo.button': 'Відмінити', - 'undo.tooltip': 'Відмінити: {action}', - 'undo.assignPlace': 'Місце додано до дня', - 'undo.removeAssignment': 'Місце вилучено з дня', - 'undo.reorder': 'Місця переставлено', - 'undo.optimize': 'Маршрут оптимізовано', - 'undo.deletePlace': 'Місце видалено', - 'undo.deletePlaces': 'Місця видалено', - 'undo.moveDay': 'Місце переміщено в інший день', - 'undo.lock': 'Блокування місця змінено', - 'undo.importGpx': 'Імпорт GPX', - 'undo.importKeyholeMarkup': 'Імпорт KMZ/KML', - 'undo.importGoogleList': 'Імпорт з Google Maps', - 'undo.importNaverList': 'Імпорт з Naver Maps', - - // Notifications - 'notifications.title': 'Сповіщення', - 'notifications.markAllRead': 'Позначити всі прочитаними', - 'notifications.deleteAll': 'Видалити всі', - 'notifications.showAll': 'Показати всі сповіщення', - 'notifications.empty': 'Немає сповіщень', - 'notifications.emptyDescription': 'Ви в курсі всіх подій!', - 'notifications.all': 'Усі', - 'notifications.unreadOnly': 'Непрочитані', - 'notifications.markRead': 'Позначити як прочитане', - 'notifications.markUnread': 'Позначити як непрочитане', - 'notifications.delete': 'Видалити', - 'notifications.system': 'Система', - 'notifications.synologySessionCleared.title': 'Synology Photos відключено', - 'notifications.synologySessionCleared.text': 'Ваш сервер або акаунт змінено — перейдіть у Налаштування, щоб перевірити з’єднання знову.', - 'memories.error.loadAlbums': 'Не вдалося завантажити альбоми', - 'memories.error.linkAlbum': 'Не вдалося прив\'язати альбом', - 'memories.error.unlinkAlbum': 'Не вдалося від\'вязати альбом', - 'memories.error.syncAlbum': 'Не вдалося синхронізувати альбом', - 'memories.error.loadPhotos': 'Не вдалося завантажити фотографії', - 'memories.error.addPhotos': 'Не вдалося додати фотографії', - 'memories.error.removePhoto': 'Не вдалося видалити фотографію', - 'memories.error.toggleSharing': 'Не вдалося оновити налаштування доступу', - 'undo.addPlace': 'Місце додано', - 'undo.done': 'Відмінено: {action}', - 'notifications.test.title': 'Тестове сповіщення від {actor}', - 'notifications.test.text': 'Це просте тестове сповіщення.', - 'notifications.test.booleanTitle': '{actor} запрошує підтвердження', - 'notifications.test.booleanText': 'Тестове сповіщення з вибором.', - 'notifications.test.accept': 'Підтвердити', - 'notifications.test.decline': 'Відхилити', - 'notifications.test.navigateTitle': 'Подивіться на це', - 'notifications.test.navigateText': 'Тестове сповіщення з переходом.', - 'notifications.test.goThere': 'Перейти', - 'notifications.test.adminTitle': 'Розсилка адміністратора', - 'notifications.test.adminText': '{actor} надіслав тестове сповіщення всім адміністраторам.', - 'notifications.test.tripTitle': '{actor} написав у вашій поїздці', - 'notifications.test.tripText': 'Тестове сповіщення для поїздки "{trip}".', - - // Todo - 'todo.subtab.packing': 'Список речей', - 'todo.subtab.todo': 'Задачі', - 'todo.completed': 'виконано', - 'todo.filter.all': 'Усі', - 'todo.filter.open': 'Відкриття', - 'todo.filter.done': 'Виконані', - 'todo.uncategorized': 'Без категорії', - 'todo.namePlaceholder': 'Назва завдання', - 'todo.descriptionPlaceholder': 'Опис (необов’язково)', - 'todo.unassigned': 'Не призначено', - 'todo.noCategory': 'Без категорії', - 'todo.hasDescription': 'Є опис', - 'todo.addItem': 'Нова задача', - 'todo.sidebar.sortBy': 'Сорзувати за', - 'todo.priority': 'Пріоритет', - 'todo.newCategoryLabel': 'нова', - 'budget.categoriesLabel': 'категорії', - 'todo.newCategory': 'Назва категорії', - 'todo.addCategory': 'Додати категорію', - 'todo.newItem': 'Нова задача', - 'todo.empty': 'Задач поки немає. Додайте задачу, щоб почати!', - 'todo.filter.my': 'Мої задачі', - 'todo.filter.overdue': 'Просрочені', - 'todo.sidebar.tasks': 'Задачі', - 'todo.sidebar.categories': 'Категорії', - 'todo.detail.title': 'Задача', - 'todo.detail.description': 'Опис', - 'todo.detail.category': 'Категорія', - 'todo.detail.dueDate': 'Строк виконання', - 'todo.detail.assignedTo': 'Назначено', - 'todo.detail.delete': 'Видалити', - 'todo.detail.save': 'Зберегти зміни', - 'todo.detail.create': 'Створити задачу', - 'todo.detail.priority': 'Пріоритет', - 'todo.detail.noPriority': 'Немає', - 'todo.sortByPrio': 'Пріоритет', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': 'Доступна нова версія', - 'settings.notificationPreferences.noChannels': 'Канали сповіщень не налаштовані. Попросіть адміністратора налаштувати сповіщення електронною поштою або через webhook.', - 'settings.webhookUrl.label': 'URL вебхука', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': 'Введіть URL вашого вебхука Discord, Slack або власного для отримання сповіщень.', - 'settings.webhookUrl.saved': 'URL вебхука збережено', - 'settings.webhookUrl.test': 'Тест', - 'settings.webhookUrl.testSuccess': 'Тестовий вебхук успішно надіслано', - 'settings.webhookUrl.testFailed': 'Помилка тестового вебхука', - 'settings.ntfyUrl.topicLabel': 'Тема Ntfy', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'URL сервера Ntfy (необов’язково)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': 'Введіть тему Ntfy для отримання push-сповіщень. Залиште поле сервера пустим, щоб використовувати налаштування за замовчуванням, задані адміністратором.', - 'settings.ntfyUrl.tokenLabel': 'Токен доступу (необов’язково)', - 'settings.ntfyUrl.tokenHint': 'Потрібно для тем, захищених паролем.', - 'settings.ntfyUrl.saved': 'Налаштування Ntfy збережені', - 'settings.ntfyUrl.test': 'Тест', - 'settings.ntfyUrl.testSuccess': 'Тестове сповіщення Ntfy успішно надіслано', - 'settings.ntfyUrl.testFailed': 'Помилка надсилання тестового сповіщення Ntfy', - 'settings.ntfyUrl.tokenCleared': 'Токен доступу очищено', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': 'Сповіщення в додатку завжди активні і не можуть бути вимкнені глобально.', - 'admin.notifications.adminWebhookPanel.title': 'Webhook адміністратора', - 'admin.notifications.adminWebhookPanel.hint': 'Цей webhook використовується виключно для сповіщень адміністратора (наприклад, повідомлень про версії). Він незалежить від користувацьких вебхуків і надсилається автоматично при наявності URL.', - 'admin.notifications.adminWebhookPanel.saved': 'URL вебхука адміністратора збережено', - 'admin.notifications.adminWebhookPanel.testSuccess': 'Тестовий webhook успішно надіслано', - 'admin.notifications.adminWebhookPanel.testFailed': 'Помилка тестового вебхука', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Webhook адміністратора надсилається автоматично при наявності URL', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': 'Дозволяє користувачам налаштовувати власні теми Ntfy для push-сповіщень. Встановіть сервер за замовчуванням нижче, щоб попередньо заповнити налаштування користувачів.', - 'admin.notifications.testNtfy': 'Надіслати тестовий Ntfy', - 'admin.notifications.testNtfySuccess': 'Тестовий Ntfy успішно надіслано', - 'admin.notifications.testNtfyFailed': 'Помилка надсилання тестового Ntfy', - 'admin.notifications.adminNtfyPanel.title': 'Ntfy адміністратора', - 'admin.notifications.adminNtfyPanel.hint': 'Ця тема Ntfy використовується виключно для сповіщень адміністратора (наприклад, повідомлень про версії). Вона незалежна від тем користувачів і завжди надсилається при наявності налаштування.', - 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy', - 'admin.notifications.adminNtfyPanel.serverHint': 'Також використовується як сервер за замовчуванням для ntfy-сповіщень користувачів. Залиште пустим, щоб використовувати ntfy.sh. Користувачі можуть змінити це в своїх налаштуваннях.', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема адміністратора', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': 'Токен доступу (необов’язково)', - 'admin.notifications.adminNtfyPanel.tokenCleared': 'Токен доступу адміністратора очищено', - 'admin.notifications.adminNtfyPanel.saved': 'Налаштування Ntfy адміністратора збережено', - 'admin.notifications.adminNtfyPanel.test': 'Надіслати тестовий Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': 'Тестовий Ntfy успішно надіслано', - 'admin.notifications.adminNtfyPanel.testFailed': 'Помилка надсилання тестового Ntfy', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': 'Ntfy адміністратора завжди надсилається при наявності налаштованої теми', - 'admin.notifications.adminNotificationsHint': 'Налаштуйте, які канали доставляють сповіщення адміністратора (наприклад, повідомлення про версії). Вебхук надсилається автоматично, якщо задано URL вебхука адміністратора.', - 'admin.notifications.tripReminders.title': 'Нагадування про поїздки', - 'admin.notifications.tripReminders.hint': 'Надсилає нагадування перед початком поїздки (необхідно вказати дні нагадування в параметрах поїздки).', - 'admin.notifications.tripReminders.enabled': 'Нагадування про поїздки увімкнено', - 'admin.notifications.tripReminders.disabled': 'Нагадування про поїздки вимкнено', - 'admin.tabs.notifications': 'Сповіщення', - 'notifications.versionAvailable.title': 'Доступне оновлення', - 'notifications.versionAvailable.text': 'TREK {version} тепер доступний.', - 'notifications.versionAvailable.button': 'Докладніше', - 'notif.test.title': '[Тест] Сповіщення', - 'notif.test.simple.text': 'Це просте тестове сповіщення.', - 'notif.test.boolean.text': 'Ви приймаєте це тестове сповіщення?', - 'notif.test.navigate.text': 'Натисніть нижче, щоб перейти на панель управління.', - - // Notifications - 'notif.trip_invite.title': 'Запрошення до поїздки', - 'notif.trip_invite.text': '{actor} запросив вас до {trip}', - 'notif.booking_change.title': 'Бронювання оновлено', - 'notif.booking_change.text': '{actor} оновив бронювання в {trip}', - 'notif.trip_reminder.title': 'Нагадування про поїздку', - 'notif.trip_reminder.text': 'Ваша поїздка {trip} скоро почнеться!', - 'notif.todo_due.title': 'Завдання до терміну', - 'notif.todo_due.text': '{todo} у {trip} — термін {due}', - 'notif.vacay_invite.title': 'Запрошення Vacay Fusion', - 'notif.vacay_invite.text': '{actor} запрошує вас об’єднати плани відпустки', - 'notif.photos_shared.title': 'Фото опубліковано', - 'notif.photos_shared.text': '{actor} поділився {count} фото у {trip}', - 'notif.collab_message.title': 'Нове повідомлення', - 'notif.collab_message.text': '{actor} надіслав повідомлення в {trip}', - 'notif.packing_tagged.title': 'Завдання для пакування', - 'notif.packing_tagged.text': '{actor} призначив вас у {category} у {trip}', - 'notif.version_available.title': 'Доступна нова версія', - 'notif.version_available.text': 'TREK {version} тепер доступний', - 'notif.action.view_trip': 'Відкрити поїздку', - 'notif.action.view_collab': 'Відкрити повідомлення', - 'notif.action.view_packing': 'Відкрити пакування', - 'notif.action.view_photos': 'Відкрити фото', - 'notif.action.view_vacay': 'Відкрити Vacay', - 'notif.action.view_admin': 'Перейти в адмін', - 'notif.action.view': 'Відкрити', - 'notif.action.accept': 'Прийняти', - 'notif.action.decline': 'Відхилити', - 'notif.generic.title': 'Сповіщення', - 'notif.generic.text': 'У вас нове сповіщення', - 'notif.dev.unknown_event.title': '[DEV] Невідома подія', - 'notif.dev.unknown_event.text': 'Тип події "{event}" не зареєстровано в EVENT_NOTIFICATION_CONFIG', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': 'щойно', - 'common.hoursAgo': '{count} год. тому', - 'common.daysAgo': '{count} дн. тому', - 'memories.saveRouteNotConfigured': 'Маршрут збереження не налаштовано для цього провайдера', - 'memories.testRouteNotConfigured': 'Маршрут тестування не налаштовано для цього провайдера', - 'memories.fillRequiredFields': 'Будь ласка, заповніть усі обов’язкові поля', - 'journey.search.placeholder': 'Пошук подорожей…', - 'journey.search.noResults': 'Подорожей за запитом «{query}» не знайдено', - 'journey.title': 'Journey', - 'journey.subtitle': 'Відстежуйте свої подорожі в реальному часі', - 'journey.new': 'Нова подорож', - 'journey.create': 'Створити', - 'journey.titlePlaceholder': 'Куди ви їдете?', - 'journey.empty': 'Поки що немає подорожей', - 'journey.emptyHint': 'Почніть документувати свою наступну поїздку', - 'journey.deleted': 'Подорож видалено', - 'journey.createError': 'Не вдалося створити подорож', - 'journey.deleteError': 'Не вдалося видалити подорож', - 'journey.deleteConfirmTitle': 'Видалити', - 'journey.deleteConfirmMessage': 'Видалити «{title}»? Цю дію не можна скасувати.', - 'journey.deleteConfirmGeneric': 'Ви впевнені, що хочете це видалити?', - 'journey.notFound': 'Подорож не знайдено', - 'journey.photos': 'Фото', - 'journey.timelineEmpty': 'Поки що немає зупинок', - 'journey.timelineEmptyHint': 'Додайте позначку або напишіть запис у щоденник', - 'journey.status.draft': 'Чернетка', - 'journey.status.active': 'Активно', - 'journey.status.completed': 'Завершено', - 'journey.status.upcoming': 'Найближчим часом', - 'journey.status.archived': 'В архіві', - 'journey.checkin.add': 'Відзначитись', - 'journey.checkin.namePlaceholder': 'Назва місця', - 'journey.checkin.notesPlaceholder': 'Нотатки (необов’язково)', - 'journey.checkin.save': 'Зберегти', - 'journey.checkin.error': 'Не вдалося зберегти позначку', - 'journey.entry.add': 'Щоденник', - 'journey.entry.edit': 'Редагувати запис', - 'journey.entry.titlePlaceholder': 'Заголовок (необов’язково)', - 'journey.entry.bodyPlaceholder': 'Що сталося сьогодні?', - 'journey.entry.save': 'Зберегти', - 'journey.entry.error': 'Не вдалося зберегти запис', - 'journey.photo.add': 'Фото', - 'journey.photo.uploadError': 'Завантаження не вдалося', - 'journey.share.share': 'Поділитися', - 'journey.share.public': 'Публічний', - 'journey.share.linkCopied': 'Публічне посилання скопійовано', - 'journey.share.disabled': 'Публічний доступ вимкнено', - 'journey.editor.titlePlaceholder': 'Дайте назву цьому моменту...', - 'journey.editor.bodyPlaceholder': 'Розкажіть історію цього дня...', - 'journey.editor.placePlaceholder': 'Місце розташування (необов’язково)', - 'journey.editor.tagsPlaceholder': 'Теги: прихована перлина, найкраща їжа, варто повернутися...', - 'journey.visibility.private': 'Приватний', - 'journey.visibility.shared': 'Спільний', - 'journey.visibility.public': 'Публічний', - 'journey.emptyState.title': 'Ваша історія починається тут', - 'journey.emptyState.subtitle': 'Позначтесь у місці або напишіть перший запис у щоденник', - 'journey.frontpage.subtitle': 'Перетворюйте поїздки на історії, які ви ніколи не забудете', - 'journey.frontpage.createJourney': 'Створити подорож', - 'journey.frontpage.activeJourney': 'Активна подорож', - 'journey.frontpage.allJourneys': 'Усі подорожі', - 'journey.frontpage.journeys': 'подорожей', - 'journey.frontpage.createNew': 'Створити нову подорож', - 'journey.frontpage.createNewSub': 'Оберіть поїздки, пишіть історії, діліться пригодами', - 'journey.frontpage.live': 'В ефірі', - 'journey.frontpage.synced': 'Синхронізовано', - 'journey.frontpage.continueWriting': 'Продовжити писати', - 'journey.frontpage.updated': 'Оновлено {time}', - 'journey.frontpage.suggestionLabel': 'Поїздка щойно завершилась', - 'journey.frontpage.suggestionText': 'Перетворіть {title} на подорож', - 'journey.frontpage.dismiss': 'Приховати', - 'journey.frontpage.journeyName': 'Назва подорожі', - 'journey.frontpage.namePlaceholder': 'наприклад, Південно-Східна Азія 2026', - 'journey.frontpage.selectTrips': 'Оберіть поїздки', - 'journey.frontpage.tripsSelected': 'поїздок вибрано', - 'journey.frontpage.trips': 'поїздок', - 'journey.frontpage.placesImported': 'місць буде імпортовано', - 'journey.frontpage.places': 'місць', - 'journey.detail.backToJourney': 'Назад до подорожі', - 'journey.detail.syncedWithTrips': 'Синхронізовано з поїздками', - 'journey.detail.addEntry': 'Додати запис', - 'journey.detail.newEntry': 'Новий запис', - 'journey.detail.editEntry': 'Редагувати запис', - 'journey.detail.noEntries': 'Поки що немає записів', - 'journey.detail.noEntriesHint': 'Додайте подорож, щоб почати зі шаблонних записів', - 'journey.detail.noPhotos': 'Поки що немає фото', - 'journey.detail.noPhotosHint': 'Завантажте фото до записів або перегляньте бібліотеку Immich/Synology', - 'journey.detail.journeyStats': 'Статистика подорожі', - 'journey.detail.syncedTrips': 'Синхронізовані поїздки', - 'journey.detail.noTripsLinked': 'Поки що немає прив’язаних поїздок', - 'journey.detail.contributors': 'Учасники', - 'journey.detail.journeyTab': 'Journey', - 'journey.detail.readMore': 'Читати далі', - 'journey.detail.prosCons': 'Плюси й мінуси', - 'journey.detail.photos': 'фото', - 'journey.detail.day': 'День {number}', - 'journey.detail.places': 'місць', - 'journey.stats.days': 'Днів', - 'journey.stats.cities': 'Міст', - 'journey.stats.entries': 'Записів', - 'journey.stats.photos': 'Фото', - 'journey.stats.places': 'Місць', - 'journey.skeletons.show': 'Показати пропозиції', - 'journey.skeletons.hide': 'Приховати пропозиції', - 'journey.verdict.lovedIt': 'Сподобалось', - 'journey.verdict.couldBeBetter': 'Могло бути краще', - 'journey.synced.places': 'місць', - 'journey.synced.synced': 'синхронізовано', - 'journey.editor.discardChangesConfirm': 'У вас є незбережені зміни. Відмінити?', - 'journey.editor.uploadPhotos': 'Завантажити фото', - 'journey.editor.uploading': 'Завантаження...', - 'journey.editor.fromGallery': 'Зі галереї', - 'journey.editor.allPhotosAdded': 'Усі фото вже додано', - 'journey.editor.writeStory': 'Напишіть свою історію...', - 'journey.editor.prosCons': 'Плюси й мінуси', - 'journey.editor.pros': 'Плюси', - 'journey.editor.cons': 'Мінуси', - 'journey.editor.proPlaceholder': 'Щось чудове...', - 'journey.editor.conPlaceholder': 'Не дуже добре...', - 'journey.editor.addAnother': 'Додати ще', - 'journey.editor.date': 'Дата', - 'journey.editor.location': 'Місце розташування', - 'journey.editor.searchLocation': 'Пошук місця розташування...', - 'journey.editor.mood': 'Настрій', - 'journey.editor.weather': 'Погода', - 'journey.editor.photoFirst': '1-е', - 'journey.editor.makeFirst': 'Зробити першим', - 'journey.editor.searching': 'Пошук...', - 'journey.mood.amazing': 'Неймовірно', - 'journey.mood.good': 'Добре', - 'journey.mood.neutral': 'Нейтрально', - 'journey.mood.rough': 'Важко', - 'journey.weather.sunny': 'Сонячно', - 'journey.weather.partly': 'Перемінна хмарність', - 'journey.weather.cloudy': 'Хмарно', - 'journey.weather.rainy': 'Дощ', - 'journey.weather.stormy': 'Гроза', - 'journey.weather.cold': 'Сніжно', - 'journey.trips.linkTrip': 'Прив’язати поїздку', - 'journey.trips.searchTrip': 'Пошук поїздки', - 'journey.trips.searchPlaceholder': 'Назва поїздки або напрям...', - 'journey.trips.noTripsAvailable': 'Немає доступних поїздок', - 'journey.trips.link': 'Прив’язати', - 'journey.trips.tripLinked': 'Поїздка прив’язана', - 'journey.trips.linkFailed': 'Не вдалося прив’язати поїздку', - 'journey.trips.addTrip': 'Додати поїздку', - 'journey.trips.unlinkTrip': 'Відв’язати поїздку', - 'journey.trips.unlinkMessage': 'Відв’язати «{title}»? Усі синхронізовані записи та фото з цієї поїздки будуть безповоротно видалені. Цю дію не можна скасувати.', - 'journey.trips.unlink': 'Відв’язати', - 'journey.trips.tripUnlinked': 'Поїздка відв’язана', - 'journey.trips.unlinkFailed': 'Не вдалося відв’язати поїздку', - 'journey.trips.noTripsLinkedSettings': 'Немає прив’язаних поїздок', - 'journey.contributors.invite': 'Запросити учасника', - 'journey.contributors.searchUser': 'Пошук користувача', - 'journey.contributors.searchPlaceholder': 'Ім’я користувача або email...', - 'journey.contributors.noUsers': 'Користувачів не знайдено', - 'journey.contributors.role': 'Роль', - 'journey.contributors.added': 'Учасника додано', - 'journey.contributors.addFailed': 'Не вдалося додати учасника', - 'journey.contributors.remove': 'Вилучити учасника', - 'journey.contributors.removeConfirm': 'Вилучити {username} з цієї подорожі?', - 'journey.contributors.removeFailed': 'Не вдалося вилучити учасника', - 'journey.contributors.removed': 'Учасника вилучено', - 'journey.share.publicShare': 'Публічний доступ', - 'journey.share.createLink': 'Створити посилання для спільного доступу', - 'journey.share.linkCreated': 'Посилання створено', - 'journey.share.createFailed': 'Не вдалося створити посилання', - 'journey.share.copy': 'Копіювати', - 'journey.share.copied': 'Скопійовано!', - 'journey.share.timeline': 'Хронологія', - 'journey.share.gallery': 'Галерея', - 'journey.share.map': 'Карта', - 'journey.share.removeLink': 'Видалити посилання', - 'journey.share.linkDeleted': 'Посилання видалено', - 'journey.share.deleteFailed': 'Не вдалося видалити', - 'journey.share.updateFailed': 'Не вдалося оновити', - - // Journey — Invite - 'journey.invite.role': 'Роль', - 'journey.invite.viewer': 'Спостерігач', - 'journey.invite.editor': 'Редактор', - 'journey.invite.invite': 'Запросити', - 'journey.invite.inviting': 'Запрошуємо...', - 'journey.settings.title': 'Налаштування подорожі', - 'journey.settings.coverImage': 'Обкладинка', - 'journey.settings.changeCover': 'Змінити обкладинку', - 'journey.settings.addCover': 'Додати обкладинку', - 'journey.settings.name': 'Назва', - 'journey.settings.subtitle': 'Підзаголовок', - 'journey.settings.subtitlePlaceholder': 'наприклад Таїланд, В’єтнам і Камбоджа', - 'journey.settings.endJourney': 'Архівувати подорож', - 'journey.settings.reopenJourney': 'Відновити подорож', - 'journey.settings.archived': 'Подорож архівовано', - 'journey.settings.reopened': 'Подорож відновлено', - 'journey.settings.endDescription': 'Приховує значок «В ефірі». Ви можете відновити у будь-який час.', - 'journey.settings.delete': 'Видалити', - 'journey.settings.deleteJourney': 'Видалити подорож', - 'journey.settings.deleteMessage': 'Видалити «{title}»? Усі записи та фото будуть втрачені.', - 'journey.settings.saved': 'Налаштування збережено', - 'journey.settings.saveFailed': 'Не вдалося зберегти', - 'journey.settings.coverUpdated': 'Обкладинка оновлено', - 'journey.settings.coverFailed': 'Завантаження не вдалося', - 'journey.settings.failedToDelete': 'Не вдалося видалити', - 'journey.entries.deleteTitle': 'Видалити запис', - 'journey.photosUploaded': '{count} фото завантажено', - 'journey.photosAdded': '{count} фото додано', - 'journey.public.notFound': 'Не знайдено', - 'journey.public.notFoundMessage': 'Ця подорож не існує або посилання застаріло.', - 'journey.public.readOnly': 'Тільки для читання · Публічна подорож', - 'journey.public.tagline': 'Інструмент планування та дослідження подорожей', - 'journey.public.sharedVia': 'Опубліковано через', - 'journey.public.madeWith': 'Зроблено з допомогою', - 'journey.pdf.journeyBook': 'Книга подорожі', - 'journey.pdf.madeWith': 'Зроблено з допомогою TREK', - 'journey.pdf.day': 'День', - 'journey.pdf.theEnd': 'Кінець', - 'journey.pdf.saveAsPdf': 'Зберегти як PDF', - 'journey.pdf.pages': 'сторінок', - 'journey.picker.tripPeriod': 'Період подорожі', - 'journey.picker.dateRange': 'Діапазон дат', - 'journey.picker.allPhotos': 'Усі фото', - 'journey.picker.albums': 'Альбоми', - 'journey.picker.selected': 'вибрано', - 'journey.picker.addTo': 'Додати до', - 'journey.picker.newGallery': 'Нова галерея', - 'journey.picker.selectAll': 'Вибрати все', - 'journey.picker.deselectAll': 'Скасувати вибір', - 'journey.picker.noAlbums': 'Альбомів не знайдено', - 'journey.picker.selectDate': 'Оберіть дату', - 'journey.picker.search': 'Пошук', - 'dashboard.greeting.morning': 'Доброго ранку,', - 'dashboard.greeting.afternoon': 'Доброго дня,', - 'dashboard.greeting.evening': 'Доброго вечора,', - 'dashboard.mobile.liveNow': 'Зараз у дорозі', - 'dashboard.mobile.tripProgress': 'Прогрес поїздки', - 'dashboard.mobile.daysLeft': 'залишилось {count} дн.', - 'dashboard.mobile.places': 'Місця', - 'dashboard.mobile.buddies': 'Учасники', - 'dashboard.mobile.newTrip': 'Нова поїздка', - 'dashboard.mobile.currency': 'Валюта', - 'dashboard.mobile.timezone': 'Часовий пояс', - 'dashboard.mobile.upcomingTrips': 'Найближчі поїздки', - 'dashboard.mobile.yourTrips': 'Ваші поїздки', - 'dashboard.mobile.trips': 'поїздок', - 'dashboard.mobile.starts': 'Початок', - 'dashboard.mobile.duration': 'Тривалість', - 'dashboard.mobile.day': 'день', - 'dashboard.mobile.days': 'днів', - 'dashboard.mobile.ongoing': 'Триває', - 'dashboard.mobile.startsToday': 'Починається сьогодні', - 'dashboard.mobile.tomorrow': 'Завтра', - 'dashboard.mobile.inDays': 'Через {count} дн.', - 'dashboard.mobile.inMonths': 'Через {count} міс.', - 'dashboard.mobile.completed': 'Завершено', - 'dashboard.mobile.currencyConverter': 'Конвертер валют', - 'nav.profile': 'Профіль', - 'nav.bottomSettings': 'Налаштування', - 'nav.bottomAdmin': 'Адміністрування', - 'nav.bottomLogout': 'Вийти', - 'nav.bottomAdminBadge': 'Адмін', - 'dayplan.mobile.addPlace': 'Додати місце', - 'dayplan.mobile.searchPlaces': 'Пошук місць...', - 'dayplan.mobile.allAssigned': 'Усі місця розподілені', - 'dayplan.mobile.noMatch': 'Немає збігів', - 'dayplan.mobile.createNew': 'Створити нове місце', - 'admin.addons.catalog.journey.name': 'Journey', - 'admin.addons.catalog.journey.description': 'Відстеження поїздок і щоденник подорожей з позначками, фото та щоденними історіями', - - // OAuth scope groups - 'oauth.scope.group.trips': 'Поїздки', - 'oauth.scope.group.places': 'Місця', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': 'Речі', - 'oauth.scope.group.todos': 'Задачі', - 'oauth.scope.group.budget': 'Бюджет', - 'oauth.scope.group.reservations': 'Бронювання', - 'oauth.scope.group.collab': 'Співпраця', - 'oauth.scope.group.notifications': 'Сповіщення', - 'oauth.scope.group.vacay': 'Відпустка', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': 'Погода', - 'oauth.scope.group.journey': 'Подорожі', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': 'Перегляд поїздок і маршрутів', - 'oauth.scope.trips:read.description': 'Читання поїздок, днів, нотаток і учасників', - 'oauth.scope.trips:write.label': 'Редагування поїздок і маршрутів', - 'oauth.scope.trips:write.description': 'Створення та оновлення поїздок, днів, нотаток і керування учасниками', - 'oauth.scope.trips:delete.label': 'Видалення поїздок', - 'oauth.scope.trips:delete.description': 'Безповоротне видалення поїздок — ця дія незворотна', - 'oauth.scope.trips:share.label': 'Керування посиланнями спільного доступу', - 'oauth.scope.trips:share.description': 'Створення, оновлення та відкликання публічних посилань на поїздки', - 'oauth.scope.places:read.label': 'Перегляд місць і даних карти', - 'oauth.scope.places:read.description': 'Читання місць, призначень за днями, тегів і категорій', - 'oauth.scope.places:write.label': 'Керування місцями', - 'oauth.scope.places:write.description': 'Створення, оновлення та видалення місць, призначень і тегів', - 'oauth.scope.atlas:read.label': 'Перегляд Atlas', - 'oauth.scope.atlas:read.description': 'Читання відвіданих країн, регіонів і списку бажань', - 'oauth.scope.atlas:write.label': 'Керування Atlas', - 'oauth.scope.atlas:write.description': 'Позначення відвіданих країн і регіонів, керування списком бажань', - 'oauth.scope.packing:read.label': 'Перегляд списків речей', - 'oauth.scope.packing:read.description': 'Читання речей, сумок і призначень категорій', - 'oauth.scope.packing:write.label': 'Керування списками речей', - 'oauth.scope.packing:write.description': 'Додавання, оновлення, видалення, позначення і переставлення речей та сумок', - 'oauth.scope.todos:read.label': 'Перегляд списків задач', - 'oauth.scope.todos:read.description': 'Читання задач поїздки і призначень категорій', - 'oauth.scope.todos:write.label': 'Керування списками задач', - 'oauth.scope.todos:write.description': 'Створення, оновлення, позначення, видалення і переставлення задач', - 'oauth.scope.budget:read.label': 'Перегляд бюджету', - 'oauth.scope.budget:read.description': 'Читання статей бюджету і розбивки витрат', - 'oauth.scope.budget:write.label': 'Керування бюджетом', - 'oauth.scope.budget:write.description': 'Створення, оновлення і видалення статей бюджету', - 'oauth.scope.reservations:read.label': 'Перегляд бронювань', - 'oauth.scope.reservations:read.description': 'Читання бронювань і відомостей про проживання', - 'oauth.scope.reservations:write.label': 'Керування бронюваннями', - 'oauth.scope.reservations:write.description': 'Створення, оновлення, видалення і переставлення бронювань', - 'oauth.scope.collab:read.label': 'Перегляд співпраці', - 'oauth.scope.collab:read.description': 'Читання спільних нотаток, опитувань і повідомлень', - 'oauth.scope.collab:write.label': 'Керування співпрацею', - 'oauth.scope.collab:write.description': 'Створення, оновлення і видалення нотаток, опитувань і повідомлень', - 'oauth.scope.notifications:read.label': 'Перегляд сповіщень', - 'oauth.scope.notifications:read.description': 'Читання сповіщень у додатку та кількості непрочитаних', - 'oauth.scope.notifications:write.label': 'Керування сповіщеннями', - 'oauth.scope.notifications:write.description': 'Позначення сповіщень як прочитаних і відповіді на них', - 'oauth.scope.vacay:read.label': 'Перегляд планів відпустки', - 'oauth.scope.vacay:read.description': 'Читання даних планування відпустки, записів і статистики', - 'oauth.scope.vacay:write.label': 'Керування планами відпустки', - 'oauth.scope.vacay:write.description': 'Створення і керування записами відпустки, святами і командними планами', - 'oauth.scope.geo:read.label': 'Карти і геокодування', - 'oauth.scope.geo:read.description': 'Пошук місць, розв’язання URL карт і зворотне геокодування координат', - 'oauth.scope.weather:read.label': 'Прогнози погоди', - 'oauth.scope.weather:read.description': 'Отримання прогнозів погоди для місць і дат поїздки', - 'oauth.scope.journey:read.label': 'Перегляд подорожей', - 'oauth.scope.journey:read.description': 'Читання подорожей, записів і списку учасників', - 'oauth.scope.journey:write.label': 'Керування подорожами', - 'oauth.scope.journey:write.description': 'Створення, оновлення і видалення подорожей та їх записів', - 'oauth.scope.journey:share.label': 'Керування посиланнями на подорожі', - 'oauth.scope.journey:share.description': 'Створення, оновлення і відкликання публічних посилань на подорожі', - - // System notices - 'system_notice.welcome_v1.title': 'Ласкаво просимо в TREK', - 'system_notice.welcome_v1.body': 'Ваш універсальний планувальник подорожей. Створюйте маршрути, діліться поїздками з друзями та залишайтесь організованими — онлайн та офлайн.', - 'system_notice.welcome_v1.cta_label': 'Спланувати поїздку', - 'system_notice.welcome_v1.hero_alt': 'Живописне місце призначення з інтерфейсом TREK', - 'system_notice.welcome_v1.highlight_plan': 'Детальні плани по днях для будь-яких поїздок', - 'system_notice.welcome_v1.highlight_share': 'Спільне планування', - 'system_notice.welcome_v1.highlight_offline': 'Працює офлайн на мобільному', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': 'Попереднє повідомлення', - 'system_notice.pager.next': 'Наступне повідомлення', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': 'Перейти до повідомлення {n}', - 'system_notice.pager.position': 'Повідомлення {current} із {total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': 'Фото переміщено у версії 3.0', - 'system_notice.v3_photos.body': 'Вкладку **Фото** в Планувальнику подорожей видалено. Ваші фото в безпеці — TREK ніколи не змінював вашу бібліотеку Immich або Synology.\n\nФото тепер доступні у доповненні **Journey**. Journey необов’язковий — якщо він ще недоступний, попросіть адміністратора включити його в розділі Адмін → Додатки.', - 'system_notice.v3_journey.title': 'Знайомтесь із Journey', - 'system_notice.v3_journey.body': 'Документуйте подорожі як історії з хронологіями, фотогалереями та інтерактивними картами.', - 'system_notice.v3_journey.cta_label': 'Відкрити Journey', - 'system_notice.v3_journey.highlight_timeline': 'Щоденна хронологія та галерея', - 'system_notice.v3_journey.highlight_photos': 'Імпорт з Immich або Synology', - 'system_notice.v3_journey.highlight_share': 'Спільний доступ — без входу', - 'system_notice.v3_journey.highlight_export': 'Експорт у PDF-фотокнигу', - 'system_notice.v3_features.title': 'Ще більше нового у версії 3.0', - 'system_notice.v3_features.body': 'Декілька інших важливих нововведень у цьому релізі.', - 'system_notice.v3_features.highlight_dashboard': 'Перероблена панель у mobile-first стилі', - 'system_notice.v3_features.highlight_offline': 'Повний офлайн-режим як PWA', - 'system_notice.v3_features.highlight_search': 'Автодоповнення пошуку місць у реальному часі', - 'system_notice.v3_features.highlight_import': 'Імпорт місць з KMZ/KML-файлів', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP: оновлення OAuth 2.1', - 'system_notice.v3_mcp.body': 'Інтеграція MCP була повністю перероблена. OAuth 2.1 тепер є рекомендованим методом автентифікації. Статичні токени (trek_…) застаріли і будуть видалені в майбутній версії.', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 рекомендовано (mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 детальні області дозволів', - 'system_notice.v3_mcp.highlight_deprecated': 'Статичні токени trek_ застаріли', - 'system_notice.v3_mcp.highlight_tools': 'Розширений набір інструментів', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': 'Особливе слово від мене', - 'system_notice.v3_thankyou.body': 'Перш ніж продовжити — хочу зупинитися на мить.\n\nTREK починався як сторонній проєкт, який я створив для власних поїздок. Я ніколи не думав, що він виросте в щось, чому 4 000 з вас довіряють планування своїх пригод. Кожна зірочка, кожен issue, кожен запит на фічу — я читаю їх усі, і саме вони підтримують мене у пізні ночі між основною роботою та університетом.\n\nХочу, щоб ви знали: TREK завжди буде open source, завжди self-hosted, завжди вашим. Жодного стеження, жодних підписок, жодних підводних каменів. Просто інструмент, створений людиною, яка любить подорожувати так само, як і ви.\n\nОсоблива подяка [jubnl](https://github.com/jubnl) — ти став неймовірним соратником. Багато з того, що робить версію 3.0 чудовою, несе твій відбиток. Дякую, що повірив у цей проєкт, коли він ще був сирим.\n\nІ кожному з вас, хто повідомив про помилку, переклав рядок, поділився TREK з другом або просто використовував його для планування поїздки — **дякую**. Ви — причина, чому все це існує.\n\nЗа багато нових пригод разом.\n\n— Maurice\n\n---\n\n[Приєднуйтесь до спільноти в Discord](https://discord.gg/7Q6M6jDwzf)\n\nЯкщо TREK робить ваші подорожі кращими, [невелика кава](https://ko-fi.com/mauriceboe) завжди допомагає тримати світло ввімкненим.', - 'transport.addTransport': 'Додати транспорт', - 'transport.modalTitle.create': 'Додати транспорт', - 'transport.modalTitle.edit': 'Змінити транспорт', - 'transport.title': 'Транспорт', - 'transport.addManual': 'Ручний транспорт', - - // Added to match EN keys - 'journey.editor.uploadingProgress': 'Завантаження {done}/{total}…', - 'journey.editor.uploadFailed': 'Не вдалося завантажити фото', - 'journey.editor.uploadPartialFailed': '{failed} з {total} фото не вдалося завантажити — збережіть ще раз, щоб повторити', - 'journey.photosUploadFailed': 'Деякі фото не вдалося завантажити', - 'settings.oauth.modal.machineClient': 'Машинний клієнт (без входу через браузер)', - 'settings.oauth.modal.machineClientHint': 'Використовуйте надання client_credentials — URI перенаправлення не потрібні. Токен видається безпосередньо через client_id + client_secret і діє від вашого імені в межах вибраних областей.', - 'settings.oauth.modal.machineClientUsage': 'Отримати токен: POST /oauth/token з grant_type=client_credentials, client_id і client_secret. Без браузера, без токена оновлення.', - 'settings.oauth.badge.machine': 'машина', - 'files.assignTransport': 'Транспорт', - 'files.sourceTransport': 'Транспорт', - 'system_notice.v3014_whitespace_collision.title': 'Потрібна дія: конфлікт облікового запису користувача', - 'system_notice.v3014_whitespace_collision.body': 'Оновлення 3.0.14 виявило один або кілька конфліктів імен користувачів або електронних адрес, спричинених початковими або кінцевими пробілами в збережених облікових записах. Уражені облікові записи було автоматично перейменовано. Перевірте журнали сервера на рядки, що починаються з **[migration] WHITESPACE COLLISION**, щоб визначити, які облікові записи потребують перевірки.', -} - -export default uk - diff --git a/client/src/i18n/translations/zh.ts b/client/src/i18n/translations/zh.ts deleted file mode 100644 index 20b0e80f..00000000 --- a/client/src/i18n/translations/zh.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const zh: Record = { - // Common - 'common.save': '保存', - 'common.showMore': '显示更多', - 'common.showLess': '收起', - 'common.cancel': '取消', - 'common.clear': '清除', - 'common.delete': '删除', - 'common.edit': '编辑', - 'common.add': '添加', - 'common.loading': '加载中...', - 'common.import': '导入', - 'common.select': '选择', - 'common.selectAll': '全选', - 'common.deselectAll': '取消全选', - 'common.error': '错误', - 'common.unknownError': '未知错误', - 'common.tooManyAttempts': '尝试次数过多,请稍后再试。', - 'common.back': '返回', - 'common.all': '全部', - 'common.close': '关闭', - 'common.open': '打开', - 'common.upload': '上传', - 'common.search': '搜索', - 'common.confirm': '确认', - 'common.ok': '确定', - 'common.yes': '是', - 'common.no': '否', - 'common.or': '或', - 'common.none': '无', - 'common.date': '日期', - 'common.rename': '重命名', - 'common.discardChanges': '放弃更改', - 'common.discard': '放弃', - 'common.name': '名称', - 'common.email': '邮箱', - 'common.password': '密码', - 'common.saving': '保存中...', - 'common.saved': '已保存', - 'common.expand': '展开', - 'common.collapse': '折叠', - 'trips.memberRemoved': '{username} 已移除', - 'trips.memberRemoveError': '移除失败', - 'trips.memberAdded': '{username} 已添加', - 'trips.memberAddError': '添加失败', - 'trips.reminder': '提醒', - 'trips.reminderNone': '无', - 'trips.reminderDay': '天', - 'trips.reminderDays': '天', - 'trips.reminderCustom': '自定义', - 'trips.reminderDaysBefore': '天前提醒', - 'trips.reminderDisabledHint': '旅行提醒已禁用。请在管理 > 设置 > 通知中启用。', - 'common.update': '更新', - 'common.change': '修改', - 'common.uploading': '上传中…', - 'common.backToPlanning': '返回规划', - 'common.reset': '重置', - - // Navbar - 'nav.trip': '旅行', - 'nav.share': '分享', - 'nav.settings': '设置', - 'nav.admin': '管理', - 'nav.logout': '退出登录', - 'nav.lightMode': '浅色模式', - 'nav.darkMode': '深色模式', - 'nav.autoMode': '自动模式', - 'nav.administrator': '管理员', - - // Dashboard - 'dashboard.title': '我的旅行', - 'dashboard.subtitle.loading': '加载旅行中...', - 'dashboard.subtitle.trips': '{count} 次旅行({archived} 已归档)', - 'dashboard.subtitle.empty': '开始你的第一次旅行', - 'dashboard.subtitle.activeOne': '{count} 个进行中的旅行', - 'dashboard.subtitle.activeMany': '{count} 个进行中的旅行', - 'dashboard.subtitle.archivedSuffix': ' · {count} 已归档', - 'dashboard.newTrip': '新建旅行', - 'dashboard.gridView': '网格视图', - 'dashboard.listView': '列表视图', - 'dashboard.currency': '货币', - 'dashboard.timezone': '时区', - 'dashboard.localTime': '本地', - 'dashboard.timezoneCustomTitle': '自定义时区', - 'dashboard.timezoneCustomLabelPlaceholder': '标签(可选)', - 'dashboard.timezoneCustomTzPlaceholder': '如 America/New_York', - 'dashboard.timezoneCustomAdd': '添加', - 'dashboard.timezoneCustomErrorEmpty': '请输入时区标识符', - 'dashboard.timezoneCustomErrorInvalid': '无效的时区。请使用 Europe/Berlin 这样的格式', - 'dashboard.timezoneCustomErrorDuplicate': '已添加', - 'dashboard.emptyTitle': '暂无旅行', - 'dashboard.emptyText': '创建你的第一次旅行,开始规划吧!', - 'dashboard.emptyButton': '创建第一次旅行', - 'dashboard.nextTrip': '下次旅行', - 'dashboard.shared': '共享', - 'dashboard.sharedBy': '由 {name} 分享', - 'dashboard.days': '天', - 'dashboard.places': '地点', - 'dashboard.members': '旅伴', - 'dashboard.archive': '归档', - 'dashboard.copyTrip': '复制', - 'dashboard.copySuffix': '副本', - 'dashboard.restore': '恢复', - 'dashboard.archived': '已归档', - 'dashboard.status.ongoing': '进行中', - 'dashboard.status.today': '今天', - 'dashboard.status.tomorrow': '明天', - 'dashboard.status.past': '已结束', - 'dashboard.status.daysLeft': '还剩 {count} 天', - 'dashboard.toast.loadError': '加载旅行失败', - 'dashboard.toast.created': '旅行创建成功!', - 'dashboard.toast.createError': '创建旅行失败', - 'dashboard.toast.updated': '旅行已更新!', - 'dashboard.toast.updateError': '更新旅行失败', - 'dashboard.toast.deleted': '旅行已删除', - 'dashboard.toast.deleteError': '删除旅行失败', - 'dashboard.toast.archived': '旅行已归档', - 'dashboard.toast.archiveError': '归档旅行失败', - 'dashboard.toast.restored': '旅行已恢复', - 'dashboard.toast.restoreError': '恢复旅行失败', - 'dashboard.toast.copied': '旅行已复制!', - 'dashboard.toast.copyError': '复制旅行失败', - 'dashboard.confirm.delete': '删除旅行「{title}」?所有地点和计划将被永久删除。', - 'dashboard.editTrip': '编辑旅行', - 'dashboard.createTrip': '创建新旅行', - 'dashboard.tripTitle': '标题', - 'dashboard.tripTitlePlaceholder': '如:日本夏日之旅', - 'dashboard.tripDescription': '描述', - 'dashboard.tripDescriptionPlaceholder': '这次旅行是关于什么的?', - 'dashboard.startDate': '开始日期', - 'dashboard.endDate': '结束日期', - 'dashboard.dayCount': '天数', - 'dashboard.dayCountHint': '未设置旅行日期时要规划的天数。', - 'dashboard.noDateHint': '未设置日期——将默认创建 7 天。你可以随时修改。', - 'dashboard.coverImage': '封面图片', - 'dashboard.addCoverImage': '添加封面图片', - 'dashboard.addMembers': '旅伴', - 'dashboard.addMember': '添加成员', - 'dashboard.coverSaved': '封面图片已保存', - 'dashboard.coverUploadError': '上传失败', - 'dashboard.coverRemoveError': '移除失败', - 'dashboard.titleRequired': '标题为必填项', - 'dashboard.endDateError': '结束日期必须晚于开始日期', - - // Settings - 'settings.title': '设置', - 'settings.subtitle': '配置你的个人设置', - 'settings.tabs.display': '显示', - 'settings.tabs.map': '地图', - 'settings.tabs.notifications': '通知', - 'settings.tabs.integrations': '集成', - 'settings.tabs.account': '账户', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': '关于', - 'settings.map': '地图', - 'settings.mapTemplate': '地图模板', - 'settings.mapTemplatePlaceholder.select': '选择模板...', - 'settings.mapDefaultHint': '留空则使用 OpenStreetMap(默认)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': '地图瓦片 URL 模板', - 'settings.mapProvider': '地图提供商', - 'settings.mapProviderHint': '影响行程规划和旅程地图。Atlas 始终使用 Leaflet。', - 'settings.mapLeafletSubtitle': '经典 2D,任何栅格瓦片', - 'settings.mapMapboxSubtitle': '矢量瓦片、3D 建筑和地形', - 'settings.mapExperimental': '实验性', - 'settings.mapMapboxToken': 'Mapbox 访问令牌', - 'settings.mapMapboxTokenHint': '公共令牌 (pk.*) 来自', - 'settings.mapMapboxTokenLink': 'mapbox.com → 访问令牌', - 'settings.mapStyle': '地图样式', - 'settings.mapStylePlaceholder': '选择 Mapbox 样式', - 'settings.mapStyleHint': '预设或您自己的 mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D 建筑和地形', - 'settings.map3dHint': '倾斜 + 真实 3D 建筑拉伸 — 适用于所有样式,包括卫星。', - 'settings.mapHighQuality': '高画质模式', - 'settings.mapHighQualityHint': '抗锯齿 + 地球投影,带来更清晰的边缘和更真实的世界视图。', - 'settings.mapHighQualityWarning': '可能影响低端设备的性能。', - 'settings.mapTipLabel': '提示:', - 'settings.mapTip': '右键点击并拖动以旋转/倾斜地图。中键点击添加地点(右键用于旋转)。', - 'settings.latitude': '纬度', - 'settings.longitude': '经度', - 'settings.saveMap': '保存地图', - 'settings.apiKeys': 'API 密钥', - 'settings.mapsKey': 'Google Maps API 密钥', - 'settings.mapsKeyHint': '用于地点搜索。需要 Places API (New)。在 console.cloud.google.com 获取', - 'settings.weatherKey': 'OpenWeatherMap API 密钥', - 'settings.weatherKeyHint': '用于天气数据。在 openweathermap.org/api 免费获取', - 'settings.keyPlaceholder': '输入密钥...', - 'settings.configured': '已配置', - 'settings.saveKeys': '保存密钥', - 'settings.display': '显示', - 'settings.colorMode': '颜色模式', - 'settings.light': '浅色', - 'settings.dark': '深色', - 'settings.auto': '自动', - 'settings.language': '语言', - 'settings.temperature': '温度单位', - 'settings.timeFormat': '时间格式', - 'settings.blurBookingCodes': '模糊预订代码', - 'settings.notifications': '通知', - 'settings.notifyTripInvite': '旅行邀请', - 'settings.notifyBookingChange': '预订变更', - 'settings.notifyTripReminder': '旅行提醒', - 'settings.notifyTodoDue': '待办事项即将到期', - 'settings.notifyVacayInvite': 'Vacay 融合邀请', - 'settings.notifyPhotosShared': '共享照片 (Immich)', - 'settings.notifyCollabMessage': '聊天消息 (Collab)', - 'settings.notifyPackingTagged': '行李清单:分配', - 'settings.notifyWebhook': 'Webhook 通知', - 'settings.notificationsDisabled': '通知尚未配置。请联系管理员启用电子邮件或 Webhook 通知。', - 'settings.notificationsActive': '活跃频道', - 'settings.notificationsManagedByAdmin': '通知事件由管理员配置。', - 'admin.notifications.title': '通知', - 'admin.notifications.hint': '选择一个通知渠道。一次只能激活一个。', - 'admin.notifications.none': '已禁用', - 'admin.notifications.email': '电子邮件 (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': '保存通知设置', - 'admin.notifications.saved': '通知设置已保存', - 'admin.notifications.testWebhook': '发送测试 Webhook', - 'admin.notifications.testWebhookSuccess': '测试 Webhook 发送成功', - 'admin.notifications.testWebhookFailed': '测试 Webhook 发送失败', - 'admin.smtp.title': '邮件与通知', - 'admin.smtp.hint': '用于发送电子邮件通知的 SMTP 配置。', - 'admin.smtp.testButton': '发送测试邮件', - 'admin.webhook.hint': '向外部 Webhook 发送通知(Discord、Slack 等)。', - 'admin.smtp.testSuccess': '测试邮件发送成功', - 'admin.smtp.testFailed': '测试邮件发送失败', - 'dayplan.icsTooltip': '导出日历 (ICS)', - 'share.linkTitle': '公开链接', - 'share.linkHint': '创建一个链接,任何人无需登录即可查看此旅行。仅可查看,无法编辑。', - 'share.createLink': '创建链接', - 'share.deleteLink': '删除链接', - 'share.createError': '无法创建链接', - 'common.copy': '复制', - 'common.copied': '已复制', - 'share.permMap': '地图与计划', - 'share.permBookings': '预订', - 'share.permPacking': '行李', - 'shared.expired': '链接已过期或无效', - 'shared.expiredHint': '此共享旅行链接已失效。', - 'shared.readOnly': '只读共享视图', - 'shared.tabPlan': '计划', - 'shared.tabBookings': '预订', - 'shared.tabPacking': '行李', - 'shared.tabBudget': '预算', - 'shared.tabChat': '聊天', - 'shared.days': '天', - 'shared.places': '个地点', - 'shared.other': '其他', - 'shared.totalBudget': '总预算', - 'shared.messages': '条消息', - 'shared.sharedVia': '通过以下分享', - 'shared.confirmed': '已确认', - 'shared.pending': '待确认', - 'share.permBudget': '预算', - 'share.permCollab': '聊天', - 'settings.on': '开', - 'settings.off': '关', - 'settings.mcp.title': 'MCP 配置', - 'settings.mcp.endpoint': 'MCP 端点', - 'settings.mcp.clientConfig': '客户端配置', - 'settings.mcp.clientConfigHint': '将 替换为下方列表中的 API 令牌。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。', - 'settings.mcp.clientConfigHintOAuth': '将 替换为上方创建的 OAuth 2.1 客户端凭据。首次连接时,mcp-remote 将打开浏览器完成授权。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。', - 'settings.mcp.copy': '复制', - 'settings.mcp.copied': '已复制!', - 'settings.mcp.apiTokens': 'API 令牌', - 'settings.mcp.createToken': '创建新令牌', - 'settings.mcp.noTokens': '暂无令牌,请创建一个以连接 MCP 客户端。', - 'settings.mcp.tokenCreatedAt': '创建于', - 'settings.mcp.tokenUsedAt': '使用于', - 'settings.mcp.deleteTokenTitle': '删除令牌', - 'settings.mcp.deleteTokenMessage': '此令牌将立即失效,使用它的所有 MCP 客户端将失去访问权限。', - 'settings.mcp.modal.createTitle': '创建 API 令牌', - 'settings.mcp.modal.tokenName': '令牌名称', - 'settings.mcp.modal.tokenNamePlaceholder': '例如:Claude Desktop、工作电脑', - 'settings.mcp.modal.creating': '创建中…', - 'settings.mcp.modal.create': '创建令牌', - 'settings.mcp.modal.createdTitle': '令牌已创建', - 'settings.mcp.modal.createdWarning': '此令牌只会显示一次,请立即复制并妥善保存——无法找回。', - 'settings.mcp.modal.done': '完成', - 'settings.mcp.toast.created': '令牌已创建', - 'settings.mcp.toast.createError': '创建令牌失败', - 'settings.mcp.toast.deleted': '令牌已删除', - 'settings.mcp.toast.deleteError': '删除令牌失败', - 'settings.mcp.apiTokensDeprecated': 'API 令牌已弃用,将在未来版本中移除。请改用 OAuth 2.1 客户端。', - 'settings.oauth.clients': 'OAuth 2.1 客户端', - 'settings.oauth.clientsHint': '注册 OAuth 2.1 客户端,让第三方 MCP 应用程序(Claude Web、Cursor 等)无需静态令牌即可连接。', - 'settings.oauth.createClient': '新建客户端', - 'settings.oauth.noClients': '没有已注册的 OAuth 客户端。', - 'settings.oauth.clientId': '客户端 ID', - 'settings.oauth.clientSecret': '客户端密钥', - 'settings.oauth.deleteClient': '删除客户端', - 'settings.oauth.deleteClientMessage': '此客户端及所有活跃会话将被永久删除。使用此客户端的任何应用程序将立即失去访问权限。', - 'settings.oauth.rotateSecret': '轮换密钥', - 'settings.oauth.rotateSecretMessage': '将生成新的客户端密钥,所有现有会话将立即失效。在关闭此对话框之前,请更新您的应用程序。', - 'settings.oauth.rotateSecretConfirm': '轮换', - 'settings.oauth.rotateSecretConfirming': '轮换中…', - 'settings.oauth.rotateSecretDoneTitle': '已生成新密钥', - 'settings.oauth.rotateSecretDoneWarning': '此密钥仅显示一次。请立即复制并更新您的应用程序——所有之前的会话已失效。', - 'settings.oauth.activeSessions': '活跃的 OAuth 会话', - 'settings.oauth.sessionScopes': '权限范围', - 'settings.oauth.sessionExpires': '过期时间', - 'settings.oauth.revoke': '撤销', - 'settings.oauth.revokeSession': '撤销会话', - 'settings.oauth.revokeSessionMessage': '这将立即撤销此 OAuth 会话的访问权限。', - 'settings.oauth.modal.createTitle': '注册 OAuth 客户端', - 'settings.oauth.modal.presets': '快速预设', - 'settings.oauth.modal.clientName': '应用程序名称', - 'settings.oauth.modal.clientNamePlaceholder': '例如 Claude Web、我的 MCP 应用', - 'settings.oauth.modal.redirectUris': '重定向 URI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': '每行一个 URI。需要 HTTPS(localhost 除外)。要求精确匹配。', - 'settings.oauth.modal.scopes': '允许的权限范围', - 'settings.oauth.modal.scopesHint': 'list_trips 和 get_trip_summary 始终可用——无需权限范围。它们帮助 AI 发现所需的行程 ID。', - 'settings.oauth.modal.selectAll': '全选', - 'settings.oauth.modal.deselectAll': '取消全选', - 'settings.oauth.modal.creating': '注册中…', - 'settings.oauth.modal.create': '注册客户端', - 'settings.oauth.modal.createdTitle': '客户端已注册', - 'settings.oauth.modal.createdWarning': '客户端密钥仅显示一次。请立即复制——无法恢复。', - 'settings.oauth.toast.createError': '注册 OAuth 客户端失败', - 'settings.oauth.toast.deleted': 'OAuth 客户端已删除', - 'settings.oauth.toast.deleteError': '删除 OAuth 客户端失败', - 'settings.oauth.toast.revoked': '会话已撤销', - 'settings.oauth.toast.revokeError': '撤销会话失败', - 'settings.oauth.toast.rotateError': '轮换客户端密钥失败', - 'settings.oauth.modal.machineClient': '机器客户端(无需浏览器登录)', - 'settings.oauth.modal.machineClientHint': '使用 client_credentials 授权——无需重定向 URI。令牌通过 client_id + client_secret 直接颁发,并在所选范围内以您的身份运行。', - 'settings.oauth.modal.machineClientUsage': '获取令牌:向 /oauth/token 发送 POST 请求,携带 grant_type=client_credentials、client_id 和 client_secret。无需浏览器,无刷新令牌。', - 'settings.oauth.badge.machine': '机器', - 'settings.account': '账户', - 'settings.about': '关于', - 'settings.about.reportBug': '报告错误', - 'settings.about.reportBugHint': '发现问题?告诉我们', - 'settings.about.featureRequest': '功能建议', - 'settings.about.featureRequestHint': '建议一个新功能', - 'settings.about.wikiHint': '文档和指南', - 'settings.about.supporters.badge': '月度支持者', - 'settings.about.supporters.title': '与 TREK 同行的伙伴', - 'settings.about.supporters.subtitle': '当你在规划下一段路线时,这些人也在一起规划 TREK 的未来。他们每月的支持直接用于开发与真实投入的时间——让 TREK 保持开源。', - 'settings.about.supporters.since': '{date} 起的支持者', - 'settings.about.supporters.tierEmpty': '成为第一个', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK 是一个自托管的旅行规划工具,帮助你从最初的想法到最后的回忆,全程组织你的旅行。日程规划、预算、行李清单、照片等——一切尽在一处,在你自己的服务器上。', - 'settings.about.madeWith': '用', - 'settings.about.madeBy': '由 Maurice 和不断壮大的开源社区打造。', - 'settings.username': '用户名', - 'settings.email': '邮箱', - 'settings.role': '角色', - 'settings.roleAdmin': '管理员', - 'settings.oidcLinked': '已关联', - 'settings.changePassword': '修改密码', - 'settings.mustChangePassword': '您必须更改密码才能继续。请在下方设置新密码。', - 'settings.currentPassword': '当前密码', - 'settings.currentPasswordRequired': '请输入当前密码', - 'settings.newPassword': '新密码', - 'settings.confirmPassword': '确认新密码', - 'settings.updatePassword': '更新密码', - 'settings.passwordRequired': '请输入当前密码和新密码', - 'settings.passwordTooShort': '密码至少需要 8 个字符', - 'settings.passwordMismatch': '两次输入的密码不一致', - 'settings.passwordWeak': '密码必须包含大写字母、小写字母、数字和特殊字符', - 'settings.passwordChanged': '密码修改成功', - 'settings.deleteAccount': '删除账户', - 'settings.deleteAccountTitle': '确定删除账户?', - 'settings.deleteAccountWarning': '你的账户以及所有旅行、地点和文件将被永久删除。此操作无法撤销。', - 'settings.deleteAccountConfirm': '永久删除', - 'settings.deleteBlockedTitle': '无法删除', - 'settings.deleteBlockedMessage': '你是唯一的管理员。请先将其他用户提升为管理员,然后再删除账户。', - 'settings.roleUser': '用户', - 'settings.saveProfile': '保存资料', - 'settings.mfa.title': '双因素认证 (2FA)', - 'settings.mfa.description': '登录时添加第二步验证。使用身份验证器应用(Google Authenticator、Authy 等)。', - 'settings.mfa.requiredByPolicy': '管理员要求双因素身份验证。请先完成下方的身份验证器设置后再继续。', - 'settings.mfa.backupTitle': '备用代码', - 'settings.mfa.backupDescription': '如果你无法使用身份验证器应用,可使用这些一次性备用代码登录。', - 'settings.mfa.backupWarning': '请立即保存这些代码。每个代码只能使用一次。', - 'settings.mfa.backupCopy': '复制代码', - 'settings.mfa.backupDownload': '下载 TXT', - 'settings.mfa.backupPrint': '打印 / PDF', - 'settings.mfa.backupCopied': '备用代码已复制', - 'settings.mfa.enabled': '您的账户已启用 2FA。', - 'settings.mfa.disabled': '2FA 未启用。', - 'settings.mfa.setup': '设置身份验证器', - 'settings.mfa.scanQr': '使用应用扫描此二维码,或手动输入密钥。', - 'settings.mfa.secretLabel': '密钥(手动输入)', - 'settings.mfa.codePlaceholder': '6 位验证码', - 'settings.mfa.enable': '启用 2FA', - 'settings.mfa.cancelSetup': '取消', - 'settings.mfa.disableTitle': '停用 2FA', - 'settings.mfa.disableHint': '输入您的账户密码和身份验证器中的当前验证码。', - 'settings.mfa.disable': '停用 2FA', - 'settings.mfa.toastEnabled': '双因素认证已启用', - 'settings.mfa.toastDisabled': '双因素认证已停用', - 'settings.mfa.demoBlocked': '演示模式下不可用', - 'settings.toast.mapSaved': '地图设置已保存', - 'settings.toast.keysSaved': 'API 密钥已保存', - 'settings.toast.displaySaved': '显示设置已保存', - 'settings.toast.profileSaved': '资料已保存', - 'settings.uploadAvatar': '上传头像', - 'settings.removeAvatar': '移除头像', - 'settings.avatarUploaded': '头像已更新', - 'settings.avatarRemoved': '头像已移除', - 'settings.avatarError': '上传失败', - - // Login - 'login.error': '登录失败,请检查你的凭据。', - 'login.tagline': '你的旅行。\n你的计划。', - 'login.description': '通过互动地图、预算管理和实时同步,协同规划旅行。', - 'login.features.maps': '互动地图', - 'login.features.mapsDesc': 'Google Places、路线和聚类', - 'login.features.realtime': '实时同步', - 'login.features.realtimeDesc': '通过 WebSocket 协同规划', - 'login.features.budget': '预算跟踪', - 'login.features.budgetDesc': '分类、图表和人均费用', - 'login.features.collab': '协作', - 'login.features.collabDesc': '多用户共享旅行', - 'login.features.packing': '行李清单', - 'login.features.packingDesc': '分类、进度和建议', - 'login.features.bookings': '预订', - 'login.features.bookingsDesc': '航班、酒店、餐厅等', - 'login.features.files': '文档', - 'login.features.filesDesc': '上传和管理文档', - 'login.features.routes': '智能路线', - 'login.features.routesDesc': '自动优化和导出到 Google Maps', - 'login.selfHosted': '自托管 · 开源 · 数据由你掌控', - 'login.title': '登录', - 'login.subtitle': '欢迎回来', - 'login.signingIn': '登录中…', - 'login.signIn': '登录', - 'login.createAdmin': '创建管理员账户', - 'login.createAdminHint': '为 TREK 设置第一个管理员账户。', - 'login.setNewPassword': '设置新密码', - 'login.setNewPasswordHint': '您必须更改密码才能继续。', - 'login.createAccount': '创建账户', - 'login.createAccountHint': '注册新账户。', - 'login.creating': '创建中…', - 'login.noAccount': '还没有账户?', - 'login.hasAccount': '已有账户?', - 'login.register': '注册', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': '用户名', - 'login.oidc.registrationDisabled': '注册已关闭。请联系管理员。', - 'login.oidc.noEmail': '未从提供商获取到邮箱。', - 'login.mfaTitle': '双因素认证', - 'login.mfaSubtitle': '请输入身份验证器应用中的 6 位验证码。', - 'login.mfaCodeLabel': '验证码', - 'login.mfaCodeRequired': '请输入身份验证器应用中的验证码。', - 'login.mfaHint': '打开 Google Authenticator、Authy 或其他 TOTP 应用。', - 'login.mfaBack': '← 返回登录', - 'login.mfaVerify': '验证', - 'login.invalidInviteLink': '邀请链接无效或已过期', - 'login.oidcFailed': 'OIDC 登录失败', - 'login.usernameRequired': '用户名为必填项', - 'login.passwordMinLength': '密码至少需要8个字符', - 'login.forgotPassword': '忘记密码?', - 'login.forgotPasswordTitle': '重置密码', - 'login.forgotPasswordBody': '输入您注册时使用的邮箱地址。若账户存在,我们将发送重置链接。', - 'login.forgotPasswordSubmit': '发送重置链接', - 'login.forgotPasswordSentTitle': '请查看邮箱', - 'login.forgotPasswordSentBody': '若该邮箱存在账户,重置链接正在发送中。链接将在 60 分钟后失效。', - 'login.forgotPasswordSmtpHintOff': '提示:管理员未配置 SMTP,重置链接将被写入服务器控制台,而不是通过电子邮件发送。', - 'login.backToLogin': '返回登录', - 'login.newPassword': '新密码', - 'login.confirmPassword': '确认新密码', - 'login.passwordsDontMatch': '两次输入的密码不一致', - 'login.mfaCode': '二步验证码', - 'login.resetPasswordTitle': '设置新密码', - 'login.resetPasswordBody': '请选择您在此处未使用过的强密码。至少 8 位。', - 'login.resetPasswordMfaBody': '输入您的二步验证码或备用代码以完成重置。', - 'login.resetPasswordSubmit': '重置密码', - 'login.resetPasswordVerify': '验证并重置', - 'login.resetPasswordSuccessTitle': '密码已更新', - 'login.resetPasswordSuccessBody': '您现在可以使用新密码登录了。', - 'login.resetPasswordInvalidLink': '无效的重置链接', - 'login.resetPasswordInvalidLinkBody': '此链接已丢失或损坏。请重新申请以继续。', - 'login.resetPasswordFailed': '重置失败。链接可能已过期。', - 'login.oidc.tokenFailed': '认证失败。', - 'login.oidc.invalidState': '会话无效,请重试。', - 'login.demoFailed': '演示登录失败', - 'login.oidcSignIn': '通过 {name} 登录', - 'login.oidcOnly': '密码登录已关闭。请通过 SSO 提供商登录。', - 'login.oidcLoggedOut': '您已退出登录。请重新通过 SSO 提供商登录。', - 'login.demoHint': '试用演示——无需注册', - - // Register - 'register.passwordMismatch': '两次输入的密码不一致', - 'register.passwordTooShort': '密码至少需要 8 个字符', - 'register.failed': '注册失败', - 'register.getStarted': '开始使用', - 'register.subtitle': '创建账户,开始规划你的梦想旅行。', - 'register.feature1': '无限旅行计划', - 'register.feature2': '互动地图视图', - 'register.feature3': '管理地点和分类', - 'register.feature4': '跟踪预订', - 'register.feature5': '创建行李清单', - 'register.feature6': '存储照片和文件', - 'register.createAccount': '创建账户', - 'register.startPlanning': '开始规划你的旅行', - 'register.minChars': '至少 6 个字符', - 'register.confirmPassword': '确认密码', - 'register.repeatPassword': '重复密码', - 'register.registering': '注册中...', - 'register.register': '注册', - 'register.hasAccount': '已有账户?', - 'register.signIn': '登录', - - // Admin - 'admin.title': '管理后台', - 'admin.subtitle': '用户管理和系统设置', - 'admin.tabs.users': '用户', - 'admin.tabs.categories': '分类', - 'admin.tabs.backup': '备份', - 'admin.tabs.audit': '审计', - 'admin.stats.users': '用户', - 'admin.stats.trips': '旅行', - 'admin.stats.places': '地点', - 'admin.stats.photos': '照片', - 'admin.stats.files': '文件', - 'admin.table.user': '用户', - 'admin.table.email': '邮箱', - 'admin.table.role': '角色', - 'admin.table.created': '创建时间', - 'admin.table.lastLogin': '最后登录', - 'admin.table.actions': '操作', - 'admin.you': '(你)', - 'admin.editUser': '编辑用户', - 'admin.newPassword': '新密码', - 'admin.newPasswordHint': '留空则保持当前密码', - 'admin.deleteUser': '删除用户「{name}」?所有旅行将被永久删除。', - 'admin.deleteUserTitle': '删除用户', - 'admin.newPasswordPlaceholder': '输入新密码…', - 'admin.toast.loadError': '加载管理数据失败', - 'admin.toast.userUpdated': '用户已更新', - 'admin.toast.updateError': '更新失败', - 'admin.toast.userDeleted': '用户已删除', - 'admin.toast.deleteError': '删除失败', - 'admin.toast.cannotDeleteSelf': '不能删除自己的账户', - 'admin.toast.userCreated': '用户已创建', - 'admin.toast.createError': '创建用户失败', - 'admin.toast.fieldsRequired': '用户名、邮箱和密码为必填项', - 'admin.createUser': '创建用户', - 'admin.invite.title': '邀请链接', - 'admin.invite.subtitle': '创建一次性注册链接', - 'admin.invite.create': '创建链接', - 'admin.invite.createAndCopy': '创建并复制', - 'admin.invite.empty': '尚未创建邀请链接', - 'admin.invite.maxUses': '最大使用次数', - 'admin.invite.expiry': '有效期', - 'admin.invite.uses': '已使用', - 'admin.invite.expiresAt': '过期时间', - 'admin.invite.createdBy': '由', - 'admin.invite.active': '有效', - 'admin.invite.expired': '已过期', - 'admin.invite.usedUp': '已用完', - 'admin.invite.copied': '邀请链接已复制', - 'admin.invite.copyLink': '复制链接', - 'admin.invite.deleted': '邀请链接已删除', - 'admin.invite.createError': '创建链接失败', - 'admin.invite.deleteError': '删除链接失败', - 'admin.tabs.settings': '设置', - 'admin.allowRegistration': '允许注册', - 'admin.allowRegistrationHint': '新用户可以自行注册', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': '要求双因素身份验证(2FA)', - 'admin.requireMfaHint': '未启用 2FA 的用户必须先完成设置中的配置才能使用应用。', - 'admin.apiKeys': 'API 密钥', - 'admin.apiKeysHint': '可选。启用地点的扩展数据,如照片和天气。', - 'admin.mapsKey': 'Google Maps API 密钥', - 'admin.mapsKeyHint': '用于地点搜索。在 console.cloud.google.com 获取', - 'admin.mapsKeyHintLong': '没有 API 密钥时,使用 OpenStreetMap 搜索地点。有了 Google API 密钥,还可以加载照片、评分和营业时间。在 console.cloud.google.com 获取。', - 'admin.recommended': '推荐', - 'admin.weatherKey': 'OpenWeatherMap API 密钥', - 'admin.weatherKeyHint': '用于天气数据。在 openweathermap.org 免费获取', - 'admin.validateKey': '测试', - 'admin.keyValid': '已连接', - 'admin.keyInvalid': '无效', - 'admin.keySaved': 'API 密钥已保存', - 'admin.oidcTitle': '单点登录 (OIDC)', - 'admin.oidcSubtitle': '允许通过 Google、Apple、Authentik 或 Keycloak 等外部提供商登录。', - 'admin.oidcDisplayName': '显示名称', - 'admin.oidcIssuer': '颁发者 URL', - 'admin.oidcIssuerHint': '提供商的 OpenID Connect 颁发者 URL。如 https://accounts.google.com', - 'admin.oidcSaved': 'OIDC 配置已保存', - 'admin.oidcOnlyMode': '禁用密码登录', - 'admin.oidcOnlyModeHint': '启用后,仅允许 SSO 登录。密码登录和注册将被禁用。', - - // File Types - 'admin.fileTypes': '允许的文件类型', - 'admin.fileTypesHint': '配置用户可以上传的文件类型。', - 'admin.fileTypesFormat': '以逗号分隔的扩展名(如 jpg,png,pdf,doc)。使用 * 允许所有类型。', - 'admin.fileTypesSaved': '文件类型设置已保存', - - 'admin.placesPhotos.title': '地点照片', - 'admin.placesPhotos.subtitle': '从 Google Places API 获取照片。禁用可节省 API 配额。Wikimedia 照片不受影响。', - 'admin.placesAutocomplete.title': '地点自动补全', - 'admin.placesAutocomplete.subtitle': '使用 Google Places API 提供搜索建议。禁用可节省 API 配额。', - 'admin.placesDetails.title': '地点详情', - 'admin.placesDetails.subtitle': '从 Google Places API 获取地点详细信息(营业时间、评分、网站)。禁用可节省 API 配额。', - 'admin.bagTracking.title': '行李追踪', - 'admin.bagTracking.subtitle': '为打包物品启用重量和行李分配', - 'admin.collab.chat.title': '聊天', - 'admin.collab.chat.subtitle': '实时消息协作', - 'admin.collab.notes.title': '笔记', - 'admin.collab.notes.subtitle': '共享笔记和文档', - 'admin.collab.polls.title': '投票', - 'admin.collab.polls.subtitle': '群组投票和表决', - 'admin.collab.whatsnext.title': '下一步', - 'admin.collab.whatsnext.subtitle': '活动建议和后续步骤', - 'admin.tabs.config': '个性化', - 'admin.tabs.defaults': '用户默认设置', - 'admin.defaultSettings.title': '用户默认设置', - 'admin.defaultSettings.description': '设置实例范围的默认值。未更改设置的用户将看到这些值。用户自己的更改始终优先。', - 'admin.defaultSettings.saved': '默认值已保存', - 'admin.defaultSettings.reset': '重置为内置默认值', - 'admin.defaultSettings.resetToBuiltIn': '重置', - 'admin.tabs.templates': '打包模板', - 'admin.packingTemplates.title': '打包模板', - 'admin.packingTemplates.subtitle': '创建可复用的旅行打包清单', - 'admin.packingTemplates.create': '新建模板', - 'admin.packingTemplates.namePlaceholder': '模板名称(如:海滩度假)', - 'admin.packingTemplates.empty': '尚未创建模板', - 'admin.packingTemplates.items': '物品', - 'admin.packingTemplates.categories': '分类', - 'admin.packingTemplates.itemName': '物品名称', - 'admin.packingTemplates.itemCategory': '分类', - 'admin.packingTemplates.categoryName': '分类名称(如:衣物)', - 'admin.packingTemplates.addCategory': '添加分类', - 'admin.packingTemplates.created': '模板已创建', - 'admin.packingTemplates.deleted': '模板已删除', - 'admin.packingTemplates.loadError': '加载模板失败', - 'admin.packingTemplates.createError': '创建模板失败', - 'admin.packingTemplates.deleteError': '删除模板失败', - 'admin.packingTemplates.saveError': '保存失败', - - // Addons - 'admin.tabs.addons': '扩展', - 'admin.addons.title': '扩展', - 'admin.addons.subtitle': '启用或禁用功能以自定义你的 TREK 体验。', - 'admin.addons.catalog.memories.name': '照片 (Immich)', - 'admin.addons.catalog.memories.description': '通过 Immich 实例分享旅行照片', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': '用于 AI 助手集成的模型上下文协议', - 'admin.addons.catalog.packing.name': '列表', - 'admin.addons.catalog.packing.description': '行程打包清单与待办任务', - 'admin.addons.catalog.budget.name': '预算', - 'admin.addons.catalog.budget.description': '跟踪支出并规划旅行预算', - 'admin.addons.catalog.documents.name': '文档', - 'admin.addons.catalog.documents.description': '存储和管理旅行文档', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': '带日历视图的个人假期规划器', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': '标记已访问国家和旅行统计的世界地图', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': '旅行规划的实时笔记、投票和聊天', - 'admin.addons.subtitleBefore': '启用或禁用功能以自定义你的 ', - 'admin.addons.subtitleAfter': ' 体验。', - 'admin.addons.enabled': '已启用', - 'admin.addons.disabled': '已禁用', - 'admin.addons.type.trip': '旅行', - 'admin.addons.type.global': '全局', - 'admin.addons.type.integration': '集成', - 'admin.addons.tripHint': '在每次旅行中作为标签页显示', - 'admin.addons.globalHint': '在主导航中作为独立板块显示', - 'admin.addons.integrationHint': '后端服务和 API 集成,无专属页面', - 'admin.addons.toast.updated': '扩展已更新', - 'admin.addons.toast.error': '更新扩展失败', - 'admin.addons.noAddons': '暂无可用扩展', - // Weather info - 'admin.weather.title': '天气数据', - 'admin.weather.badge': '自 2026 年 3 月 24 日起', - 'admin.weather.description': 'TREK 使用 Open-Meteo 作为天气数据源。Open-Meteo 是免费的开源天气服务——无需 API 密钥。', - 'admin.weather.forecast': '16 天天气预报', - 'admin.weather.forecastDesc': '之前为 5 天 (OpenWeatherMap)', - 'admin.weather.climate': '历史气候数据', - 'admin.weather.climateDesc': '16 天预报之外的日期使用过去 85 年的平均值', - 'admin.weather.requests': '每天 10,000 次请求', - 'admin.weather.requestsDesc': '免费,无需 API 密钥', - 'admin.weather.locationHint': '天气基于每天中第一个有坐标的地点。如果当天没有分配地点,则使用地点列表中的任意地点作为参考。', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP 访问', - 'admin.mcpTokens.title': 'MCP 访问', - 'admin.mcpTokens.subtitle': '管理所有用户的 OAuth 会话和 API 令牌', - 'admin.mcpTokens.sectionTitle': 'API 令牌', - 'admin.mcpTokens.owner': '所有者', - 'admin.mcpTokens.tokenName': '令牌名称', - 'admin.mcpTokens.created': '创建时间', - 'admin.mcpTokens.lastUsed': '最后使用', - 'admin.mcpTokens.never': '从未', - 'admin.mcpTokens.empty': '尚未创建任何 MCP 令牌', - 'admin.mcpTokens.deleteTitle': '删除令牌', - 'admin.mcpTokens.deleteMessage': '此令牌将立即被撤销。用户将失去通过此令牌的 MCP 访问权限。', - 'admin.mcpTokens.deleteSuccess': '令牌已删除', - 'admin.mcpTokens.deleteError': '删除令牌失败', - 'admin.mcpTokens.loadError': '加载令牌失败', - 'admin.oauthSessions.sectionTitle': 'OAuth 会话', - 'admin.oauthSessions.clientName': '客户端', - 'admin.oauthSessions.owner': '所有者', - 'admin.oauthSessions.scopes': '权限范围', - 'admin.oauthSessions.created': '创建时间', - 'admin.oauthSessions.empty': '暂无活跃的 OAuth 会话', - 'admin.oauthSessions.revokeTitle': '撤销会话', - 'admin.oauthSessions.revokeMessage': '此 OAuth 会话将立即被撤销。客户端将失去 MCP 访问权限。', - 'admin.oauthSessions.revokeSuccess': '会话已撤销', - 'admin.oauthSessions.revokeError': '撤销会话失败', - 'admin.oauthSessions.loadError': '加载 OAuth 会话失败', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': '安全与管理员操作记录(备份、用户、MFA、设置)。', - 'admin.audit.empty': '暂无审计记录。', - 'admin.audit.refresh': '刷新', - 'admin.audit.loadMore': '加载更多', - 'admin.audit.showing': '已加载 {count} 条 · 共 {total} 条', - 'admin.audit.col.time': '时间', - 'admin.audit.col.user': '用户', - 'admin.audit.col.action': '操作', - 'admin.audit.col.resource': '资源', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': '详情', - - 'admin.github.title': '版本历史', - 'admin.github.subtitle': '{repo} 的最新更新', - 'admin.github.latest': '最新', - 'admin.github.prerelease': '预发布', - 'admin.github.showDetails': '显示详情', - 'admin.github.hideDetails': '隐藏详情', - 'admin.github.loadMore': '加载更多', - 'admin.github.loading': '加载中...', - 'admin.github.support': '帮助我继续开发 TREK', - 'admin.github.error': '加载版本失败', - 'admin.github.by': '作者', - - 'admin.update.available': '有可用更新', - 'admin.update.text': 'TREK {version} 已发布。你当前使用的是 {current}。', - 'admin.update.button': '在 GitHub 查看', - 'admin.update.install': '安装更新', - 'admin.update.confirmTitle': '确定安装更新?', - 'admin.update.confirmText': 'TREK 将从 {current} 更新到 {version}。服务器将自动重启。', - 'admin.update.dataInfo': '你的所有数据(旅行、用户、API 密钥、上传文件、Vacay、Atlas、预算)将被保留。', - 'admin.update.warning': '重启期间应用将短暂不可用。', - 'admin.update.confirm': '立即更新', - 'admin.update.installing': '更新中…', - 'admin.update.success': '更新已安装!服务器正在重启…', - 'admin.update.failed': '更新失败', - 'admin.update.backupHint': '建议在更新前创建备份。', - 'admin.update.backupLink': '前往备份', - 'admin.update.howTo': '如何更新', - 'admin.update.dockerText': '你的 TREK 实例运行在 Docker 中。要更新到 {version},请在服务器上执行以下命令:', - 'admin.update.reloadHint': '请在几秒后刷新页面。', - - // Vacay addon - 'vacay.subtitle': '规划和管理假期', - 'vacay.settings': '设置', - 'vacay.year': '年份', - 'vacay.addYear': '添加下一年', - 'vacay.addPrevYear': '添加上一年', - 'vacay.removeYear': '移除年份', - 'vacay.removeYearConfirm': '移除 {year}?', - 'vacay.removeYearHint': '该年度所有假期记录和公司假日将被永久删除。', - 'vacay.remove': '移除', - 'vacay.persons': '成员', - 'vacay.noPersons': '暂无成员', - 'vacay.addPerson': '添加成员', - 'vacay.editPerson': '编辑成员', - 'vacay.removePerson': '移除成员', - 'vacay.removePersonConfirm': '移除 {name}?', - 'vacay.removePersonHint': '该成员的所有假期记录将被永久删除。', - 'vacay.personName': '姓名', - 'vacay.personNamePlaceholder': '输入姓名', - 'vacay.color': '颜色', - 'vacay.add': '添加', - 'vacay.legend': '图例', - 'vacay.publicHoliday': '公共假日', - 'vacay.companyHoliday': '公司假日', - 'vacay.weekend': '周末', - 'vacay.modeVacation': '休假', - 'vacay.modeCompany': '公司假日', - 'vacay.entitlement': '年假额度', - 'vacay.entitlementDays': '天', - 'vacay.used': '已用', - 'vacay.remaining': '剩余', - 'vacay.carriedOver': '从 {year} 结转', - 'vacay.blockWeekends': '锁定周末', - 'vacay.blockWeekendsHint': '禁止在周六和周日安排假期', - 'vacay.weekendDays': '周末', - 'vacay.mon': '周一', - 'vacay.tue': '周二', - 'vacay.wed': '周三', - 'vacay.thu': '周四', - 'vacay.fri': '周五', - 'vacay.sat': '周六', - 'vacay.sun': '周日', - 'vacay.publicHolidays': '公共假日', - 'vacay.publicHolidaysHint': '在日历中标记公共假日', - 'vacay.selectCountry': '选择国家', - 'vacay.selectRegion': '选择地区(可选)', - 'vacay.companyHolidays': '公司假日', - 'vacay.companyHolidaysHint': '允许标记公司统一休假日', - 'vacay.companyHolidaysNoDeduct': '公司假日不计入年假天数。', - 'vacay.weekStart': '每周开始于', - 'vacay.weekStartHint': '选择日历周从周一还是周日开始', - 'vacay.carryOver': '结转', - 'vacay.carryOverHint': '自动将剩余年假天数结转到下一年', - 'vacay.sharing': '共享', - 'vacay.sharingHint': '与其他 TREK 用户共享你的假期计划', - 'vacay.owner': '所有者', - 'vacay.shareEmailPlaceholder': 'TREK 用户邮箱', - 'vacay.shareSuccess': '计划共享成功', - 'vacay.shareError': '无法共享计划', - 'vacay.dissolve': '解除合并', - 'vacay.dissolveHint': '重新分离日历。你的记录将被保留。', - 'vacay.dissolveAction': '解除', - 'vacay.dissolved': '日历已分离', - 'vacay.fusedWith': '已合并', - 'vacay.you': '你', - 'vacay.noData': '暂无数据', - 'vacay.changeColor': '更改颜色', - 'vacay.inviteUser': '邀请用户', - 'vacay.inviteHint': '邀请其他 TREK 用户共享合并的假期日历。', - 'vacay.selectUser': '选择用户', - 'vacay.sendInvite': '发送邀请', - 'vacay.inviteSent': '邀请已发送', - 'vacay.inviteError': '无法发送邀请', - 'vacay.pending': '待处理', - 'vacay.noUsersAvailable': '没有可用用户', - 'vacay.accept': '接受', - 'vacay.decline': '拒绝', - 'vacay.acceptFusion': '接受并合并', - 'vacay.inviteTitle': '合并请求', - 'vacay.inviteWantsToFuse': '想要与你共享假期日历。', - 'vacay.fuseInfo1': '你们双方将在一个共享日历中看到所有假期记录。', - 'vacay.fuseInfo2': '双方都可以为对方创建和编辑记录。', - 'vacay.fuseInfo3': '双方都可以删除记录和修改年假额度。', - 'vacay.fuseInfo4': '公共假日和公司假日等设置将共享。', - 'vacay.fuseInfo5': '任何一方都可以随时解除合并。你的记录将被保留。', - 'vacay.addCalendar': '添加日历', - 'vacay.calendarColor': '颜色', - 'vacay.calendarLabel': '标签', - 'vacay.noCalendars': '无日历', - 'nav.myTrips': '我的旅行', - - // Atlas addon - 'atlas.subtitle': '你的全球旅行足迹', - 'atlas.countries': '国家', - 'atlas.trips': '旅行', - 'atlas.places': '地点', - 'atlas.days': '天', - 'atlas.visitedCountries': '已访问国家', - 'atlas.cities': '城市', - 'atlas.noData': '暂无旅行数据', - 'atlas.noDataHint': '创建旅行并添加地点以查看世界地图', - 'atlas.lastTrip': '上次旅行', - 'atlas.nextTrip': '下次旅行', - 'atlas.daysLeft': '天后出发', - 'atlas.streak': '连续', - 'atlas.year': '年', - 'atlas.years': '年', - 'atlas.yearInRow': '年连续', - 'atlas.yearsInRow': '年连续', - 'atlas.tripIn': '次旅行在', - 'atlas.tripsIn': '次旅行在', - 'atlas.since': '自', - 'atlas.europe': '欧洲', - 'atlas.asia': '亚洲', - 'atlas.northAmerica': '北美洲', - 'atlas.southAmerica': '南美洲', - 'atlas.africa': '非洲', - 'atlas.oceania': '大洋洲', - 'atlas.other': '其他', - 'atlas.firstVisit': '首次旅行', - 'atlas.lastVisitLabel': '最近旅行', - 'atlas.tripSingular': '次旅行', - 'atlas.tripPlural': '次旅行', - 'atlas.placeVisited': '个地点已访问', - 'atlas.placesVisited': '个地点已访问', - 'atlas.statsTab': '统计', - 'atlas.bucketTab': '心愿单', - 'atlas.addBucket': '添加到心愿单', - 'atlas.bucketNamePlaceholder': '地点或目的地...', - 'atlas.bucketNotesPlaceholder': '备注(可选)', - 'atlas.bucketEmpty': '你的心愿单是空的', - 'atlas.bucketEmptyHint': '添加你梦想去的地方', - 'atlas.unmark': '移除', - 'atlas.confirmMark': '将此国家标记为已访问?', - 'atlas.confirmUnmark': '从已访问列表中移除此国家?', - 'atlas.confirmUnmarkRegion': '从已访问列表中移除此地区?', - 'atlas.markVisited': '标记为已访问', - 'atlas.markVisitedHint': '将此国家添加到已访问列表', - 'atlas.markRegionVisitedHint': '将此地区添加到已访问列表', - 'atlas.addToBucket': '添加到心愿单', - 'atlas.addPoi': '添加地点', - 'atlas.searchCountry': '搜索国家...', - 'atlas.month': '月份', - 'atlas.addToBucketHint': '保存为想去的地方', - 'atlas.bucketWhen': '你计划什么时候去?', - - // Trip Planner - 'trip.tabs.plan': '计划', - 'trip.tabs.transports': '交通', - 'trip.tabs.reservations': '预订', - 'trip.tabs.reservationsShort': '预订', - 'trip.tabs.packing': '行李清单', - 'trip.tabs.packingShort': '行李', - 'trip.tabs.lists': '列表', - 'trip.tabs.listsShort': '列表', - 'trip.tabs.budget': '预算', - 'trip.tabs.files': '文件', - 'trip.loading': '加载旅行中...', - 'trip.loadingPhotos': '正在加载地点照片...', - 'trip.mobilePlan': '计划', - 'trip.mobilePlaces': '地点', - 'trip.toast.placeUpdated': '地点已更新', - 'trip.toast.placeAdded': '地点已添加', - 'trip.toast.placeDeleted': '地点已删除', - 'trip.toast.selectDay': '请先选择一天', - 'trip.toast.assignedToDay': '地点已分配到当天', - 'trip.toast.reorderError': '排序失败', - 'trip.toast.reservationUpdated': '预订已更新', - 'trip.toast.reservationAdded': '预订已添加', - 'trip.toast.deleted': '已删除', - 'trip.confirm.deletePlace': '确定要删除这个地点吗?', - 'trip.confirm.deletePlaces': '删除 {count} 个地点?', - 'trip.toast.placesDeleted': '已删除 {count} 个地点', - - // Day Plan Sidebar - 'dayplan.emptyDay': '当天暂无计划', - 'dayplan.addNote': '添加备注', - 'dayplan.editNote': '编辑备注', - 'dayplan.noteAdd': '添加备注', - 'dayplan.noteEdit': '编辑备注', - 'dayplan.noteTitle': '备注', - 'dayplan.noteSubtitle': '每日备注', - 'dayplan.totalCost': '总费用', - 'dayplan.days': '天', - 'dayplan.dayN': '第 {n} 天', - 'dayplan.calculating': '计算中...', - 'dayplan.route': '路线', - 'dayplan.optimize': '优化', - 'dayplan.optimized': '路线已优化', - 'dayplan.routeError': '路线计算失败', - 'dayplan.toast.needTwoPlaces': '路线优化至少需要两个地点', - 'dayplan.toast.routeOptimized': '路线已优化', - 'dayplan.toast.noGeoPlaces': '未找到有坐标的地点用于路线计算', - 'dayplan.confirmed': '已确认', - 'dayplan.pendingRes': '待确认', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': '导出当天计划为 PDF', - 'dayplan.pdfError': 'PDF 导出失败', - 'dayplan.cannotReorderTransport': '有固定时间的预订无法重新排序', - 'dayplan.confirmRemoveTimeTitle': '移除时间?', - 'dayplan.confirmRemoveTimeBody': '此地点有固定时间({time})。移动后将移除时间并允许自由排序。', - 'dayplan.confirmRemoveTimeAction': '移除时间并移动', - 'dayplan.cannotDropOnTimed': '无法将项目放置在有固定时间的条目之间', - 'dayplan.cannotBreakChronology': '这将打乱已计划项目和预订的时间顺序', - - // Places Sidebar - 'places.addPlace': '添加地点/活动', - 'places.importFile': '导入文件', - 'places.sidebarDrop': '拖放以导入', - 'places.importFileHint': '从 Google My Maps、Google Earth 或 GPS 追踪器等工具导入 .gpx、.kml 或 .kmz 文件。', - 'places.importFileDropHere': '点击选择文件或拖放到此处', - 'places.importFileDropActive': '释放文件以选择', - 'places.importFileUnsupported': '不支持的文件类型,请使用 .gpx、.kml 或 .kmz。', - 'places.importFileTooLarge': '文件过大。最大上传大小为 {maxMb} MB。', - 'places.importFileError': '导入失败', - 'places.importAllSkipped': '所有地点已在行程中。', - 'places.gpxImported': '已从 GPX 导入 {count} 个地点', - 'places.gpxImportTypes': '要导入什么?', - 'places.gpxImportWaypoints': '路点', - 'places.gpxImportRoutes': '路线', - 'places.gpxImportTracks': '轨迹(含路径几何)', - 'places.gpxImportNoneSelected': '请至少选择一种导入类型。', - 'places.kmlImportTypes': '要导入什么?', - 'places.kmlImportPoints': '点(Placemarks)', - 'places.kmlImportPaths': '路径(LineStrings)', - 'places.kmlImportNoneSelected': '请至少选择一种类型。', - 'places.selectionCount': '已选 {count} 项', - 'places.deleteSelected': '删除所选', - 'places.kmlKmzImported': '已从 KMZ/KML 导入 {count} 个地点', - 'places.urlResolved': '已从 URL 导入地点', - 'places.importList': '列表导入', - 'places.kmlKmzSummaryValues': 'Placemarks:{total} • 已导入:{created} • 已跳过:{skipped}', - 'places.importGoogleList': 'Google 列表', - 'places.importNaverList': 'Naver 列表', - 'places.googleListHint': '粘贴共享的 Google Maps 列表链接以导入所有地点。', - 'places.googleListImported': '已从"{list}"导入 {count} 个地点', - 'places.googleListError': 'Google Maps 列表导入失败', - 'places.naverListHint': '粘贴共享的 Naver Maps 列表链接以导入所有地点。', - 'places.naverListImported': '已从"{list}"导入 {count} 个地点', - 'places.naverListError': 'Naver Maps 列表导入失败', - 'places.viewDetails': '查看详情', - 'places.assignToDay': '添加到哪一天?', - 'places.all': '全部', - 'places.unplanned': '未规划', - 'places.filterTracks': '路线', - 'places.search': '搜索地点...', - 'places.allCategories': '所有分类', - 'places.categoriesSelected': '个分类', - 'places.clearFilter': '清除筛选', - 'places.count': '{count} 个地点', - 'places.countSingular': '1 个地点', - 'places.allPlanned': '所有地点已规划', - 'places.noneFound': '未找到地点', - 'places.editPlace': '编辑地点', - 'places.formName': '名称', - 'places.formNamePlaceholder': '如:埃菲尔铁塔', - 'places.formDescription': '描述', - 'places.formDescriptionPlaceholder': '简短描述...', - 'places.formAddress': '地址', - 'places.formAddressPlaceholder': '街道、城市、国家', - 'places.formLat': '纬度(如 48.8566)', - 'places.formLng': '经度(如 2.3522)', - 'places.formCategory': '分类', - 'places.noCategory': '无分类', - 'places.categoryNamePlaceholder': '分类名称', - 'places.formTime': '时间', - 'places.startTime': '开始', - 'places.endTime': '结束', - 'places.endTimeBeforeStart': '结束时间早于开始时间', - 'places.timeCollision': '时间冲突:', - 'places.formWebsite': '网站', - 'places.formNotes': '备注', - 'places.formNotesPlaceholder': '个人备注...', - 'places.formReservation': '预订', - 'places.reservationNotesPlaceholder': '预订备注、确认号...', - 'places.mapsSearchPlaceholder': '搜索地点...', - 'places.mapsSearchError': '地点搜索失败。', - 'places.loadingDetails': '正在加载地点详情…', - 'places.osmHint': '使用 OpenStreetMap 搜索(无照片、营业时间或评分)。在设置中添加 Google API 密钥以获取完整信息。', - 'places.osmActive': '通过 OpenStreetMap 搜索(无照片、评分或营业时间)。在设置中添加 Google API 密钥以获取增强数据。', - 'places.categoryCreateError': '创建分类失败', - 'places.nameRequired': '请输入名称', - 'places.saveError': '保存失败', - // Place Inspector - 'inspector.opened': '营业中', - 'inspector.closed': '已关闭', - 'inspector.openingHours': '营业时间', - 'inspector.showHours': '显示营业时间', - 'inspector.files': '文件', - 'inspector.filesCount': '{count} 个文件', - 'inspector.removeFromDay': '从当天移除', - 'inspector.remove': '删除', - 'inspector.addToDay': '添加到当天', - 'inspector.confirmedRes': '已确认预订', - 'inspector.pendingRes': '待确认预订', - 'inspector.google': '在 Google Maps 中打开', - 'inspector.website': '打开网站', - 'inspector.addRes': '预订', - 'inspector.editRes': '编辑预订', - 'inspector.participants': '参与者', - 'inspector.trackStats': '轨迹数据', - - // Reservations - 'reservations.title': '预订', - 'reservations.empty': '暂无预订', - 'reservations.emptyHint': '添加航班、酒店等预订信息', - 'reservations.add': '添加预订', - 'reservations.addManual': '手动添加', - 'reservations.placeHint': '提示:建议从地点直接创建预订,以便与日程计划关联。', - 'reservations.confirmed': '已确认', - 'reservations.pending': '待确认', - 'reservations.summary': '{confirmed} 已确认,{pending} 待确认', - 'reservations.fromPlan': '来自计划', - 'reservations.showFiles': '查看文件', - 'reservations.editTitle': '编辑预订', - 'reservations.status': '状态', - 'reservations.datetime': '日期和时间', - 'reservations.startTime': '开始时间', - 'reservations.endTime': '结束时间', - 'reservations.date': '日期', - 'reservations.time': '时间', - 'reservations.timeAlt': '时间(备选,如 19:30)', - 'reservations.notes': '备注', - 'reservations.notesPlaceholder': '其他备注...', - 'reservations.meta.airline': '航空公司', - 'reservations.meta.flightNumber': '航班号', - 'reservations.meta.from': '出发', - 'reservations.meta.to': '到达', - 'reservations.needsReview': '待确认', - 'reservations.needsReviewHint': '无法自动匹配机场 — 请确认位置。', - 'reservations.searchLocation': '搜索车站、港口、地址...', - 'airport.searchPlaceholder': '机场代码或城市(如 FRA)', - 'map.connections': '连接', - 'map.showConnections': '显示预订路线', - 'map.hideConnections': '隐藏预订路线', - 'settings.bookingLabels': '预订路线标签', - 'settings.bookingLabelsHint': '在地图上显示车站 / 机场名称。关闭时仅显示图标。', - 'reservations.meta.trainNumber': '车次', - 'reservations.meta.platform': '站台', - 'reservations.meta.seat': '座位', - 'reservations.meta.checkIn': '入住', - 'reservations.meta.checkInUntil': '入住截止', - 'reservations.meta.checkOut': '退房', - 'reservations.meta.linkAccommodation': '住宿', - 'reservations.meta.pickAccommodation': '关联住宿', - 'reservations.meta.noAccommodation': '无', - 'reservations.meta.hotelPlace': '住宿', - 'reservations.meta.pickHotel': '选择住宿', - 'reservations.meta.fromDay': '从', - 'reservations.meta.toDay': '到', - 'reservations.meta.selectDay': '选择日期', - 'reservations.type.flight': '航班', - 'reservations.type.hotel': '住宿', - 'reservations.type.restaurant': '餐厅', - 'reservations.type.train': '火车', - 'reservations.type.car': '汽车', - 'reservations.type.cruise': '邮轮', - 'reservations.type.event': '活动', - 'reservations.type.tour': '旅游团', - 'reservations.type.other': '其他', - 'reservations.confirm.delete': '确定要删除预订「{name}」吗?', - 'reservations.confirm.deleteTitle': '删除预订?', - 'reservations.confirm.deleteBody': '"{name}" 将被永久删除。', - 'reservations.toast.updated': '预订已更新', - 'reservations.toast.removed': '预订已删除', - 'reservations.toast.fileUploaded': '文件已上传', - 'reservations.toast.uploadError': '上传失败', - 'reservations.newTitle': '新建预订', - 'reservations.bookingType': '预订类型', - 'reservations.titleLabel': '标题', - 'reservations.titlePlaceholder': '如:汉莎 LH123、阿德隆酒店...', - 'reservations.locationAddress': '地点 / 地址', - 'reservations.locationPlaceholder': '地址、机场、酒店...', - 'reservations.confirmationCode': '预订码', - 'reservations.confirmationPlaceholder': '如:ABC12345', - 'reservations.day': '日期', - 'reservations.noDay': '无日期', - 'reservations.place': '地点', - 'reservations.noPlace': '无地点', - 'reservations.pendingSave': '将被保存…', - 'reservations.uploading': '上传中...', - 'reservations.attachFile': '附加文件', - 'reservations.linkExisting': '关联已有文件', - 'reservations.toast.saveError': '保存失败', - 'reservations.toast.updateError': '更新失败', - 'reservations.toast.deleteError': '删除失败', - 'reservations.confirm.remove': '移除「{name}」的预订?', - 'reservations.linkAssignment': '关联日程分配', - 'reservations.pickAssignment': '从计划中选择一个分配...', - 'reservations.noAssignment': '无关联(独立)', - 'reservations.price': '价格', - 'reservations.budgetCategory': '预算类别', - 'reservations.budgetCategoryPlaceholder': '例:交通、住宿', - 'reservations.budgetCategoryAuto': '自动(按预订类型)', - 'reservations.budgetHint': '保存时将自动创建预算条目。', - 'reservations.departureDate': '出发', - 'reservations.arrivalDate': '到达', - 'reservations.departureTime': '出发时间', - 'reservations.arrivalTime': '到达时间', - 'reservations.pickupDate': '取车', - 'reservations.returnDate': '还车', - 'reservations.pickupTime': '取车时间', - 'reservations.returnTime': '还车时间', - 'reservations.endDate': '结束日期', - 'reservations.meta.departureTimezone': '出发时区', - 'reservations.meta.arrivalTimezone': '到达时区', - 'reservations.span.departure': '出发', - 'reservations.span.arrival': '到达', - 'reservations.span.inTransit': '途中', - 'reservations.span.pickup': '取车', - 'reservations.span.return': '还车', - 'reservations.span.active': '使用中', - 'reservations.span.start': '开始', - 'reservations.span.end': '结束', - 'reservations.span.ongoing': '进行中', - 'reservations.validation.endBeforeStart': '结束日期/时间必须晚于开始日期/时间', - 'reservations.addBooking': '添加预订', - - // Budget - 'budget.title': '预算', - 'budget.exportCsv': '导出 CSV', - 'budget.emptyTitle': '尚未创建预算', - 'budget.emptyText': '创建分类和条目来规划旅行预算', - 'budget.emptyPlaceholder': '输入分类名称...', - 'budget.createCategory': '创建分类', - 'budget.category': '分类', - 'budget.categoryName': '分类名称', - 'budget.table.name': '名称', - 'budget.table.total': '合计', - 'budget.table.persons': '人数', - 'budget.table.days': '天数', - 'budget.table.perPerson': '人均', - 'budget.table.perDay': '日均', - 'budget.table.perPersonDay': '人日均', - 'budget.table.note': '备注', - 'budget.table.date': '日期', - 'budget.newEntry': '新建条目', - 'budget.defaultEntry': '新建条目', - 'budget.defaultCategory': '新分类', - 'budget.total': '合计', - 'budget.totalBudget': '总预算', - 'budget.byCategory': '按分类', - 'budget.editTooltip': '点击编辑', - 'budget.linkedToReservation': '已关联到预订——请在那里编辑名称', - 'budget.confirm.deleteCategory': '确定删除分类「{name}」及其 {count} 个条目?', - 'budget.deleteCategory': '删除分类', - 'budget.perPerson': '人均', - 'budget.paid': '已支付', - 'budget.open': '未支付', - 'budget.noMembers': '未分配成员', - 'budget.settlement': '结算', - 'budget.settlementInfo': '点击预算项目上的成员头像将其标记为绿色——表示该成员已付款。结算会显示谁欠谁多少。', - 'budget.netBalances': '净余额', - - // Files - 'files.title': '文件', - 'files.pageTitle': '文件与文档', - 'files.subtitle': '{trip} 的 {count} 个文件', - 'files.download': '下载', - 'files.openError': '无法打开文件', - 'files.downloadPdf': '下载 PDF', - 'files.count': '{count} 个文件', - 'files.countSingular': '1 个文件', - 'files.uploaded': '已上传 {count} 个', - 'files.uploadError': '上传失败', - 'files.dropzone': '将文件拖放到此处', - 'files.dropzoneHint': '或点击浏览', - 'files.allowedTypes': '图片、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大 50 MB', - 'files.uploading': '上传中...', - 'files.filterAll': '全部', - 'files.filterPdf': 'PDF', - 'files.filterImages': '图片', - 'files.filterDocs': '文档', - 'files.filterCollab': '协作笔记', - 'files.sourceCollab': '来自协作笔记', - 'files.empty': '暂无文件', - 'files.emptyHint': '上传文件以附加到旅行中', - 'files.openTab': '在新标签页中打开', - 'files.confirm.delete': '确定要删除此文件吗?', - 'files.toast.deleted': '文件已删除', - 'files.toast.deleteError': '删除文件失败', - 'files.sourcePlan': '日程计划', - 'files.sourceBooking': '预订', - 'files.sourceTransport': '交通', - 'files.attach': '附加', - 'files.pasteHint': '也可以从剪贴板粘贴图片 (Ctrl+V)', - 'files.trash': '回收站', - 'files.trashEmpty': '回收站为空', - 'files.emptyTrash': '清空回收站', - 'files.restore': '恢复', - 'files.star': '收藏', - 'files.unstar': '取消收藏', - 'files.assign': '分配', - 'files.assignTitle': '分配文件', - 'files.assignPlace': '地点', - 'files.assignBooking': '预订', - 'files.assignTransport': '交通', - 'files.unassigned': '未分配', - 'files.unlink': '移除关联', - 'files.toast.trashed': '已移至回收站', - 'files.toast.restored': '文件已恢复', - 'files.toast.trashEmptied': '回收站已清空', - 'files.toast.assigned': '文件已分配', - 'files.toast.assignError': '分配失败', - 'files.toast.restoreError': '恢复失败', - 'files.confirm.permanentDelete': '永久删除此文件?此操作无法撤销。', - 'files.confirm.emptyTrash': '永久删除回收站中的所有文件?此操作无法撤销。', - 'files.noteLabel': '备注', - 'files.notePlaceholder': '添加备注...', - - // Packing - 'packing.title': '行李清单', - 'packing.empty': '行李清单为空', - 'packing.import': '导入', - 'packing.importTitle': '导入装箱清单', - 'packing.importHint': '每行一个物品。可选用逗号、分号或制表符分隔类别和数量:名称, 类别, 数量', - 'packing.importPlaceholder': '牙刷\n防晒霜, 卫生\nT恤, 衣物, 5\n护照, 证件', - 'packing.importCsv': '加载 CSV/TXT', - 'packing.importAction': '导入 {count}', - 'packing.importSuccess': '已导入 {count} 项', - 'packing.importError': '导入失败', - 'packing.importEmpty': '没有可导入的项目', - 'packing.progress': '已打包 {packed}/{total}({percent}%)', - 'packing.clearChecked': '移除 {count} 个已勾选', - 'packing.clearCheckedShort': '移除 {count} 个', - 'packing.suggestions': '建议', - 'packing.suggestionsTitle': '添加建议', - 'packing.allSuggested': '所有建议已添加', - 'packing.allPacked': '全部打包完成!', - 'packing.addPlaceholder': '添加新物品...', - 'packing.categoryPlaceholder': '分类...', - 'packing.filterAll': '全部', - 'packing.filterOpen': '未完成', - 'packing.filterDone': '已完成', - 'packing.emptyTitle': '行李清单为空', - 'packing.emptyHint': '添加物品或使用建议', - 'packing.emptyFiltered': '没有匹配的物品', - 'packing.menuRename': '重命名', - 'packing.menuCheckAll': '全部勾选', - 'packing.menuUncheckAll': '取消全部勾选', - 'packing.menuDeleteCat': '删除分类', - 'packing.addItem': '添加物品', - 'packing.addItemPlaceholder': '物品名称...', - 'packing.addCategory': '添加分类', - 'packing.newCategoryPlaceholder': '分类名称(如:衣物)', - 'packing.applyTemplate': '应用模板', - 'packing.template': '模板', - 'packing.templateApplied': '已从模板添加 {count} 个物品', - 'packing.templateError': '应用模板失败', - 'packing.saveAsTemplate': '保存为模板', - 'packing.templateName': '模板名称', - 'packing.templateSaved': '行李清单已保存为模板', - 'packing.noMembers': '无成员', - 'packing.bags': '行李', - 'packing.noBag': '未分配', - 'packing.totalWeight': '总重量', - 'packing.bagName': '名称...', - 'packing.addBag': '添加行李', - 'packing.changeCategory': '更改分类', - 'packing.confirm.clearChecked': '确定移除 {count} 个已勾选的物品?', - 'packing.confirm.deleteCat': '确定删除分类「{name}」及其 {count} 个物品?', - 'packing.defaultCategory': '其他', - 'packing.toast.saveError': '保存失败', - 'packing.toast.deleteError': '删除失败', - 'packing.toast.renameError': '重命名失败', - 'packing.toast.addError': '添加失败', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: '护照', category: '证件' }, - { name: '身份证', category: '证件' }, - { name: '旅行保险', category: '证件' }, - { name: '机票', category: '证件' }, - { name: '信用卡', category: '财务' }, - { name: '现金', category: '财务' }, - { name: '签证', category: '证件' }, - { name: 'T恤', category: '衣物' }, - { name: '裤子', category: '衣物' }, - { name: '内衣', category: '衣物' }, - { name: '袜子', category: '衣物' }, - { name: '外套', category: '衣物' }, - { name: '睡衣', category: '衣物' }, - { name: '泳衣', category: '衣物' }, - { name: '雨衣', category: '衣物' }, - { name: '舒适的鞋子', category: '衣物' }, - { name: '牙刷', category: '洗漱用品' }, - { name: '牙膏', category: '洗漱用品' }, - { name: '洗发水', category: '洗漱用品' }, - { name: '除臭剂', category: '洗漱用品' }, - { name: '防晒霜', category: '洗漱用品' }, - { name: '剃须刀', category: '洗漱用品' }, - { name: '充电器', category: '电子产品' }, - { name: '充电宝', category: '电子产品' }, - { name: '耳机', category: '电子产品' }, - { name: '旅行转换插头', category: '电子产品' }, - { name: '相机', category: '电子产品' }, - { name: '止痛药', category: '健康' }, - { name: '创可贴', category: '健康' }, - { name: '消毒液', category: '健康' }, - ], - - // Members / Sharing - 'members.shareTrip': '分享旅行', - 'members.inviteUser': '邀请用户', - 'members.selectUser': '选择用户…', - 'members.invite': '邀请', - 'members.allHaveAccess': '所有用户均已拥有访问权限。', - 'members.access': '访问权限', - 'members.person': '人', - 'members.persons': '人', - 'members.you': '你', - 'members.owner': '所有者', - 'members.leaveTrip': '退出旅行', - 'members.removeAccess': '移除访问权限', - 'members.confirmLeave': '退出旅行?你将失去访问权限。', - 'members.confirmRemove': '移除该用户的访问权限?', - 'members.loadError': '加载成员失败', - 'members.added': '已添加', - 'members.addError': '添加失败', - 'members.removed': '成员已移除', - 'members.removeError': '移除失败', - - // Categories (Admin) - 'categories.title': '分类', - 'categories.subtitle': '管理地点分类', - 'categories.new': '新建分类', - 'categories.empty': '暂无分类', - 'categories.namePlaceholder': '分类名称', - 'categories.icon': '图标', - 'categories.color': '颜色', - 'categories.customColor': '选择自定义颜色', - 'categories.preview': '预览', - 'categories.defaultName': '分类', - 'categories.update': '更新', - 'categories.create': '创建', - 'categories.confirm.delete': '删除分类?该分类下的地点不会被删除。', - 'categories.toast.loadError': '加载分类失败', - 'categories.toast.nameRequired': '请输入名称', - 'categories.toast.updated': '分类已更新', - 'categories.toast.created': '分类已创建', - 'categories.toast.saveError': '保存失败', - 'categories.toast.deleted': '分类已删除', - 'categories.toast.deleteError': '删除失败', - - // Backup (Admin) - 'backup.title': '数据备份', - 'backup.subtitle': '数据库和所有上传文件', - 'backup.refresh': '刷新', - 'backup.upload': '上传备份', - 'backup.uploading': '上传中…', - 'backup.create': '创建备份', - 'backup.creating': '创建中…', - 'backup.empty': '暂无备份', - 'backup.createFirst': '创建第一个备份', - 'backup.download': '下载', - 'backup.restore': '恢复', - 'backup.confirm.restore': '恢复备份「{name}」?\n\n所有当前数据将被备份数据替换。', - 'backup.confirm.uploadRestore': '上传并恢复备份文件「{name}」?\n\n所有当前数据将被覆盖。', - 'backup.confirm.delete': '删除备份「{name}」?', - 'backup.toast.loadError': '加载备份失败', - 'backup.toast.created': '备份创建成功', - 'backup.toast.createError': '创建备份失败', - 'backup.toast.restored': '备份已恢复。页面即将刷新…', - 'backup.toast.restoreError': '恢复失败', - 'backup.toast.uploadError': '上传失败', - 'backup.toast.deleted': '备份已删除', - 'backup.toast.deleteError': '删除失败', - 'backup.toast.downloadError': '下载失败', - 'backup.toast.settingsSaved': '自动备份设置已保存', - 'backup.toast.settingsError': '保存设置失败', - 'backup.auto.title': '自动备份', - 'backup.auto.subtitle': '按计划自动备份', - 'backup.auto.enable': '启用自动备份', - 'backup.auto.enableHint': '将按所选计划自动创建备份', - 'backup.auto.interval': '间隔', - 'backup.auto.hour': '执行时间', - 'backup.auto.hourHint': '服务器本地时间({format} 格式)', - 'backup.auto.dayOfWeek': '星期几', - 'backup.auto.dayOfMonth': '每月几号', - 'backup.auto.dayOfMonthHint': '限 1–28 以兼容所有月份', - 'backup.auto.scheduleSummary': '计划', - 'backup.auto.summaryDaily': '每天 {hour}:00', - 'backup.auto.summaryWeekly': '每{day} {hour}:00', - 'backup.auto.summaryMonthly': '每月 {day} 号 {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': '自动备份通过 Docker 环境变量配置。要更改设置,请更新 docker-compose.yml 并重启容器。', - 'backup.auto.copyEnv': '复制 Docker 环境变量', - 'backup.auto.envCopied': 'Docker 环境变量已复制到剪贴板', - 'backup.auto.keepLabel': '自动删除旧备份', - 'backup.dow.sunday': '周日', - 'backup.dow.monday': '周一', - 'backup.dow.tuesday': '周二', - 'backup.dow.wednesday': '周三', - 'backup.dow.thursday': '周四', - 'backup.dow.friday': '周五', - 'backup.dow.saturday': '周六', - 'backup.interval.hourly': '每小时', - 'backup.interval.daily': '每天', - 'backup.interval.weekly': '每周', - 'backup.interval.monthly': '每月', - 'backup.keep.1day': '1 天', - 'backup.keep.3days': '3 天', - 'backup.keep.7days': '7 天', - 'backup.keep.14days': '14 天', - 'backup.keep.30days': '30 天', - 'backup.keep.forever': '永久保留', - - // Photos - 'photos.title': '照片', - 'photos.subtitle': '{trip} 的 {count} 张照片', - 'photos.dropHere': '将照片拖放至此...', - 'photos.dropHereActive': '将照片拖放至此', - 'photos.captionForAll': '标题(所有)', - 'photos.captionPlaceholder': '可选标题...', - 'photos.addCaption': '添加标题...', - 'photos.allDays': '所有天', - 'photos.noPhotos': '暂无照片', - 'photos.uploadHint': '上传你的旅行照片', - 'photos.clickToSelect': '或点击选择', - 'photos.linkPlace': '关联地点', - 'photos.noPlace': '无地点', - 'photos.uploadN': '上传 {n} 张照片', - 'photos.linkDay': '关联天数', - 'photos.noDay': '无天数', - 'photos.dayLabel': '第 {number} 天', - 'photos.photoSelected': '张照片已选择', - 'photos.photosSelected': '张照片已选择', - 'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 张照片', - - // Backup restore modal - 'backup.restoreConfirmTitle': '恢复备份?', - 'backup.restoreWarning': '所有当前数据(旅行、地点、用户、上传文件)将被备份数据永久替换。此操作无法撤销。', - 'backup.restoreTip': '提示:恢复前建议先备份当前状态。', - 'backup.restoreConfirm': '确认恢复', - - // PDF - 'pdf.travelPlan': '旅行计划', - 'pdf.planned': '已规划', - 'pdf.costLabel': '费用 EUR', - 'pdf.preview': 'PDF 预览', - 'pdf.saveAsPdf': '保存为 PDF', - - // Planner - 'planner.places': '地点', - 'planner.bookings': '预订', - 'planner.packingList': '行李清单', - 'planner.documents': '文档', - 'planner.dayPlan': '日程计划', - 'planner.reservations': '预订', - 'planner.minTwoPlaces': '至少需要 2 个有坐标的地点', - 'planner.noGeoPlaces': '没有有坐标的地点', - 'planner.routeCalculated': '路线已计算', - 'planner.routeCalcFailed': '无法计算路线', - 'planner.routeError': '路线计算错误', - 'planner.icsExportFailed': 'ICS 导出失败', - 'planner.routeOptimized': '路线已优化', - 'planner.reservationUpdated': '预订已更新', - 'planner.reservationAdded': '预订已添加', - 'planner.confirmDeleteReservation': '删除预订?', - 'planner.reservationDeleted': '预订已删除', - 'planner.days': '天', - 'planner.allPlaces': '所有地点', - 'planner.totalPlaces': '共 {n} 个地点', - 'planner.noDaysPlanned': '尚未规划天数', - 'planner.editTrip': '编辑旅行 \u2192', - 'planner.placeOne': '1 个地点', - 'planner.placeN': '{n} 个地点', - 'planner.addNote': '添加备注', - 'planner.noEntries': '当天无条目', - 'planner.addPlace': '添加地点/活动', - 'planner.addPlaceShort': '+ 添加地点/活动', - 'planner.resPending': '预订待确认 · ', - 'planner.resConfirmed': '预订已确认 · ', - 'planner.notePlaceholder': '备注…', - 'planner.noteTimePlaceholder': '时间(可选)', - 'planner.noteExamplePlaceholder': '如:14:30 从中央车站乘 S3,7 号码头渡轮,午餐休息…', - 'planner.totalCost': '总费用', - 'planner.searchPlaces': '搜索地点…', - 'planner.allCategories': '所有分类', - 'planner.noPlacesFound': '未找到地点', - 'planner.addFirstPlace': '添加第一个地点', - 'planner.noReservations': '暂无预订', - 'planner.addFirstReservation': '添加第一个预订', - 'planner.new': '新建', - 'planner.addToDay': '+ 天', - 'planner.calculating': '计算中…', - 'planner.route': '路线', - 'planner.optimize': '优化', - 'planner.openGoogleMaps': '在 Google Maps 中打开', - 'planner.selectDayHint': '从左侧列表选择一天以查看日程计划', - 'planner.noPlacesForDay': '当天暂无地点', - 'planner.addPlacesLink': '添加地点 \u2192', - 'planner.minTotal': '分钟 合计', - 'planner.noReservation': '无预订', - 'planner.removeFromDay': '从当天移除', - 'planner.addToThisDay': '添加到当天', - 'planner.overview': '概览', - 'planner.noDays': '暂无天数', - 'planner.editTripToAddDays': '编辑旅行以添加天数', - 'planner.dayCount': '{n} 天', - 'planner.clickToUnlock': '点击解锁', - 'planner.keepPosition': '路线优化时保持位置', - 'planner.dayDetails': '日程详情', - 'planner.dayN': '第 {n} 天', - - // Dashboard Stats - 'stats.countries': '国家', - 'stats.cities': '城市', - 'stats.trips': '旅行', - 'stats.places': '地点', - 'stats.worldProgress': '全球进度', - 'stats.visited': '已访问', - 'stats.remaining': '未访问', - 'stats.visitedCountries': '已访问国家', - - // Day Detail Panel - 'day.precipProb': '降水概率', - 'day.precipitation': '降水量', - 'day.wind': '风速', - 'day.sunrise': '日出', - 'day.sunset': '日落', - 'day.hourlyForecast': '逐小时预报', - 'day.climateHint': '历史平均值——实际预报在该日期前 16 天内可用。', - 'day.noWeather': '无天气数据。请添加有坐标的地点。', - 'day.overview': '每日概览', - 'day.accommodation': '住宿', - 'day.addAccommodation': '添加住宿', - 'day.hotelDayRange': '应用到天数', - 'day.noPlacesForHotel': '请先在旅行中添加地点', - 'day.allDays': '全部', - 'day.checkIn': '入住', - 'day.checkInUntil': '截止', - 'day.checkOut': '退房', - 'day.confirmation': '确认号', - 'day.editAccommodation': '编辑住宿', - 'day.reservations': '预订', - - // Memories / Immich - 'memories.title': '照片', - 'memories.notConnected': 'Immich 未连接', - 'memories.notConnectedHint': '在设置中连接您的 Immich 实例以在此查看旅行照片。', - 'memories.notConnectedMultipleHint': '请在设置中连接以下任一照片提供商:{provider_names},以便向此行程添加照片。', - 'memories.noDates': '为旅行添加日期以加载照片。', - 'memories.noPhotos': '未找到照片', - 'memories.noPhotosHint': 'Immich 中未找到此旅行日期范围内的照片。', - 'memories.photosFound': '张照片', - 'memories.fromOthers': '来自他人', - 'memories.sharePhotos': '分享照片', - 'memories.sharing': '分享中', - 'memories.reviewTitle': '审查您的照片', - 'memories.reviewHint': '点击照片以将其从分享中排除。', - 'memories.shareCount': '分享 {count} 张照片', - 'memories.providerUrl': '服务器 URL', - 'memories.providerApiKey': 'API 密钥', - 'memories.providerUsername': '用户名', - 'memories.providerPassword': '密码', - 'memories.providerOTP': 'MFA 验证码(如已启用)', - 'memories.skipSSLVerification': '跳过 SSL 证书验证', - 'memories.immichAutoUpload': '上传 Journey 照片时同步到 Immich', - 'memories.providerUrlHintSynology': '在 URL 中包含照片应用路径,例如 https://nas:5001/photo', - 'memories.testConnection': '测试连接', - 'memories.testShort': '测试', - 'memories.testFirst': '请先测试连接', - 'memories.connected': '已连接', - 'memories.disconnected': '未连接', - 'memories.connectionSuccess': '已连接到 Immich', - 'memories.connectionError': '无法连接到 Immich', - 'memories.saved': '{provider_name} 设置已保存', - 'memories.providerDisconnectedBanner': '您与 {provider_name} 的连接已断开。请在设置中重新连接以查看照片。', - 'memories.saveError': '无法保存 {provider_name} 设置', - 'memories.oldest': '最早优先', - 'memories.newest': '最新优先', - 'memories.allLocations': '所有地点', - 'memories.addPhotos': '添加照片', - 'memories.linkAlbum': '关联相册', - 'memories.selectAlbum': '选择 Immich 相册', - 'memories.selectAlbumMultiple': '选择相册', - 'memories.noAlbums': '未找到相册', - 'memories.syncAlbum': '同步相册', - 'memories.unlinkAlbum': '取消关联', - 'memories.photos': '张照片', - 'memories.selectPhotos': '从 Immich 选择照片', - 'memories.selectPhotosMultiple': '选择照片', - 'memories.selectHint': '点击照片以选择。', - 'memories.selected': '已选择', - 'memories.addSelected': '添加 {count} 张照片', - 'memories.alreadyAdded': '已添加', - 'memories.private': '私密', - 'memories.stopSharing': '停止分享', - 'memories.tripDates': '旅行日期', - 'memories.allPhotos': '所有照片', - 'memories.confirmShareTitle': '与旅行成员分享?', - 'memories.confirmShareHint': '{count} 张照片将对本次旅行的所有成员可见。你可以稍后将单张照片设为私密。', - 'memories.confirmShareButton': '分享照片', - - // Collab Addon - 'collab.tabs.chat': '聊天', - 'collab.tabs.notes': '笔记', - 'collab.tabs.polls': '投票', - 'collab.whatsNext.title': '接下来', - 'collab.whatsNext.today': '今天', - 'collab.whatsNext.tomorrow': '明天', - 'collab.whatsNext.empty': '暂无活动', - 'collab.whatsNext.until': '至', - 'collab.whatsNext.emptyHint': '有时间安排的活动将显示在此', - 'collab.chat.send': '发送', - 'collab.chat.placeholder': '输入消息...', - 'collab.chat.empty': '开始对话', - 'collab.chat.emptyHint': '消息对所有旅行成员可见', - 'collab.chat.emptyDesc': '与旅伴分享想法、计划和动态', - 'collab.chat.today': '今天', - 'collab.chat.yesterday': '昨天', - 'collab.chat.deletedMessage': '删除了一条消息', - 'collab.chat.reply': '回复', - 'collab.chat.loadMore': '加载更早的消息', - 'collab.chat.justNow': '刚刚', - 'collab.chat.minutesAgo': '{n} 分钟前', - 'collab.chat.hoursAgo': '{n} 小时前', - 'collab.notes.title': '笔记', - 'collab.notes.new': '新建笔记', - 'collab.notes.empty': '暂无笔记', - 'collab.notes.emptyHint': '开始记录想法和计划', - 'collab.notes.all': '全部', - 'collab.notes.titlePlaceholder': '笔记标题', - 'collab.notes.contentPlaceholder': '写点什么...', - 'collab.notes.categoryPlaceholder': '分类', - 'collab.notes.newCategory': '新建分类...', - 'collab.notes.category': '分类', - 'collab.notes.noCategory': '无分类', - 'collab.notes.color': '颜色', - 'collab.notes.save': '保存', - 'collab.notes.cancel': '取消', - 'collab.notes.edit': '编辑', - 'collab.notes.delete': '删除', - 'collab.notes.pin': '置顶', - 'collab.notes.unpin': '取消置顶', - 'collab.notes.daysAgo': '{n} 天前', - 'collab.notes.categorySettings': '管理分类', - 'collab.notes.create': '创建', - 'collab.notes.website': '网站', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': '附加文件', - 'collab.notes.noCategoriesYet': '暂无分类', - 'collab.notes.emptyDesc': '创建一个笔记开始吧', - 'collab.polls.title': '投票', - 'collab.polls.new': '新建投票', - 'collab.polls.empty': '暂无投票', - 'collab.polls.emptyHint': '向团队提问并一起投票', - 'collab.polls.question': '问题', - 'collab.polls.questionPlaceholder': '我们应该做什么?', - 'collab.polls.addOption': '+ 添加选项', - 'collab.polls.optionPlaceholder': '选项 {n}', - 'collab.polls.create': '创建投票', - 'collab.polls.close': '关闭', - 'collab.polls.closed': '已关闭', - 'collab.polls.votes': '{n} 票', - 'collab.polls.vote': '{n} 票', - 'collab.polls.multipleChoice': '多选', - 'collab.polls.multiChoice': '多选', - 'collab.polls.deadline': '截止时间', - 'collab.polls.option': '选项', - 'collab.polls.options': '选项', - 'collab.polls.delete': '删除', - 'collab.polls.closedSection': '已关闭', - - // Permissions - 'admin.tabs.permissions': '权限', - 'perm.title': '权限设置', - 'perm.subtitle': '控制谁可以在应用中执行操作', - 'perm.saved': '权限设置已保存', - 'perm.resetDefaults': '恢复默认', - 'perm.customized': '已自定义', - 'perm.level.admin': '仅管理员', - 'perm.level.tripOwner': '旅行所有者', - 'perm.level.tripMember': '旅行成员', - 'perm.level.everybody': '所有人', - 'perm.cat.trip': '旅行管理', - 'perm.cat.members': '成员管理', - 'perm.cat.files': '文件', - 'perm.cat.content': '内容与日程', - 'perm.cat.extras': '预算、行李与协作', - 'perm.action.trip_create': '创建旅行', - 'perm.action.trip_edit': '编辑旅行详情', - 'perm.action.trip_delete': '删除旅行', - 'perm.action.trip_archive': '归档 / 取消归档旅行', - 'perm.action.trip_cover_upload': '上传封面图片', - 'perm.action.member_manage': '添加 / 移除成员', - 'perm.action.file_upload': '上传文件', - 'perm.action.file_edit': '编辑文件元数据', - 'perm.action.file_delete': '删除文件', - 'perm.action.place_edit': '添加 / 编辑 / 删除地点', - 'perm.action.day_edit': '编辑日程、备注与分配', - 'perm.action.reservation_edit': '管理预订', - 'perm.action.budget_edit': '管理预算', - 'perm.action.packing_edit': '管理行李清单', - 'perm.action.collab_edit': '协作(笔记、投票、聊天)', - 'perm.action.share_manage': '管理分享链接', - 'perm.actionHint.trip_create': '谁可以创建新旅行', - 'perm.actionHint.trip_edit': '谁可以更改旅行名称、日期、描述和货币', - 'perm.actionHint.trip_delete': '谁可以永久删除旅行', - 'perm.actionHint.trip_archive': '谁可以归档或取消归档旅行', - 'perm.actionHint.trip_cover_upload': '谁可以上传或更改封面图片', - 'perm.actionHint.member_manage': '谁可以邀请或移除旅行成员', - 'perm.actionHint.file_upload': '谁可以向旅行上传文件', - 'perm.actionHint.file_edit': '谁可以编辑文件描述和链接', - 'perm.actionHint.file_delete': '谁可以将文件移至回收站或永久删除', - 'perm.actionHint.place_edit': '谁可以添加、编辑或删除地点', - 'perm.actionHint.day_edit': '谁可以编辑日程、日程备注和地点分配', - 'perm.actionHint.reservation_edit': '谁可以创建、编辑或删除预订', - 'perm.actionHint.budget_edit': '谁可以创建、编辑或删除预算项目', - 'perm.actionHint.packing_edit': '谁可以管理行李物品和包袋', - 'perm.actionHint.collab_edit': '谁可以创建笔记、投票和发送消息', - 'perm.actionHint.share_manage': '谁可以创建或删除公开分享链接', - // Undo - 'undo.button': '撤销', - 'undo.tooltip': '撤销:{action}', - 'undo.assignPlace': '地点已分配至某天', - 'undo.removeAssignment': '地点已从某天移除', - 'undo.reorder': '地点已重新排序', - 'undo.optimize': '路线已优化', - 'undo.deletePlace': '地点已删除', - 'undo.deletePlaces': '地点已删除', - 'undo.moveDay': '地点已移至另一天', - 'undo.lock': '地点锁定已切换', - 'undo.importGpx': 'GPX 导入', - 'undo.importKeyholeMarkup': 'KMZ/KML 导入', - 'undo.importGoogleList': 'Google 地图导入', - 'undo.importNaverList': 'Naver 地图导入', - - // Notifications - 'notifications.title': '通知', - 'notifications.markAllRead': '全部标为已读', - 'notifications.deleteAll': '全部删除', - 'notifications.showAll': '查看所有通知', - 'notifications.empty': '暂无通知', - 'notifications.emptyDescription': '您已全部查阅!', - 'notifications.all': '全部', - 'notifications.unreadOnly': '未读', - 'notifications.markRead': '标为已读', - 'notifications.markUnread': '标为未读', - 'notifications.delete': '删除', - 'notifications.system': '系统', - 'notifications.synologySessionCleared.title': 'Synology Photos 已断开连接', - 'notifications.synologySessionCleared.text': '您的服务器或账户已更改 — 请前往设置重新测试您的连接。', - 'memories.error.loadAlbums': '加载相册失败', - 'memories.error.linkAlbum': '关联相册失败', - 'memories.error.unlinkAlbum': '取消关联相册失败', - 'memories.error.syncAlbum': '同步相册失败', - 'memories.error.loadPhotos': '加载照片失败', - 'memories.error.addPhotos': '添加照片失败', - 'memories.error.removePhoto': '删除照片失败', - 'memories.error.toggleSharing': '更新共享设置失败', - 'undo.addPlace': '地点已添加', - 'undo.done': '已撤销:{action}', - 'notifications.test.title': '来自 {actor} 的测试通知', - 'notifications.test.text': '这是一条简单的测试通知。', - 'notifications.test.booleanTitle': '{actor} 请求您的审批', - 'notifications.test.booleanText': '测试布尔通知。', - 'notifications.test.accept': '批准', - 'notifications.test.decline': '拒绝', - 'notifications.test.navigateTitle': '查看详情', - 'notifications.test.navigateText': '测试跳转通知。', - 'notifications.test.goThere': '前往', - 'notifications.test.adminTitle': '管理员广播', - 'notifications.test.adminText': '{actor} 向所有管理员发送了测试通知。', - 'notifications.test.tripTitle': '{actor} 在您的行程中发帖', - 'notifications.test.tripText': '行程"{trip}"的测试通知。', - - // Todo - 'todo.subtab.packing': '行李清单', - 'todo.subtab.todo': '待办事项', - 'todo.completed': '已完成', - 'todo.filter.all': '全部', - 'todo.filter.open': '进行中', - 'todo.filter.done': '已完成', - 'todo.uncategorized': '未分类', - 'todo.namePlaceholder': '任务名称', - 'todo.descriptionPlaceholder': '描述(可选)', - 'todo.unassigned': '未分配', - 'todo.noCategory': '无分类', - 'todo.hasDescription': '有描述', - 'todo.addItem': '新建任务', - 'todo.sidebar.sortBy': '排序方式', - 'todo.priority': '优先级', - 'todo.newCategoryLabel': '新建', - 'budget.categoriesLabel': '类别', - 'todo.newCategory': '分类名称', - 'todo.addCategory': '添加分类', - 'todo.newItem': '新任务', - 'todo.empty': '暂无任务,添加一个任务开始吧!', - 'todo.filter.my': '我的任务', - 'todo.filter.overdue': '已逾期', - 'todo.sidebar.tasks': '任务', - 'todo.sidebar.categories': '分类', - 'todo.detail.title': '任务', - 'todo.detail.description': '描述', - 'todo.detail.category': '分类', - 'todo.detail.dueDate': '截止日期', - 'todo.detail.assignedTo': '分配给', - 'todo.detail.delete': '删除', - 'todo.detail.save': '保存更改', - 'todo.detail.create': '创建任务', - 'todo.detail.priority': '优先级', - 'todo.detail.noPriority': '无', - 'todo.sortByPrio': '优先级', - - // Notification system (added from feat/notification-system) - 'settings.notifyVersionAvailable': '有新版本可用', - 'settings.notificationPreferences.noChannels': '未配置通知渠道。请联系管理员设置电子邮件或 Webhook 通知。', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': '输入您的 Discord、Slack 或自定义 Webhook URL 以接收通知。', - 'settings.webhookUrl.saved': 'Webhook URL 已保存', - 'settings.webhookUrl.test': '测试', - 'settings.webhookUrl.testSuccess': '测试 Webhook 发送成功', - 'settings.webhookUrl.testFailed': '测试 Webhook 失败', - 'settings.ntfyUrl.topicLabel': 'Ntfy 主题', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy 服务器 URL(可选)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': '输入您的 Ntfy 主题以接收推送通知。将服务器留空以使用管理员配置的默认值。', - 'settings.ntfyUrl.tokenLabel': '访问令牌(可选)', - 'settings.ntfyUrl.tokenHint': '受密码保护的主题需要此项。', - 'settings.ntfyUrl.saved': 'Ntfy 设置已保存', - 'settings.ntfyUrl.test': '测试', - 'settings.ntfyUrl.testSuccess': '测试 Ntfy 通知发送成功', - 'settings.ntfyUrl.testFailed': '测试 Ntfy 通知失败', - 'settings.ntfyUrl.tokenCleared': '访问令牌已清除', - 'settings.notificationPreferences.inapp': 'In-App', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.email': 'Email', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'admin.notifications.emailPanel.title': 'Email (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': 'In-App', - 'admin.notifications.inappPanel.hint': '应用内通知始终处于活跃状态,无法全局禁用。', - 'admin.notifications.adminWebhookPanel.title': '管理员 Webhook', - 'admin.notifications.adminWebhookPanel.hint': '此 Webhook 专用于管理员通知(如版本更新提醒)。它与用户 Webhook 相互独立,配置 URL 后自动触发。', - 'admin.notifications.adminWebhookPanel.saved': '管理员 Webhook URL 已保存', - 'admin.notifications.adminWebhookPanel.testSuccess': '测试 Webhook 发送成功', - 'admin.notifications.adminWebhookPanel.testFailed': '测试 Webhook 失败', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 后管理员 Webhook 自动触发', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': '允许用户配置自己的 ntfy 主题以接收推送通知。在下方设置默认服务器以预填充用户设置。', - 'admin.notifications.testNtfy': '发送测试 Ntfy', - 'admin.notifications.testNtfySuccess': '测试 Ntfy 发送成功', - 'admin.notifications.testNtfyFailed': '测试 Ntfy 失败', - 'admin.notifications.adminNtfyPanel.title': '管理员 Ntfy', - 'admin.notifications.adminNtfyPanel.hint': '此 Ntfy 主题专用于管理员通知(如版本更新提醒)。它与每用户主题相互独立,配置后始终触发。', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 服务器 URL', - 'admin.notifications.adminNtfyPanel.serverHint': '同时用作用户 ntfy 通知的默认服务器。留空则默认使用 ntfy.sh。用户可在其自己的设置中覆盖此项。', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': '管理员主题', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': '访问令牌(可选)', - 'admin.notifications.adminNtfyPanel.tokenCleared': '管理员访问令牌已清除', - 'admin.notifications.adminNtfyPanel.saved': '管理员 Ntfy 设置已保存', - 'admin.notifications.adminNtfyPanel.test': '发送测试 Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': '测试 Ntfy 发送成功', - 'admin.notifications.adminNtfyPanel.testFailed': '测试 Ntfy 失败', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': '配置主题后管理员 Ntfy 始终触发', - 'admin.notifications.adminNotificationsHint': '配置哪些渠道发送管理员通知(如版本更新提醒)。设置管理员 Webhook URL 后,Webhook 将自动触发。', - 'admin.notifications.tripReminders.title': '行程提醒', - 'admin.notifications.tripReminders.hint': '在行程开始前发送提醒通知(需要在行程中设置提醒天数)。', - 'admin.notifications.tripReminders.enabled': '行程提醒已启用', - 'admin.notifications.tripReminders.disabled': '行程提醒已禁用', - 'admin.tabs.notifications': '通知', - 'notifications.versionAvailable.title': '有可用更新', - 'notifications.versionAvailable.text': 'TREK {version} 现已可用。', - 'notifications.versionAvailable.button': '查看详情', - 'notif.test.title': '[测试] 通知', - 'notif.test.simple.text': '这是一条简单的测试通知。', - 'notif.test.boolean.text': '您是否接受此测试通知?', - 'notif.test.navigate.text': '点击下方前往控制台。', - - // Notifications - 'notif.trip_invite.title': '旅行邀请', - 'notif.trip_invite.text': '{actor} 邀请您加入 {trip}', - 'notif.booking_change.title': '预订已更新', - 'notif.booking_change.text': '{actor} 更新了 {trip} 中的预订', - 'notif.trip_reminder.title': '旅行提醒', - 'notif.trip_reminder.text': '您的旅行 {trip} 即将开始!', - 'notif.todo_due.title': '待办事项即将到期', - 'notif.todo_due.text': '{trip} 中的 {todo} 将于 {due} 到期', - 'notif.vacay_invite.title': 'Vacay 融合邀请', - 'notif.vacay_invite.text': '{actor} 邀请您合并假期计划', - 'notif.photos_shared.title': '照片已分享', - 'notif.photos_shared.text': '{actor} 在 {trip} 中分享了 {count} 张照片', - 'notif.collab_message.title': '新消息', - 'notif.collab_message.text': '{actor} 在 {trip} 中发送了消息', - 'notif.packing_tagged.title': '行李分配', - 'notif.packing_tagged.text': '{actor} 将您分配到 {trip} 中的 {category}', - 'notif.version_available.title': '新版本可用', - 'notif.version_available.text': 'TREK {version} 现已可用', - 'notif.action.view_trip': '查看旅行', - 'notif.action.view_collab': '查看消息', - 'notif.action.view_packing': '查看行李', - 'notif.action.view_photos': '查看照片', - 'notif.action.view_vacay': '查看 Vacay', - 'notif.action.view_admin': '前往管理', - 'notif.action.view': '查看', - 'notif.action.accept': '接受', - 'notif.action.decline': '拒绝', - 'notif.generic.title': '通知', - 'notif.generic.text': '您有一条新通知', - 'notif.dev.unknown_event.title': '[DEV] 未知事件', - 'notif.dev.unknown_event.text': '事件类型 "{event}" 未在 EVENT_NOTIFICATION_CONFIG 中注册', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': '刚刚', - 'common.hoursAgo': '{count}小时前', - 'common.daysAgo': '{count}天前', - 'memories.saveRouteNotConfigured': '此提供商未配置保存路由', - 'memories.testRouteNotConfigured': '此提供商未配置测试路由', - 'memories.fillRequiredFields': '请填写所有必填字段', - 'journey.search.placeholder': '搜索旅程…', - 'journey.search.noResults': '没有与"{query}"匹配的旅程', - 'journey.title': '旅程', - 'journey.subtitle': '实时记录你的旅行', - 'journey.new': '新建旅程', - 'journey.create': '创建', - 'journey.titlePlaceholder': '你要去哪里?', - 'journey.empty': '还没有旅程', - 'journey.emptyHint': '开始记录你的下一次旅行', - 'journey.deleted': '旅程已删除', - 'journey.createError': '无法创建旅程', - 'journey.deleteError': '无法删除旅程', - 'journey.deleteConfirmTitle': '删除', - 'journey.deleteConfirmMessage': '删除"{title}"?此操作无法撤销。', - 'journey.deleteConfirmGeneric': '确定要删除吗?', - 'journey.notFound': '未找到旅程', - 'journey.photos': '照片', - 'journey.timelineEmpty': '还没有行程', - 'journey.timelineEmptyHint': '添加一个签到或写一篇日志开始记录', - 'journey.status.draft': '草稿', - 'journey.status.active': '进行中', - 'journey.status.completed': '已完成', - 'journey.status.upcoming': '即将开始', - 'journey.status.archived': '已归档', - 'journey.checkin.add': '签到', - 'journey.checkin.namePlaceholder': '地点名称', - 'journey.checkin.notesPlaceholder': '备注(可选)', - 'journey.checkin.save': '保存', - 'journey.checkin.error': '无法保存签到', - 'journey.entry.add': '日志', - 'journey.entry.edit': '编辑条目', - 'journey.entry.titlePlaceholder': '标题(可选)', - 'journey.entry.bodyPlaceholder': '今天发生了什么?', - 'journey.entry.save': '保存', - 'journey.entry.error': '无法保存条目', - 'journey.photo.add': '照片', - 'journey.photo.uploadError': '上传失败', - 'journey.share.share': '分享', - 'journey.share.public': '公开', - 'journey.share.linkCopied': '公开链接已复制', - 'journey.share.disabled': '已关闭公开分享', - 'journey.editor.titlePlaceholder': '给这个瞬间起个名字...', - 'journey.editor.bodyPlaceholder': '讲述这一天的故事...', - 'journey.editor.placePlaceholder': '地点(可选)', - 'journey.editor.tagsPlaceholder': '标签:隐藏宝藏、最佳美食、值得再去...', - 'journey.visibility.private': '私密', - 'journey.visibility.shared': '共享', - 'journey.visibility.public': '公开', - 'journey.emptyState.title': '你的故事从这里开始', - 'journey.emptyState.subtitle': '在某个地方签到或写下你的第一篇日志', - 'journey.frontpage.subtitle': '将旅行变成永远不会忘记的故事', - 'journey.frontpage.createJourney': '创建旅程', - 'journey.frontpage.activeJourney': '进行中的旅程', - 'journey.frontpage.allJourneys': '所有旅程', - 'journey.frontpage.journeys': '个旅程', - 'journey.frontpage.createNew': '创建新旅程', - 'journey.frontpage.createNewSub': '选择旅行、写故事、分享你的冒险', - 'journey.frontpage.live': '实时', - 'journey.frontpage.synced': '已同步', - 'journey.frontpage.continueWriting': '继续写作', - 'journey.frontpage.updated': '更新于 {time}', - 'journey.frontpage.suggestionLabel': '旅行刚结束', - 'journey.frontpage.suggestionText': '将 {title} 变成一段旅程', - 'journey.frontpage.dismiss': '忽略', - 'journey.frontpage.journeyName': '旅程名称', - 'journey.frontpage.namePlaceholder': '例如 东南亚 2026', - 'journey.frontpage.selectTrips': '选择旅行', - 'journey.frontpage.tripsSelected': '个旅行已选择', - 'journey.frontpage.trips': '个旅行', - 'journey.frontpage.placesImported': '个地点将被导入', - 'journey.frontpage.places': '个地点', - 'journey.detail.backToJourney': '返回旅程', - 'journey.detail.syncedWithTrips': '已与旅行同步', - 'journey.detail.addEntry': '添加条目', - 'journey.detail.newEntry': '新建条目', - 'journey.detail.editEntry': '编辑条目', - 'journey.detail.noEntries': '还没有条目', - 'journey.detail.noEntriesHint': '添加一个旅行以生成骨架条目', - 'journey.detail.noPhotos': '还没有照片', - 'journey.detail.noPhotosHint': '上传照片到条目或浏览你的 Immich/Synology 相册', - 'journey.detail.journeyStats': '旅程统计', - 'journey.detail.syncedTrips': '已同步的旅行', - 'journey.detail.noTripsLinked': '尚未关联旅行', - 'journey.detail.contributors': '贡献者', - 'journey.detail.readMore': '阅读更多', - 'journey.detail.prosCons': '优缺点', - 'journey.detail.photos': '照片', - 'journey.detail.day': '第{number}天', - 'journey.detail.places': '个地点', - 'journey.stats.days': '天', - 'journey.stats.cities': '城市', - 'journey.stats.entries': '条目', - 'journey.stats.photos': '照片', - 'journey.stats.places': '地点', - 'journey.skeletons.show': '显示建议', - 'journey.skeletons.hide': '隐藏建议', - 'journey.verdict.lovedIt': '非常喜欢', - 'journey.verdict.couldBeBetter': '有待改进', - 'journey.synced.places': '个地点', - 'journey.synced.synced': '已同步', - 'journey.editor.discardChangesConfirm': '您有未保存的更改。要放弃吗?', - 'journey.editor.uploadFailed': '照片上传失败', - 'journey.editor.uploadPhotos': '上传照片', - 'journey.editor.uploading': '上传中...', - 'journey.editor.uploadingProgress': '上传中 {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{total} 张中有 {failed} 张上传失败 — 再次保存以重试', - 'journey.editor.fromGallery': '从相册', - 'journey.editor.allPhotosAdded': '所有照片已添加', - 'journey.editor.writeStory': '写下你的故事...', - 'journey.editor.prosCons': '优缺点', - 'journey.editor.pros': '优点', - 'journey.editor.cons': '缺点', - 'journey.editor.proPlaceholder': '好的方面...', - 'journey.editor.conPlaceholder': '不好的方面...', - 'journey.editor.addAnother': '再添加一个', - 'journey.editor.date': '日期', - 'journey.editor.location': '地点', - 'journey.editor.searchLocation': '搜索地点...', - 'journey.editor.mood': '心情', - 'journey.editor.weather': '天气', - 'journey.editor.photoFirst': '第1张', - 'journey.editor.makeFirst': '设为第1张', - 'journey.editor.searching': '搜索中...', - 'journey.mood.amazing': '太棒了', - 'journey.mood.good': '不错', - 'journey.mood.neutral': '一般', - 'journey.mood.rough': '糟糕', - 'journey.weather.sunny': '晴天', - 'journey.weather.partly': '多云', - 'journey.weather.cloudy': '阴天', - 'journey.weather.rainy': '雨天', - 'journey.weather.stormy': '暴风雨', - 'journey.weather.cold': '雪天', - 'journey.trips.linkTrip': '关联旅行', - 'journey.trips.searchTrip': '搜索旅行', - 'journey.trips.searchPlaceholder': '旅行名称或目的地...', - 'journey.trips.noTripsAvailable': '没有可用的旅行', - 'journey.trips.link': '关联', - 'journey.trips.tripLinked': '旅行已关联', - 'journey.trips.linkFailed': '关联旅行失败', - 'journey.trips.addTrip': '添加旅行', - 'journey.trips.unlinkTrip': '取消关联旅行', - 'journey.trips.unlinkMessage': '取消关联"{title}"?此旅行中所有已同步的条目和照片将被永久删除。此操作无法撤销。', - 'journey.trips.unlink': '取消关联', - 'journey.trips.tripUnlinked': '旅行已取消关联', - 'journey.trips.unlinkFailed': '取消关联失败', - 'journey.trips.noTripsLinkedSettings': '未关联旅行', - 'journey.contributors.invite': '邀请贡献者', - 'journey.contributors.searchUser': '搜索用户', - 'journey.contributors.searchPlaceholder': '用户名或邮箱...', - 'journey.contributors.noUsers': '未找到用户', - 'journey.contributors.role': '角色', - 'journey.contributors.added': '贡献者已添加', - 'journey.contributors.addFailed': '添加贡献者失败', - 'journey.share.publicShare': '公开分享', - 'journey.share.createLink': '创建分享链接', - 'journey.share.linkCreated': '分享链接已创建', - 'journey.share.createFailed': '创建链接失败', - 'journey.share.copy': '复制', - 'journey.share.copied': '已复制!', - 'journey.share.timeline': '时间线', - 'journey.share.gallery': '图库', - 'journey.share.map': '地图', - 'journey.share.removeLink': '移除分享链接', - 'journey.share.linkDeleted': '分享链接已删除', - 'journey.share.deleteFailed': '删除失败', - 'journey.share.updateFailed': '更新失败', - - // Journey — Invite - 'journey.invite.role': '角色', - 'journey.invite.viewer': '查看者', - 'journey.invite.editor': '编辑者', - 'journey.invite.invite': '邀请', - 'journey.invite.inviting': '邀请中...', - 'journey.settings.title': '旅程设置', - 'journey.settings.coverImage': '封面图片', - 'journey.settings.changeCover': '更换封面', - 'journey.settings.addCover': '添加封面图片', - 'journey.settings.name': '名称', - 'journey.settings.subtitle': '副标题', - 'journey.settings.subtitlePlaceholder': '例如 泰国、越南和柬埔寨', - 'journey.settings.endJourney': '归档旅程', - 'journey.settings.reopenJourney': '恢复旅程', - 'journey.settings.archived': '旅程已归档', - 'journey.settings.reopened': '旅程已重新开启', - 'journey.settings.endDescription': '隐藏直播标记。您可以随时重新开启。', - 'journey.settings.delete': '删除', - 'journey.settings.deleteJourney': '删除旅程', - 'journey.settings.deleteMessage': '删除"{title}"?所有条目和照片将丢失。', - 'journey.settings.saved': '设置已保存', - 'journey.settings.saveFailed': '保存失败', - 'journey.settings.coverUpdated': '封面已更新', - 'journey.settings.coverFailed': '上传失败', - 'journey.settings.failedToDelete': '删除失败', - 'journey.entries.deleteTitle': '删除条目', - 'journey.photosUploaded': '{count} 张照片已上传', - 'journey.photosUploadFailed': '部分照片上传失败', - 'journey.photosAdded': '{count} 张照片已添加', - 'journey.public.notFound': '未找到', - 'journey.public.notFoundMessage': '此旅程不存在或链接已过期。', - 'journey.public.readOnly': '只读 · 公开旅程', - 'journey.public.tagline': '旅行资源与探索工具包', - 'journey.public.sharedVia': '分享自', - 'journey.public.madeWith': '由', - 'journey.pdf.journeyBook': '旅程手册', - 'journey.pdf.madeWith': '由 TREK 制作', - 'journey.pdf.day': '第', - 'journey.pdf.theEnd': '终', - 'journey.pdf.saveAsPdf': '保存为 PDF', - 'journey.pdf.pages': '页', - 'journey.picker.tripPeriod': '旅行时间段', - 'journey.picker.dateRange': '日期范围', - 'journey.picker.allPhotos': '所有照片', - 'journey.picker.albums': '相册', - 'journey.picker.selected': '已选择', - 'journey.picker.addTo': '添加到', - 'journey.picker.newGallery': '新相册', - 'journey.picker.selectAll': '全选', - 'journey.picker.deselectAll': '取消全选', - 'journey.picker.noAlbums': '未找到相册', - 'journey.picker.selectDate': '选择日期', - 'journey.picker.search': '搜索', - 'dashboard.greeting.morning': '早上好,', - 'dashboard.greeting.afternoon': '下午好,', - 'dashboard.greeting.evening': '晚上好,', - 'dashboard.mobile.liveNow': '进行中', - 'dashboard.mobile.tripProgress': '旅行进度', - 'dashboard.mobile.daysLeft': '还剩 {count} 天', - 'dashboard.mobile.places': '地点', - 'dashboard.mobile.buddies': '旅伴', - 'dashboard.mobile.newTrip': '新建旅行', - 'dashboard.mobile.currency': '货币', - 'dashboard.mobile.timezone': '时区', - 'dashboard.mobile.upcomingTrips': '即将到来的旅行', - 'dashboard.mobile.yourTrips': '我的旅行', - 'dashboard.mobile.trips': '个旅行', - 'dashboard.mobile.starts': '出发', - 'dashboard.mobile.duration': '时长', - 'dashboard.mobile.day': '天', - 'dashboard.mobile.days': '天', - 'dashboard.mobile.ongoing': '进行中', - 'dashboard.mobile.startsToday': '今天出发', - 'dashboard.mobile.tomorrow': '明天', - 'dashboard.mobile.inDays': '{count} 天后', - 'dashboard.mobile.inMonths': '{count} 个月后', - 'dashboard.mobile.completed': '已完成', - 'dashboard.mobile.currencyConverter': '汇率转换', - 'nav.profile': '个人资料', - 'nav.bottomSettings': '设置', - 'nav.bottomAdmin': '管理设置', - 'nav.bottomLogout': '退出登录', - 'nav.bottomAdminBadge': '管理员', - 'dayplan.mobile.addPlace': '添加地点', - 'dayplan.mobile.searchPlaces': '搜索地点...', - 'dayplan.mobile.allAssigned': '所有地点已分配', - 'dayplan.mobile.noMatch': '无匹配', - 'dayplan.mobile.createNew': '创建新地点', - 'admin.addons.catalog.journey.name': '旅程', - 'admin.addons.catalog.journey.description': '旅行追踪与旅行日志,包含签到、照片和每日故事', - // OAuth scope groups - 'oauth.scope.group.trips': '行程', - 'oauth.scope.group.places': '地点', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': '行李', - 'oauth.scope.group.todos': '待办事项', - 'oauth.scope.group.budget': '预算', - 'oauth.scope.group.reservations': '预订', - 'oauth.scope.group.collab': '协作', - 'oauth.scope.group.notifications': '通知', - 'oauth.scope.group.vacay': '假期', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': '天气', - 'oauth.scope.group.journey': '旅程', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': '查看行程和行程计划', - 'oauth.scope.trips:read.description': '读取行程、天数、每日笔记和成员', - 'oauth.scope.trips:write.label': '编辑行程和行程计划', - 'oauth.scope.trips:write.description': '创建和更新行程、天数、笔记并管理成员', - 'oauth.scope.trips:delete.label': '删除行程', - 'oauth.scope.trips:delete.description': '永久删除整个行程——此操作不可撤销', - 'oauth.scope.trips:share.label': '管理分享链接', - 'oauth.scope.trips:share.description': '创建、更新和撤销行程的公开分享链接', - 'oauth.scope.places:read.label': '查看地点和地图数据', - 'oauth.scope.places:read.description': '读取地点、每日分配、标签和分类', - 'oauth.scope.places:write.label': '管理地点', - 'oauth.scope.places:write.description': '创建、更新和删除地点、分配和标签', - 'oauth.scope.atlas:read.label': '查看 Atlas', - 'oauth.scope.atlas:read.description': '读取已访问国家、地区和心愿清单', - 'oauth.scope.atlas:write.label': '管理 Atlas', - 'oauth.scope.atlas:write.description': '标记已访问国家和地区,管理心愿清单', - 'oauth.scope.packing:read.label': '查看行李清单', - 'oauth.scope.packing:read.description': '读取行李物品、包袋和分类负责人', - 'oauth.scope.packing:write.label': '管理行李清单', - 'oauth.scope.packing:write.description': '添加、更新、删除、勾选和重新排列行李物品和包袋', - 'oauth.scope.todos:read.label': '查看待办清单', - 'oauth.scope.todos:read.description': '读取行程待办事项和分类负责人', - 'oauth.scope.todos:write.label': '管理待办清单', - 'oauth.scope.todos:write.description': '创建、更新、勾选、删除和重新排列待办事项', - 'oauth.scope.budget:read.label': '查看预算', - 'oauth.scope.budget:read.description': '读取预算条目和费用明细', - 'oauth.scope.budget:write.label': '管理预算', - 'oauth.scope.budget:write.description': '创建、更新和删除预算条目', - 'oauth.scope.reservations:read.label': '查看预订', - 'oauth.scope.reservations:read.description': '读取预订和住宿详情', - 'oauth.scope.reservations:write.label': '管理预订', - 'oauth.scope.reservations:write.description': '创建、更新、删除和重新排列预订', - 'oauth.scope.collab:read.label': '查看协作', - 'oauth.scope.collab:read.description': '读取协作笔记、投票和消息', - 'oauth.scope.collab:write.label': '管理协作', - 'oauth.scope.collab:write.description': '创建、更新和删除协作笔记、投票和消息', - 'oauth.scope.notifications:read.label': '查看通知', - 'oauth.scope.notifications:read.description': '读取应用内通知和未读数量', - 'oauth.scope.notifications:write.label': '管理通知', - 'oauth.scope.notifications:write.description': '将通知标记为已读并回复', - 'oauth.scope.vacay:read.label': '查看假期计划', - 'oauth.scope.vacay:read.description': '读取假期计划数据、条目和统计', - 'oauth.scope.vacay:write.label': '管理假期计划', - 'oauth.scope.vacay:write.description': '创建和管理假期条目、节假日和团队计划', - 'oauth.scope.geo:read.label': '地图和地理编码', - 'oauth.scope.geo:read.description': '搜索位置、解析地图 URL 和反向地理编码坐标', - 'oauth.scope.weather:read.label': '天气预报', - 'oauth.scope.weather:read.description': '获取行程地点和日期的天气预报', - 'oauth.scope.journey:read.label': '查看旅程', - 'oauth.scope.journey:read.description': '读取旅程、条目和贡献者列表', - 'oauth.scope.journey:write.label': '管理旅程', - 'oauth.scope.journey:write.description': '创建、更新和删除旅程及其条目', - 'oauth.scope.journey:share.label': '管理旅程链接', - 'oauth.scope.journey:share.description': '创建、更新和撤销旅程的公开分享链接', - - // System notices - 'system_notice.welcome_v1.title': '欢迎使用 TREK', - 'system_notice.welcome_v1.body': '您的全能旅行规划器。制定行程、与朋友分享旅行,随时保持井然有序——在线或离线均可。', - 'system_notice.welcome_v1.cta_label': '规划行程', - 'system_notice.welcome_v1.hero_alt': '风景优美的旅游目的地与 TREK 界面', - 'system_notice.welcome_v1.highlight_plan': '逐日行程规划', - 'system_notice.welcome_v1.highlight_share': '与旅行伙伴协作', - 'system_notice.welcome_v1.highlight_offline': '移动端支持离线使用', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': '上一条通知', - 'system_notice.pager.next': '下一条通知', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': '转到通知 {n}', - 'system_notice.pager.position': '通知 {current}/{total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': '3.0 版照片已迁移', - 'system_notice.v3_photos.body': '行程规划器中的​**照片**标签已被移除。您的照片安全无虑 — TREK 从未修改您的 Immich 或 Synology 相册。\n\n照片现在位于 **Journey** 插件中。Journey 是可选的 — 如果尚未启用,请联系管理员在 Admin → 插件 中开启。', - 'system_notice.v3_journey.title': '认识 Journey — 旅行日记', - 'system_notice.v3_journey.body': '将您的旅程记录为展示时间线、照片画廊和互动地图的丰富旅行故事。', - 'system_notice.v3_journey.cta_label': '打开 Journey', - 'system_notice.v3_journey.highlight_timeline': '每日时间线与画廊', - 'system_notice.v3_journey.highlight_photos': '从 Immich 或 Synology 导入', - 'system_notice.v3_journey.highlight_share': '公开分享 — 无需登录', - 'system_notice.v3_journey.highlight_export': '导出为 PDF 相册书', - 'system_notice.v3_features.title': '3.0 版更多亮点', - 'system_notice.v3_features.body': '此版本还有一些其他值得了解的新功能。', - 'system_notice.v3_features.highlight_dashboard': '移动优先仪表板重设计', - 'system_notice.v3_features.highlight_offline': '作为 PWA 的完整离线模式', - 'system_notice.v3_features.highlight_search': '地点搜索实时自动补全', - 'system_notice.v3_features.highlight_import': '从 KMZ/KML 文件导入地点', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1 升级', - 'system_notice.v3_mcp.body': 'MCP 集成已全面重构。OAuth 2.1 现为推荐的身份验证方式。静态令牌(trek_…)已弃用,将在未来版本中移除。', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 推荐(mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 个细粒度权限范围', - 'system_notice.v3_mcp.highlight_deprecated': '静态 trek_ 令牌已弃用', - 'system_notice.v3_mcp.highlight_tools': '扩展工具集与提示词', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': '来自我的一封私人信', - 'system_notice.v3_thankyou.body': '在你继续之前——我想停下来说几句。\n\nTREK 最初只是我为自己的旅行而做的一个业余项目。我从未想过它会成长为 4,000 人信赖的冒险规划工具。每一颗星标、每一个 issue、每一个功能请求——我都会读,它们在全职工作和大学学业之间的深夜里支撑着我继续前行。\n\n我想让你们知道:TREK 将永远开源,永远可自托管,永远属于你们。没有追踪,没有订阅,没有任何附加条件。只是一个热爱旅行的人为同样热爱旅行的你们打造的工具。\n\n特别感谢 [jubnl](https://github.com/jubnl)——你已经成为一位不可思议的合作者。3.0 版本中许多精彩之处都留下了你的印记。感谢你在这个项目还很粗糙的时候就选择了相信它。\n\n也感谢你们每一位——报告了 bug、翻译了文本、向朋友分享了 TREK,或者只是用它规划了一次旅行——**谢谢你们**。你们是这一切存在的原因。\n\n愿我们一起踏上更多的冒险旅程。\n\n— Maurice\n\n---\n\n[加入 Discord 社区](https://discord.gg/7Q6M6jDwzf)\n\n如果 TREK 让你的旅行更美好,一杯[小小的咖啡](https://ko-fi.com/mauriceboe)能让这盏灯一直亮着。', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': '需要操作:用户账户冲突', - 'system_notice.v3014_whitespace_collision.body': '3.0.14 版本升级检测到一个或多个由存储账户中首尾空白字符引发的用户名或邮箱冲突。受影响的账户已自动重命名。请检查服务器日志中以 **[migration] WHITESPACE COLLISION** 开头的行,以确认哪些账户需要审查。', - 'transport.addTransport': '添加交通', - 'transport.modalTitle.create': '添加交通', - 'transport.modalTitle.edit': '编辑交通', - 'transport.title': '交通', - 'transport.addManual': '手动添加交通', -} - -export default zh - diff --git a/client/src/i18n/translations/zhTw.ts b/client/src/i18n/translations/zhTw.ts deleted file mode 100644 index b89ce88f..00000000 --- a/client/src/i18n/translations/zhTw.ts +++ /dev/null @@ -1,2367 +0,0 @@ -const zhTw: Record = { - // Common - 'common.save': '儲存', - 'common.showMore': '顯示更多', - 'common.showLess': '收起', - 'common.cancel': '取消', - 'common.clear': '清除', - 'common.delete': '刪除', - 'common.edit': '編輯', - 'common.add': '新增', - 'common.loading': '載入中...', - 'common.import': '匯入', - 'common.select': '選擇', - 'common.selectAll': '全選', - 'common.deselectAll': '取消全選', - 'common.error': '錯誤', - 'common.unknownError': '未知錯誤', - 'common.tooManyAttempts': '嘗試次數過多,請稍後再試。', - 'common.back': '返回', - 'common.all': '全部', - 'common.close': '關閉', - 'common.open': '開啟', - 'common.upload': '上傳', - 'common.search': '搜尋', - 'common.confirm': '確認', - 'common.ok': '確定', - 'common.yes': '是', - 'common.no': '否', - 'common.or': '或', - 'common.none': '無', - 'common.date': '日期', - 'common.rename': '重新命名', - 'common.discardChanges': '捨棄變更', - 'common.discard': '捨棄', - 'common.name': '名稱', - 'common.email': '郵箱', - 'common.password': '密碼', - 'common.saving': '儲存中...', - 'common.saved': '已儲存', - 'common.expand': '展開', - 'common.collapse': '折疊', - 'trips.memberRemoved': '{username} 已移除', - 'trips.memberRemoveError': '移除失敗', - 'trips.memberAdded': '{username} 已新增', - 'trips.memberAddError': '新增失敗', - 'trips.reminder': '提醒', - 'trips.reminderNone': '無', - 'trips.reminderDay': '天', - 'trips.reminderDays': '天', - 'trips.reminderCustom': '自定義', - 'trips.reminderDaysBefore': '天前提醒', - 'trips.reminderDisabledHint': '旅行提醒已停用。請在管理 > 設定 > 通知中啟用。', - 'common.update': '更新', - 'common.change': '修改', - 'common.uploading': '上傳中…', - 'common.backToPlanning': '返回規劃', - 'common.reset': '重置', - - // Navbar - 'nav.trip': '旅行', - 'nav.share': '分享', - 'nav.settings': '設定', - 'nav.admin': '管理', - 'nav.logout': '退出登入', - 'nav.lightMode': '淺色模式', - 'nav.darkMode': '深色模式', - 'nav.autoMode': '自動模式', - 'nav.administrator': '管理員', - - // Dashboard - 'dashboard.title': '我的旅行', - 'dashboard.subtitle.loading': '載入旅行中...', - 'dashboard.subtitle.trips': '{count} 次旅行({archived} 已歸檔)', - 'dashboard.subtitle.empty': '開始你的第一次旅行', - 'dashboard.subtitle.activeOne': '{count} 個進行中的旅行', - 'dashboard.subtitle.activeMany': '{count} 個進行中的旅行', - 'dashboard.subtitle.archivedSuffix': ' · {count} 已歸檔', - 'dashboard.newTrip': '新建旅行', - 'dashboard.gridView': '網格檢視', - 'dashboard.listView': '列表檢視', - 'dashboard.currency': '貨幣', - 'dashboard.timezone': '時區', - 'dashboard.localTime': '本地', - 'dashboard.timezoneCustomTitle': '自定義時區', - 'dashboard.timezoneCustomLabelPlaceholder': '標籤(可選)', - 'dashboard.timezoneCustomTzPlaceholder': '如 America/New_York', - 'dashboard.timezoneCustomAdd': '新增', - 'dashboard.timezoneCustomErrorEmpty': '請輸入時區識別符號', - 'dashboard.timezoneCustomErrorInvalid': '無效的時區。請使用 Europe/Berlin 這樣的格式', - 'dashboard.timezoneCustomErrorDuplicate': '已新增', - 'dashboard.emptyTitle': '暫無旅行', - 'dashboard.emptyText': '建立你的第一次旅行,開始規劃吧!', - 'dashboard.emptyButton': '建立第一次旅行', - 'dashboard.nextTrip': '下次旅行', - 'dashboard.shared': '共享', - 'dashboard.sharedBy': '由 {name} 分享', - 'dashboard.days': '天', - 'dashboard.places': '地點', - 'dashboard.members': '旅伴', - 'dashboard.archive': '歸檔', - 'dashboard.copyTrip': '複製', - 'dashboard.copySuffix': '副本', - 'dashboard.restore': '恢復', - 'dashboard.archived': '已歸檔', - 'dashboard.status.ongoing': '進行中', - 'dashboard.status.today': '今天', - 'dashboard.status.tomorrow': '明天', - 'dashboard.status.past': '已結束', - 'dashboard.status.daysLeft': '還剩 {count} 天', - 'dashboard.toast.loadError': '載入旅行失敗', - 'dashboard.toast.created': '旅行建立成功!', - 'dashboard.toast.createError': '建立旅行失敗', - 'dashboard.toast.updated': '旅行已更新!', - 'dashboard.toast.updateError': '更新旅行失敗', - 'dashboard.toast.deleted': '旅行已刪除', - 'dashboard.toast.deleteError': '刪除旅行失敗', - 'dashboard.toast.archived': '旅行已歸檔', - 'dashboard.toast.archiveError': '歸檔旅行失敗', - 'dashboard.toast.restored': '旅行已恢復', - 'dashboard.toast.restoreError': '恢復旅行失敗', - 'dashboard.toast.copied': '旅行已複製!', - 'dashboard.toast.copyError': '複製旅行失敗', - 'dashboard.confirm.delete': '刪除旅行「{title}」?所有地點和計劃將被永久刪除。', - 'dashboard.editTrip': '編輯旅行', - 'dashboard.createTrip': '建立新旅行', - 'dashboard.tripTitle': '標題', - 'dashboard.tripTitlePlaceholder': '如:日本夏日之旅', - 'dashboard.tripDescription': '描述', - 'dashboard.tripDescriptionPlaceholder': '這次旅行是關於什麼的?', - 'dashboard.startDate': '開始日期', - 'dashboard.endDate': '結束日期', - 'dashboard.dayCount': '天數', - 'dashboard.dayCountHint': '未設定旅行日期時,要規劃的天數。', - 'dashboard.noDateHint': '未設定日期——將預設建立 7 天。你可以隨時修改。', - 'dashboard.coverImage': '封面圖片', - 'dashboard.addCoverImage': '新增封面圖片', - 'dashboard.addMembers': '旅伴', - 'dashboard.addMember': '新增成員', - 'dashboard.coverSaved': '封面圖片已儲存', - 'dashboard.coverUploadError': '上傳失敗', - 'dashboard.coverRemoveError': '移除失敗', - 'dashboard.titleRequired': '標題為必填項', - 'dashboard.endDateError': '結束日期必須晚於開始日期', - - // Settings - 'settings.title': '設定', - 'settings.subtitle': '配置你的個人設定', - 'settings.tabs.display': '顯示', - 'settings.tabs.map': '地圖', - 'settings.tabs.notifications': '通知', - 'settings.tabs.integrations': '整合', - 'settings.tabs.account': '帳戶', - 'settings.tabs.offline': 'Offline', - 'settings.tabs.about': '關於', - 'settings.map': '地圖', - 'settings.mapTemplate': '地圖模板', - 'settings.mapTemplatePlaceholder.select': '選擇模板...', - 'settings.mapDefaultHint': '留空則使用 OpenStreetMap(預設)', - 'settings.mapTemplatePlaceholder': 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', - 'settings.mapHint': '地圖瓦片 URL 模板', - 'settings.mapProvider': '地圖提供商', - 'settings.mapProviderHint': '影響行程規劃和旅程地圖。Atlas 始終使用 Leaflet。', - 'settings.mapLeafletSubtitle': '經典 2D,任何柵格瓦片', - 'settings.mapMapboxSubtitle': '向量瓦片、3D 建築和地形', - 'settings.mapExperimental': '實驗性', - 'settings.mapMapboxToken': 'Mapbox 存取權杖', - 'settings.mapMapboxTokenHint': '公開權杖 (pk.*) 來自', - 'settings.mapMapboxTokenLink': 'mapbox.com → 存取權杖', - 'settings.mapStyle': '地圖樣式', - 'settings.mapStylePlaceholder': '選擇 Mapbox 樣式', - 'settings.mapStyleHint': '預設或您自己的 mapbox://styles/USER/ID URL', - 'settings.map3dBuildings': '3D 建築和地形', - 'settings.map3dHint': '傾斜 + 真實 3D 建築拉伸 — 適用於所有樣式,包括衛星。', - 'settings.mapHighQuality': '高畫質模式', - 'settings.mapHighQualityHint': '抗鋸齒 + 地球投影,帶來更清晰的邊緣和更真實的世界視圖。', - 'settings.mapHighQualityWarning': '可能影響低階裝置的效能。', - 'settings.mapTipLabel': '提示:', - 'settings.mapTip': '右鍵點擊並拖曳以旋轉/傾斜地圖。中鍵點擊新增地點(右鍵用於旋轉)。', - 'settings.latitude': '緯度', - 'settings.longitude': '經度', - 'settings.saveMap': '儲存地圖', - 'settings.apiKeys': 'API 金鑰', - 'settings.mapsKey': 'Google Maps API 金鑰', - 'settings.mapsKeyHint': '用於地點搜尋。需要 Places API (New)。在 console.cloud.google.com 獲取', - 'settings.weatherKey': 'OpenWeatherMap API 金鑰', - 'settings.weatherKeyHint': '用於天氣資料。在 openweathermap.org/api 免費獲取', - 'settings.keyPlaceholder': '輸入金鑰...', - 'settings.configured': '已配置', - 'settings.saveKeys': '儲存金鑰', - 'settings.display': '顯示', - 'settings.colorMode': '顏色模式', - 'settings.light': '淺色', - 'settings.dark': '深色', - 'settings.auto': '自動', - 'settings.language': '語言', - 'settings.temperature': '溫度單位', - 'settings.timeFormat': '時間格式', - 'settings.blurBookingCodes': '模糊預訂程式碼', - 'settings.notifications': '通知', - 'settings.notifyTripInvite': '旅行邀請', - 'settings.notifyBookingChange': '預訂變更', - 'settings.notifyTripReminder': '旅行提醒', - 'settings.notifyTodoDue': '待辦事項即將到期', - 'settings.notifyVacayInvite': 'Vacay 融合邀請', - 'settings.notifyPhotosShared': '共享照片 (Immich)', - 'settings.notifyCollabMessage': '聊天訊息 (Collab)', - 'settings.notifyPackingTagged': '行李清單:分配', - 'settings.notifyWebhook': 'Webhook 通知', - 'settings.notifyVersionAvailable': '有新版本可用', - 'settings.notificationPreferences.email': '電子郵件', - 'settings.notificationPreferences.webhook': 'Webhook', - 'settings.notificationPreferences.inapp': '應用程式內', - 'settings.notificationPreferences.ntfy': 'Ntfy', - 'settings.notificationPreferences.noChannels': '未配置通知渠道。請聯絡管理員設定電子郵件或 Webhook 通知。', - 'settings.webhookUrl.label': 'Webhook URL', - 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', - 'settings.webhookUrl.hint': '輸入您的 Discord、Slack 或自訂 Webhook URL 以接收通知。', - 'settings.webhookUrl.saved': 'Webhook URL 已儲存', - 'settings.webhookUrl.test': '測試', - 'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功', - 'settings.webhookUrl.testFailed': '測試 Webhook 傳送失敗', - 'settings.ntfyUrl.topicLabel': 'Ntfy 主題', - 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', - 'settings.ntfyUrl.serverLabel': 'Ntfy 伺服器 URL(選填)', - 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', - 'settings.ntfyUrl.hint': '輸入您的 Ntfy 主題以接收推播通知。將伺服器留空以使用管理員設定的預設值。', - 'settings.ntfyUrl.tokenLabel': '存取權杖(選填)', - 'settings.ntfyUrl.tokenHint': '受密碼保護的主題需要此項目。', - 'settings.ntfyUrl.saved': 'Ntfy 設定已儲存', - 'settings.ntfyUrl.test': '測試', - 'settings.ntfyUrl.testSuccess': '測試 Ntfy 通知傳送成功', - 'settings.ntfyUrl.testFailed': '測試 Ntfy 通知失敗', - 'settings.ntfyUrl.tokenCleared': '存取權杖已清除', - 'settings.notificationsDisabled': '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。', - 'settings.notificationsActive': '活躍頻道', - 'settings.notificationsManagedByAdmin': '通知事件由管理員配置。', - 'admin.notifications.title': '通知', - 'admin.notifications.hint': '選擇一個通知渠道。一次只能啟用一個。', - 'admin.notifications.none': '已停用', - 'admin.notifications.email': '電子郵件 (SMTP)', - 'admin.notifications.webhook': 'Webhook', - 'admin.notifications.save': '儲存通知設定', - 'admin.notifications.saved': '通知設定已儲存', - 'admin.notifications.testWebhook': '傳送測試 Webhook', - 'admin.notifications.testWebhookSuccess': '測試 Webhook 傳送成功', - 'admin.notifications.testWebhookFailed': '測試 Webhook 傳送失敗', - 'admin.notifications.emailPanel.title': '電子郵件 (SMTP)', - 'admin.notifications.webhookPanel.title': 'Webhook', - 'admin.notifications.inappPanel.title': '應用程式內通知', - 'admin.notifications.inappPanel.hint': '應用程式內通知始終啟用,無法全域性停用。', - 'admin.notifications.adminWebhookPanel.title': '管理員 Webhook', - 'admin.notifications.adminWebhookPanel.hint': '此 Webhook 專用於管理員通知(例如版本提醒)。它與每位使用者的 Webhook 分開,設定後始終會觸發。', - 'admin.notifications.adminWebhookPanel.saved': '管理員 Webhook URL 已儲存', - 'admin.notifications.adminWebhookPanel.testSuccess': '測試 Webhook 傳送成功', - 'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 傳送失敗', - 'admin.notifications.adminWebhookPanel.alwaysOnHint': '配置 URL 後,管理員 Webhook 始終觸發', - 'admin.notifications.ntfy': 'Ntfy', - 'admin.ntfy.hint': '允許使用者設定自己的 ntfy 主題以接收推播通知。在下方設定預設伺服器以預先填入使用者設定。', - 'admin.notifications.testNtfy': '傳送測試 Ntfy', - 'admin.notifications.testNtfySuccess': '測試 Ntfy 傳送成功', - 'admin.notifications.testNtfyFailed': '測試 Ntfy 失敗', - 'admin.notifications.adminNtfyPanel.title': '管理員 Ntfy', - 'admin.notifications.adminNtfyPanel.hint': '此 Ntfy 主題專用於管理員通知(例如版本提醒)。它與每位使用者的主題分開,設定後始終會觸發。', - 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 伺服器 URL', - 'admin.notifications.adminNtfyPanel.serverHint': '同時用作使用者 ntfy 通知的預設伺服器。留空則預設使用 ntfy.sh。使用者可在自己的設定中覆寫此項。', - 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', - 'admin.notifications.adminNtfyPanel.topicLabel': '管理員主題', - 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', - 'admin.notifications.adminNtfyPanel.tokenLabel': '存取權杖(選填)', - 'admin.notifications.adminNtfyPanel.tokenCleared': '管理員存取權杖已清除', - 'admin.notifications.adminNtfyPanel.saved': '管理員 Ntfy 設定已儲存', - 'admin.notifications.adminNtfyPanel.test': '傳送測試 Ntfy', - 'admin.notifications.adminNtfyPanel.testSuccess': '測試 Ntfy 傳送成功', - 'admin.notifications.adminNtfyPanel.testFailed': '測試 Ntfy 失敗', - 'admin.notifications.adminNtfyPanel.alwaysOnHint': '設定主題後管理員 Ntfy 始終觸發', - 'admin.notifications.adminNotificationsHint': '配置哪些渠道傳遞僅管理員通知(例如版本提醒)。', - 'admin.notifications.tripReminders.title': '行程提醒', - 'admin.notifications.tripReminders.hint': '在行程開始前發送提醒通知(需要在行程中設定提醒天數)。', - 'admin.notifications.tripReminders.enabled': '行程提醒已啟用', - 'admin.notifications.tripReminders.disabled': '行程提醒已停用', - 'admin.smtp.title': '郵件與通知', - 'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。', - 'admin.smtp.testButton': '傳送測試郵件', - 'admin.webhook.hint': '允許使用者配置自己的 Webhook URL 以接收通知(Discord、Slack 等)。', - 'admin.smtp.testSuccess': '測試郵件傳送成功', - 'admin.smtp.testFailed': '測試郵件傳送失敗', - 'dayplan.icsTooltip': '匯出日曆 (ICS)', - 'share.linkTitle': '公開連結', - 'share.linkHint': '建立一個連結,任何人無需登入即可檢視此旅行。僅可檢視,無法編輯。', - 'share.createLink': '建立連結', - 'share.deleteLink': '刪除連結', - 'share.createError': '無法建立連結', - 'common.copy': '複製', - 'common.copied': '已複製', - 'share.permMap': '地圖與計劃', - 'share.permBookings': '預訂', - 'share.permPacking': '行李', - 'shared.expired': '連結已過期或無效', - 'shared.expiredHint': '此共享旅行連結已失效。', - 'shared.readOnly': '只讀共享檢視', - 'shared.tabPlan': '計劃', - 'shared.tabBookings': '預訂', - 'shared.tabPacking': '行李', - 'shared.tabBudget': '預算', - 'shared.tabChat': '聊天', - 'shared.days': '天', - 'shared.places': '個地點', - 'shared.other': '其他', - 'shared.totalBudget': '總預算', - 'shared.messages': '條訊息', - 'shared.sharedVia': '透過以下分享', - 'shared.confirmed': '已確認', - 'shared.pending': '待確認', - 'share.permBudget': '預算', - 'share.permCollab': '聊天', - 'settings.on': '開', - 'settings.off': '關', - 'settings.mcp.title': 'MCP 配置', - 'settings.mcp.endpoint': 'MCP 端點', - 'settings.mcp.clientConfig': '客戶端配置', - 'settings.mcp.clientConfigHint': '將 替換為下方列表中的 API 令牌。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。', - 'settings.mcp.clientConfigHintOAuth': '將 替換為上方建立的 OAuth 2.1 客戶端所顯示的憑據。首次連線時,mcp-remote 將開啟瀏覽器完成授權。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。', - 'settings.mcp.copy': '複製', - 'settings.mcp.copied': '已複製!', - 'settings.mcp.apiTokens': 'API 令牌', - 'settings.mcp.createToken': '建立新令牌', - 'settings.mcp.noTokens': '暫無令牌,請建立一個以連線 MCP 客戶端。', - 'settings.mcp.tokenCreatedAt': '創建於', - 'settings.mcp.tokenUsedAt': '使用於', - 'settings.mcp.deleteTokenTitle': '刪除令牌', - 'settings.mcp.deleteTokenMessage': '此令牌將立即失效,使用它的所有 MCP 客戶端將失去訪問許可權。', - 'settings.mcp.modal.createTitle': '建立 API 令牌', - 'settings.mcp.modal.tokenName': '令牌名稱', - 'settings.mcp.modal.tokenNamePlaceholder': '例如:Claude Desktop、工作電腦', - 'settings.mcp.modal.creating': '建立中…', - 'settings.mcp.modal.create': '建立令牌', - 'settings.mcp.modal.createdTitle': '令牌已建立', - 'settings.mcp.modal.createdWarning': '此令牌只會顯示一次,請立即複製並妥善儲存——無法找回。', - 'settings.mcp.modal.done': '完成', - 'settings.mcp.toast.created': '令牌已建立', - 'settings.mcp.toast.createError': '建立令牌失敗', - 'settings.mcp.toast.deleted': '令牌已刪除', - 'settings.mcp.toast.deleteError': '刪除令牌失敗', - 'settings.mcp.apiTokensDeprecated': 'API 金鑰已棄用,將於未來版本中移除。請改用 OAuth 2.1 客戶端。', - 'settings.oauth.clients': 'OAuth 2.1 客戶端', - 'settings.oauth.clientsHint': '註冊 OAuth 2.1 客戶端,讓第三方 MCP 應用程式(Claude Web、Cursor 等)無需靜態金鑰即可連線。', - 'settings.oauth.createClient': '新增客戶端', - 'settings.oauth.noClients': '尚無已註冊的 OAuth 客戶端。', - 'settings.oauth.clientId': '客戶端 ID', - 'settings.oauth.clientSecret': '客戶端密鑰', - 'settings.oauth.deleteClient': '刪除客戶端', - 'settings.oauth.deleteClientMessage': '此客戶端及所有活躍工作階段將被永久刪除。任何使用此客戶端的應用程式將立即失去存取權限。', - 'settings.oauth.rotateSecret': '輪換密鑰', - 'settings.oauth.rotateSecretMessage': '將產生新的客戶端密鑰,所有現有工作階段將立即失效。請在關閉此對話框前更新您的應用程式。', - 'settings.oauth.rotateSecretConfirm': '輪換', - 'settings.oauth.rotateSecretConfirming': '輪換中…', - 'settings.oauth.rotateSecretDoneTitle': '已產生新密鑰', - 'settings.oauth.rotateSecretDoneWarning': '此密鑰僅顯示一次。請立即複製並更新您的應用程式——所有先前的工作階段已失效。', - 'settings.oauth.activeSessions': '活躍的 OAuth 工作階段', - 'settings.oauth.sessionScopes': '授權範圍', - 'settings.oauth.sessionExpires': '到期時間', - 'settings.oauth.revoke': '撤銷', - 'settings.oauth.revokeSession': '撤銷工作階段', - 'settings.oauth.revokeSessionMessage': '這將立即撤銷此 OAuth 工作階段的存取權限。', - 'settings.oauth.modal.createTitle': '註冊 OAuth 客戶端', - 'settings.oauth.modal.presets': '快速預設', - 'settings.oauth.modal.clientName': '應用程式名稱', - 'settings.oauth.modal.clientNamePlaceholder': '例如 Claude Web、我的 MCP 應用程式', - 'settings.oauth.modal.redirectUris': '重新導向 URI', - 'settings.oauth.modal.redirectUrisPlaceholder': 'https://your-app.com/callback\nhttps://your-app.com/auth', - 'settings.oauth.modal.redirectUrisHint': '每行一個 URI。需要 HTTPS(localhost 除外)。需要完全符合。', - 'settings.oauth.modal.scopes': '允許的授權範圍', - 'settings.oauth.modal.scopesHint': 'list_trips 和 get_trip_summary 始終可用——不需要授權範圍。它們可幫助 AI 找到所需的行程 ID。', - 'settings.oauth.modal.selectAll': '全選', - 'settings.oauth.modal.deselectAll': '取消全選', - 'settings.oauth.modal.creating': '註冊中…', - 'settings.oauth.modal.create': '註冊客戶端', - 'settings.oauth.modal.createdTitle': '客戶端已註冊', - 'settings.oauth.modal.createdWarning': '客戶端密鑰僅顯示一次。請立即複製——無法恢復。', - 'settings.oauth.toast.createError': '註冊 OAuth 客戶端失敗', - 'settings.oauth.toast.deleted': 'OAuth 客戶端已刪除', - 'settings.oauth.toast.deleteError': '刪除 OAuth 客戶端失敗', - 'settings.oauth.toast.revoked': '工作階段已撤銷', - 'settings.oauth.toast.revokeError': '撤銷工作階段失敗', - 'settings.oauth.toast.rotateError': '輪換客戶端密鑰失敗', - 'settings.oauth.modal.machineClient': '機器客戶端(無需瀏覽器登入)', - 'settings.oauth.modal.machineClientHint': '使用 client_credentials 授權——無需重新導向 URI。令牌透過 client_id + client_secret 直接簽發,並在所選範圍內以您的身份運行。', - 'settings.oauth.modal.machineClientUsage': '取得令牌:向 /oauth/token 發送 POST 請求,攜帶 grant_type=client_credentials、client_id 和 client_secret。無需瀏覽器,無重整令牌。', - 'settings.oauth.badge.machine': '機器', - 'settings.account': '賬戶', - 'settings.about': '關於', - 'settings.about.reportBug': '回報錯誤', - 'settings.about.reportBugHint': '發現問題?告訴我們', - 'settings.about.featureRequest': '功能建議', - 'settings.about.featureRequestHint': '建議新功能', - 'settings.about.wikiHint': '文件與指南', - 'settings.about.supporters.badge': '月度支持者', - 'settings.about.supporters.title': '與 TREK 同行的夥伴', - 'settings.about.supporters.subtitle': '當你規劃下一段路線時,這些人也在一起規劃 TREK 的未來。他們每月的支持直接用於開發與實際投入的時間——讓 TREK 保持開源。', - 'settings.about.supporters.since': '自 {date} 起的支持者', - 'settings.about.supporters.tierEmpty': '成為第一個', - 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', - 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', - 'settings.about.supporter.tier.businessClassDreamer': 'Business Class Dreamer', - 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', - 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', - 'settings.about.description': 'TREK 是一款自架旅遊規劃器,幫助您從最初構想到最後回憶,整理每次旅行。日程規劃、預算、行李清單、照片及更多功能——全部集中在您自己的伺服器上。', - 'settings.about.madeWith': '以', - 'settings.about.madeBy': '由 Maurice 及不斷成長的開源社群製作。', - 'settings.username': '使用者名稱', - 'settings.email': '郵箱', - 'settings.role': '角色', - 'settings.roleAdmin': '管理員', - 'settings.oidcLinked': '已關聯', - 'settings.changePassword': '修改密碼', - 'settings.mustChangePassword': '您必須更改密碼才能繼續。請在下方設定新密碼。', - 'settings.currentPassword': '當前密碼', - 'settings.currentPasswordRequired': '請輸入當前密碼', - 'settings.newPassword': '新密碼', - 'settings.confirmPassword': '確認新密碼', - 'settings.updatePassword': '更新密碼', - 'settings.passwordRequired': '請輸入當前密碼和新密碼', - 'settings.passwordTooShort': '密碼至少需要 8 個字元', - 'settings.passwordMismatch': '兩次輸入的密碼不一致', - 'settings.passwordWeak': '密碼必須包含大寫字母、小寫字母、數字和特殊字元', - 'settings.passwordChanged': '密碼修改成功', - 'settings.deleteAccount': '刪除賬戶', - 'settings.deleteAccountTitle': '確定刪除賬戶?', - 'settings.deleteAccountWarning': '你的賬戶以及所有旅行、地點和檔案將被永久刪除。此操作無法撤銷。', - 'settings.deleteAccountConfirm': '永久刪除', - 'settings.deleteBlockedTitle': '無法刪除', - 'settings.deleteBlockedMessage': '你是唯一的管理員。請先將其他使用者提升為管理員,然後再刪除賬戶。', - 'settings.roleUser': '使用者', - 'settings.saveProfile': '儲存資料', - 'settings.mfa.title': '雙因素認證 (2FA)', - 'settings.mfa.description': '登入時新增第二步驗證。使用身份驗證器應用(Google Authenticator、Authy 等)。', - 'settings.mfa.requiredByPolicy': '管理員要求雙因素身份驗證。請先完成下方的身份驗證器設定後再繼續。', - 'settings.mfa.backupTitle': '備用程式碼', - 'settings.mfa.backupDescription': '如果你無法使用身份驗證器應用,可使用這些一次性備用程式碼登入。', - 'settings.mfa.backupWarning': '請立即儲存這些程式碼。每個程式碼只能使用一次。', - 'settings.mfa.backupCopy': '複製程式碼', - 'settings.mfa.backupDownload': '下載 TXT', - 'settings.mfa.backupPrint': '列印 / PDF', - 'settings.mfa.backupCopied': '備用程式碼已複製', - 'settings.mfa.enabled': '您的賬戶已啟用 2FA。', - 'settings.mfa.disabled': '2FA 未啟用。', - 'settings.mfa.setup': '設定身份驗證器', - 'settings.mfa.scanQr': '使用應用掃描此二維碼,或手動輸入金鑰。', - 'settings.mfa.secretLabel': '金鑰(手動輸入)', - 'settings.mfa.codePlaceholder': '6 位驗證碼', - 'settings.mfa.enable': '啟用 2FA', - 'settings.mfa.cancelSetup': '取消', - 'settings.mfa.disableTitle': '停用 2FA', - 'settings.mfa.disableHint': '輸入您的賬戶密碼和身份驗證器中的當前驗證碼。', - 'settings.mfa.disable': '停用 2FA', - 'settings.mfa.toastEnabled': '雙因素認證已啟用', - 'settings.mfa.toastDisabled': '雙因素認證已停用', - 'settings.mfa.demoBlocked': '演示模式下不可用', - 'settings.toast.mapSaved': '地圖設定已儲存', - 'settings.toast.keysSaved': 'API 金鑰已儲存', - 'settings.toast.displaySaved': '顯示設定已儲存', - 'settings.toast.profileSaved': '資料已儲存', - 'settings.uploadAvatar': '上傳頭像', - 'settings.removeAvatar': '移除頭像', - 'settings.avatarUploaded': '頭像已更新', - 'settings.avatarRemoved': '頭像已移除', - 'settings.avatarError': '上傳失敗', - - // Login - 'login.error': '登入失敗,請檢查你的憑據。', - 'login.tagline': '你的旅行。\n你的計劃。', - 'login.description': '透過互動地圖、預算管理和即時同步,協同規劃旅行。', - 'login.features.maps': '互動地圖', - 'login.features.mapsDesc': 'Google Places、路線和聚類', - 'login.features.realtime': '即時同步', - 'login.features.realtimeDesc': '透過 WebSocket 協同規劃', - 'login.features.budget': '預算跟蹤', - 'login.features.budgetDesc': '分類、圖表和人均費用', - 'login.features.collab': '協作', - 'login.features.collabDesc': '多使用者共享旅行', - 'login.features.packing': '行李清單', - 'login.features.packingDesc': '分類、進度和建議', - 'login.features.bookings': '預訂', - 'login.features.bookingsDesc': '航班、酒店、餐廳等', - 'login.features.files': '文件', - 'login.features.filesDesc': '上傳和管理文件', - 'login.features.routes': '智慧路線', - 'login.features.routesDesc': '自動最佳化和匯出到 Google Maps', - 'login.selfHosted': '自託管 · 開源 · 資料由你掌控', - 'login.title': '登入', - 'login.subtitle': '歡迎回來', - 'login.signingIn': '登入中…', - 'login.signIn': '登入', - 'login.createAdmin': '建立管理員賬戶', - 'login.createAdminHint': '為 TREK 設定第一個管理員賬戶。', - 'login.setNewPassword': '設定新密碼', - 'login.setNewPasswordHint': '您必須更改密碼才能繼續。', - 'login.createAccount': '建立賬戶', - 'login.createAccountHint': '註冊新賬戶。', - 'login.creating': '建立中…', - 'login.noAccount': '還沒有賬戶?', - 'login.hasAccount': '已有賬戶?', - 'login.register': '註冊', - 'login.emailPlaceholder': 'your@email.com', - 'login.username': '使用者名稱', - 'login.oidc.registrationDisabled': '註冊已關閉。請聯絡管理員。', - 'login.oidc.noEmail': '未從提供商獲取到郵箱。', - 'login.mfaTitle': '雙因素認證', - 'login.mfaSubtitle': '請輸入身份驗證器應用中的 6 位驗證碼。', - 'login.mfaCodeLabel': '驗證碼', - 'login.mfaCodeRequired': '請輸入身份驗證器應用中的驗證碼。', - 'login.mfaHint': '開啟 Google Authenticator、Authy 或其他 TOTP 應用。', - 'login.mfaBack': '← 返回登入', - 'login.mfaVerify': '驗證', - 'login.invalidInviteLink': '邀請連結無效或已過期', - 'login.oidcFailed': 'OIDC 登入失敗', - 'login.usernameRequired': '使用者名稱為必填', - 'login.passwordMinLength': '密碼至少需要8個字元', - 'login.forgotPassword': '忘記密碼?', - 'login.forgotPasswordTitle': '重設密碼', - 'login.forgotPasswordBody': '請輸入您註冊時使用的電子郵件。若帳號存在,我們將傳送重設連結。', - 'login.forgotPasswordSubmit': '傳送重設連結', - 'login.forgotPasswordSentTitle': '請查看您的電子郵件', - 'login.forgotPasswordSentBody': '若此電子郵件存在帳號,重設連結正在傳送中。連結將於 60 分鐘後失效。', - 'login.forgotPasswordSmtpHintOff': '提醒:管理員尚未設定 SMTP,重設連結將寫入伺服器控制台,而非透過電子郵件寄送。', - 'login.backToLogin': '返回登入', - 'login.newPassword': '新密碼', - 'login.confirmPassword': '確認新密碼', - 'login.passwordsDontMatch': '兩次輸入的密碼不一致', - 'login.mfaCode': '2FA 驗證碼', - 'login.resetPasswordTitle': '設定新密碼', - 'login.resetPasswordBody': '請選擇您在此處尚未使用過的強密碼。至少 8 個字元。', - 'login.resetPasswordMfaBody': '請輸入您的 2FA 驗證碼或備用代碼以完成重設。', - 'login.resetPasswordSubmit': '重設密碼', - 'login.resetPasswordVerify': '驗證並重設', - 'login.resetPasswordSuccessTitle': '密碼已更新', - 'login.resetPasswordSuccessBody': '您現在可以使用新密碼登入。', - 'login.resetPasswordInvalidLink': '無效的重設連結', - 'login.resetPasswordInvalidLinkBody': '此連結遺失或已損壞。請重新申請以繼續。', - 'login.resetPasswordFailed': '重設失敗。連結可能已過期。', - 'login.oidc.tokenFailed': '認證失敗。', - 'login.oidc.invalidState': '會話無效,請重試。', - 'login.demoFailed': '演示登入失敗', - 'login.oidcSignIn': '透過 {name} 登入', - 'login.oidcOnly': '密碼登入已關閉。請透過 SSO 提供商登入。', - 'login.oidcLoggedOut': '您已登出。請重新透過 SSO 提供商登入。', - 'login.demoHint': '試用演示——無需註冊', - - // Register - 'register.passwordMismatch': '兩次輸入的密碼不一致', - 'register.passwordTooShort': '密碼至少需要 8 個字元', - 'register.failed': '註冊失敗', - 'register.getStarted': '開始使用', - 'register.subtitle': '建立賬戶,開始規劃你的夢想旅行。', - 'register.feature1': '無限旅行計劃', - 'register.feature2': '互動地圖檢視', - 'register.feature3': '管理地點和分類', - 'register.feature4': '跟蹤預訂', - 'register.feature5': '建立行李清單', - 'register.feature6': '儲存照片和檔案', - 'register.createAccount': '建立賬戶', - 'register.startPlanning': '開始規劃你的旅行', - 'register.minChars': '至少 6 個字元', - 'register.confirmPassword': '確認密碼', - 'register.repeatPassword': '重複密碼', - 'register.registering': '註冊中...', - 'register.register': '註冊', - 'register.hasAccount': '已有賬戶?', - 'register.signIn': '登入', - - // Admin - 'admin.title': '管理後臺', - 'admin.subtitle': '使用者管理和系統設定', - 'admin.tabs.users': '使用者', - 'admin.tabs.categories': '分類', - 'admin.tabs.backup': '備份', - 'admin.tabs.audit': '審計日誌', - 'admin.tabs.notifications': '通知', - 'admin.stats.users': '使用者', - 'admin.stats.trips': '旅行', - 'admin.stats.places': '地點', - 'admin.stats.photos': '照片', - 'admin.stats.files': '檔案', - 'admin.table.user': '使用者', - 'admin.table.email': '郵箱', - 'admin.table.role': '角色', - 'admin.table.created': '建立時間', - 'admin.table.lastLogin': '最後登入', - 'admin.table.actions': '操作', - 'admin.you': '(你)', - 'admin.editUser': '編輯使用者', - 'admin.newPassword': '新密碼', - 'admin.newPasswordHint': '留空則保持當前密碼', - 'admin.deleteUser': '刪除使用者「{name}」?所有旅行將被永久刪除。', - 'admin.deleteUserTitle': '刪除使用者', - 'admin.newPasswordPlaceholder': '輸入新密碼…', - 'admin.toast.loadError': '載入管理資料失敗', - 'admin.toast.userUpdated': '使用者已更新', - 'admin.toast.updateError': '更新失敗', - 'admin.toast.userDeleted': '使用者已刪除', - 'admin.toast.deleteError': '刪除失敗', - 'admin.toast.cannotDeleteSelf': '不能刪除自己的賬戶', - 'admin.toast.userCreated': '使用者已建立', - 'admin.toast.createError': '建立使用者失敗', - 'admin.toast.fieldsRequired': '使用者名稱、郵箱和密碼為必填項', - 'admin.createUser': '建立使用者', - 'admin.invite.title': '邀請連結', - 'admin.invite.subtitle': '建立一次性註冊連結', - 'admin.invite.create': '建立連結', - 'admin.invite.createAndCopy': '建立並複製', - 'admin.invite.empty': '尚未建立邀請連結', - 'admin.invite.maxUses': '最大使用次數', - 'admin.invite.expiry': '有效期', - 'admin.invite.uses': '已使用', - 'admin.invite.expiresAt': '過期時間', - 'admin.invite.createdBy': '由', - 'admin.invite.active': '有效', - 'admin.invite.expired': '已過期', - 'admin.invite.usedUp': '已用完', - 'admin.invite.copied': '邀請連結已複製', - 'admin.invite.copyLink': '複製連結', - 'admin.invite.deleted': '邀請連結已刪除', - 'admin.invite.createError': '建立連結失敗', - 'admin.invite.deleteError': '刪除連結失敗', - 'admin.tabs.settings': '設定', - 'admin.allowRegistration': '允許註冊', - 'admin.allowRegistrationHint': '新使用者可以自行註冊', - 'admin.authMethods': 'Authentication Methods', - 'admin.passwordLogin': 'Password Login', - 'admin.passwordLoginHint': 'Allow users to sign in with email and password', - 'admin.passwordRegistration': 'Password Registration', - 'admin.passwordRegistrationHint': 'Allow new users to register with email and password', - 'admin.oidcLogin': 'SSO Login', - 'admin.oidcLoginHint': 'Allow users to sign in with SSO', - 'admin.oidcRegistration': 'SSO Auto-Provisioning', - 'admin.oidcRegistrationHint': 'Automatically create accounts for new SSO users', - 'admin.envOverrideHint': 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', - 'admin.lockoutWarning': 'At least one login method must remain enabled', - 'admin.requireMfa': '要求雙因素身份驗證(2FA)', - 'admin.requireMfaHint': '未啟用 2FA 的使用者必須先完成設定中的配置才能使用應用。', - 'admin.apiKeys': 'API 金鑰', - 'admin.apiKeysHint': '可選。啟用地點的擴充套件資料,如照片和天氣。', - 'admin.mapsKey': 'Google Maps API 金鑰', - 'admin.mapsKeyHint': '用於地點搜尋。在 console.cloud.google.com 獲取', - 'admin.mapsKeyHintLong': '沒有 API 金鑰時,使用 OpenStreetMap 搜尋地點。有了 Google API 金鑰,還可以載入照片、評分和營業時間。在 console.cloud.google.com 獲取。', - 'admin.recommended': '推薦', - 'admin.weatherKey': 'OpenWeatherMap API 金鑰', - 'admin.weatherKeyHint': '用於天氣資料。在 openweathermap.org 免費獲取', - 'admin.validateKey': '測試', - 'admin.keyValid': '已連線', - 'admin.keyInvalid': '無效', - 'admin.keySaved': 'API 金鑰已儲存', - 'admin.oidcTitle': '單點登入 (OIDC)', - 'admin.oidcSubtitle': '允許透過 Google、Apple、Authentik 或 Keycloak 等外部提供商登入。', - 'admin.oidcDisplayName': '顯示名稱', - 'admin.oidcIssuer': '頒發者 URL', - 'admin.oidcIssuerHint': '提供商的 OpenID Connect 頒發者 URL。如 https://accounts.google.com', - 'admin.oidcSaved': 'OIDC 配置已儲存', - 'admin.oidcOnlyMode': '停用密碼登入', - 'admin.oidcOnlyModeHint': '啟用後,僅允許 SSO 登入。密碼登入和註冊將被停用。', - - // File Types - 'admin.fileTypes': '允許的檔案型別', - 'admin.fileTypesHint': '配置使用者可以上傳的檔案型別。', - 'admin.fileTypesFormat': '以逗號分隔的副檔名(如 jpg,png,pdf,doc)。使用 * 允許所有型別。', - 'admin.fileTypesSaved': '檔案型別設定已儲存', - - 'admin.placesPhotos.title': '地點照片', - 'admin.placesPhotos.subtitle': '從 Google Places API 獲取照片。停用可節省 API 配額。Wikimedia 照片不受影響。', - 'admin.placesAutocomplete.title': '地點自動補全', - 'admin.placesAutocomplete.subtitle': '使用 Google Places API 提供搜尋建議。停用可節省 API 配額。', - 'admin.placesDetails.title': '地點詳情', - 'admin.placesDetails.subtitle': '從 Google Places API 獲取地點詳細資訊(營業時間、評分、網站)。停用可節省 API 配額。', - 'admin.bagTracking.title': '行李追蹤', - 'admin.bagTracking.subtitle': '為打包物品啟用重量和行李分配', - 'admin.collab.chat.title': '聊天', - 'admin.collab.chat.subtitle': '即時訊息協作', - 'admin.collab.notes.title': '筆記', - 'admin.collab.notes.subtitle': '共享筆記和文件', - 'admin.collab.polls.title': '投票', - 'admin.collab.polls.subtitle': '群組投票和表決', - 'admin.collab.whatsnext.title': '下一步', - 'admin.collab.whatsnext.subtitle': '活動建議和後續步驟', - 'admin.tabs.config': '配置', - 'admin.tabs.defaults': '用戶預設設定', - 'admin.defaultSettings.title': '用戶預設設定', - 'admin.defaultSettings.description': '設定整個執行個體的預設值。未更改設定的用戶將看到這些值。用戶自己的更改始終優先。', - 'admin.defaultSettings.saved': '預設值已儲存', - 'admin.defaultSettings.reset': '重設為內建預設值', - 'admin.defaultSettings.resetToBuiltIn': '重設', - 'admin.tabs.templates': '打包模板', - 'admin.packingTemplates.title': '打包模板', - 'admin.packingTemplates.subtitle': '建立可複用的旅行打包清單', - 'admin.packingTemplates.create': '新建模板', - 'admin.packingTemplates.namePlaceholder': '模板名稱(如:海灘度假)', - 'admin.packingTemplates.empty': '尚未建立模板', - 'admin.packingTemplates.items': '物品', - 'admin.packingTemplates.categories': '分類', - 'admin.packingTemplates.itemName': '物品名稱', - 'admin.packingTemplates.itemCategory': '分類', - 'admin.packingTemplates.categoryName': '分類名稱(如:衣物)', - 'admin.packingTemplates.addCategory': '新增分類', - 'admin.packingTemplates.created': '模板已建立', - 'admin.packingTemplates.deleted': '模板已刪除', - 'admin.packingTemplates.loadError': '載入模板失敗', - 'admin.packingTemplates.createError': '建立模板失敗', - 'admin.packingTemplates.deleteError': '刪除模板失敗', - 'admin.packingTemplates.saveError': '儲存失敗', - - // Addons - 'admin.tabs.addons': '擴充套件', - 'admin.addons.title': '擴充套件', - 'admin.addons.subtitle': '啟用或停用功能以自定義你的 TREK 體驗。', - 'admin.addons.catalog.memories.name': '照片 (Immich)', - 'admin.addons.catalog.memories.description': '透過 Immich 例項分享旅行照片', - 'admin.addons.catalog.mcp.name': 'MCP', - 'admin.addons.catalog.mcp.description': '用於 AI 助手整合的模型上下文協議', - 'admin.addons.catalog.packing.name': '行李', - 'admin.addons.catalog.packing.description': '每次旅行的行李準備清單', - 'admin.addons.catalog.budget.name': '預算', - 'admin.addons.catalog.budget.description': '跟蹤支出並規劃旅行預算', - 'admin.addons.catalog.documents.name': '文件', - 'admin.addons.catalog.documents.description': '儲存和管理旅行文件', - 'admin.addons.catalog.vacay.name': 'Vacay', - 'admin.addons.catalog.vacay.description': '帶日曆檢視的個人假期規劃器', - 'admin.addons.catalog.atlas.name': 'Atlas', - 'admin.addons.catalog.atlas.description': '標記已訪問國家和旅行統計的世界地圖', - 'admin.addons.catalog.collab.name': 'Collab', - 'admin.addons.catalog.collab.description': '旅行規劃的即時筆記、投票和聊天', - 'admin.addons.subtitleBefore': '啟用或停用功能以自定義你的 ', - 'admin.addons.subtitleAfter': ' 體驗。', - 'admin.addons.enabled': '已啟用', - 'admin.addons.disabled': '已停用', - 'admin.addons.type.trip': '旅行', - 'admin.addons.type.global': '全域性', - 'admin.addons.type.integration': '整合', - 'admin.addons.tripHint': '在每次旅行中作為標籤頁顯示', - 'admin.addons.globalHint': '在主導航中作為獨立板塊顯示', - 'admin.addons.integrationHint': '後端服務和 API 整合,無專屬頁面', - 'admin.addons.toast.updated': '擴充套件已更新', - 'admin.addons.toast.error': '更新擴充套件失敗', - 'admin.addons.noAddons': '暫無可用擴充套件', - // Weather info - 'admin.weather.title': '天氣資料', - 'admin.weather.badge': '自 2026 年 3 月 24 日起', - 'admin.weather.description': 'TREK 使用 Open-Meteo 作為天氣資料來源。Open-Meteo 是免費的開源天氣服務——無需 API 金鑰。', - 'admin.weather.forecast': '16 天天氣預報', - 'admin.weather.forecastDesc': '之前為 5 天 (OpenWeatherMap)', - 'admin.weather.climate': '歷史氣候資料', - 'admin.weather.climateDesc': '16 天預報之外的日期使用過去 85 年的平均值', - 'admin.weather.requests': '每天 10,000 次請求', - 'admin.weather.requestsDesc': '免費,無需 API 金鑰', - 'admin.weather.locationHint': '天氣基於每天中第一個有座標的地點。如果當天沒有分配地點,則使用地點列表中的任意地點作為參考。', - - // MCP Tokens - 'admin.tabs.mcpTokens': 'MCP 存取', - 'admin.mcpTokens.title': 'MCP 存取', - 'admin.mcpTokens.subtitle': '管理所有使用者的 OAuth 工作階段和 API 令牌', - 'admin.mcpTokens.sectionTitle': 'API 令牌', - 'admin.mcpTokens.owner': '所有者', - 'admin.mcpTokens.tokenName': '令牌名稱', - 'admin.mcpTokens.created': '建立時間', - 'admin.mcpTokens.lastUsed': '最後使用', - 'admin.mcpTokens.never': '從未', - 'admin.mcpTokens.empty': '尚未建立任何 MCP 令牌', - 'admin.mcpTokens.deleteTitle': '刪除令牌', - 'admin.mcpTokens.deleteMessage': '此令牌將立即被撤銷。使用者將失去透過此令牌的 MCP 訪問許可權。', - 'admin.mcpTokens.deleteSuccess': '令牌已刪除', - 'admin.mcpTokens.deleteError': '刪除令牌失敗', - 'admin.mcpTokens.loadError': '載入令牌失敗', - 'admin.oauthSessions.sectionTitle': 'OAuth 工作階段', - 'admin.oauthSessions.clientName': '客戶端', - 'admin.oauthSessions.owner': '所有者', - 'admin.oauthSessions.scopes': '權限範圍', - 'admin.oauthSessions.created': '建立時間', - 'admin.oauthSessions.empty': '目前沒有活躍的 OAuth 工作階段', - 'admin.oauthSessions.revokeTitle': '撤銷工作階段', - 'admin.oauthSessions.revokeMessage': '此 OAuth 工作階段將立即被撤銷。客戶端將失去 MCP 存取權限。', - 'admin.oauthSessions.revokeSuccess': '工作階段已撤銷', - 'admin.oauthSessions.revokeError': '撤銷工作階段失敗', - 'admin.oauthSessions.loadError': '載入 OAuth 工作階段失敗', - - // GitHub - 'admin.tabs.github': 'GitHub', - - 'admin.audit.subtitle': '安全與管理員操作記錄(備份、使用者、MFA、設定)。', - 'admin.audit.empty': '暫無審計記錄。', - 'admin.audit.refresh': '重新整理', - 'admin.audit.loadMore': '載入更多', - 'admin.audit.showing': '已載入 {count} 條 · 共 {total} 條', - 'admin.audit.col.time': '時間', - 'admin.audit.col.user': '使用者', - 'admin.audit.col.action': '操作', - 'admin.audit.col.resource': '資源', - 'admin.audit.col.ip': 'IP', - 'admin.audit.col.details': '詳情', - - 'admin.github.title': '版本歷史', - 'admin.github.subtitle': '{repo} 的最新更新', - 'admin.github.latest': '最新', - 'admin.github.prerelease': '預釋出', - 'admin.github.showDetails': '顯示詳情', - 'admin.github.hideDetails': '隱藏詳情', - 'admin.github.loadMore': '載入更多', - 'admin.github.loading': '載入中...', - 'admin.github.support': '幫助我繼續開發 TREK', - 'admin.github.error': '載入版本失敗', - 'admin.github.by': '作者', - - 'admin.update.available': '有可用更新', - 'admin.update.text': 'TREK {version} 已釋出。你當前使用的是 {current}。', - 'admin.update.button': '在 GitHub 檢視', - 'admin.update.install': '安裝更新', - 'admin.update.confirmTitle': '確定安裝更新?', - 'admin.update.confirmText': 'TREK 將從 {current} 更新到 {version}。伺服器將自動重啟。', - 'admin.update.dataInfo': '你的所有資料(旅行、使用者、API 金鑰、上傳檔案、Vacay、Atlas、預算)將被保留。', - 'admin.update.warning': '重啟期間應用將短暫不可用。', - 'admin.update.confirm': '立即更新', - 'admin.update.installing': '更新中…', - 'admin.update.success': '更新已安裝!伺服器正在重啟…', - 'admin.update.failed': '更新失敗', - 'admin.update.backupHint': '建議在更新前建立備份。', - 'admin.update.backupLink': '前往備份', - 'admin.update.howTo': '如何更新', - 'admin.update.dockerText': '你的 TREK 例項執行在 Docker 中。要更新到 {version},請在伺服器上執行以下命令:', - 'admin.update.reloadHint': '請在幾秒後重新整理頁面。', - - // Vacay addon - 'vacay.subtitle': '規劃和管理假期', - 'vacay.settings': '設定', - 'vacay.year': '年份', - 'vacay.addYear': '新增下一年', - 'vacay.addPrevYear': '新增上一年', - 'vacay.removeYear': '移除年份', - 'vacay.removeYearConfirm': '移除 {year}?', - 'vacay.removeYearHint': '該年度所有假期記錄和公司假日將被永久刪除。', - 'vacay.remove': '移除', - 'vacay.persons': '成員', - 'vacay.noPersons': '暫無成員', - 'vacay.addPerson': '新增成員', - 'vacay.editPerson': '編輯成員', - 'vacay.removePerson': '移除成員', - 'vacay.removePersonConfirm': '移除 {name}?', - 'vacay.removePersonHint': '該成員的所有假期記錄將被永久刪除。', - 'vacay.personName': '姓名', - 'vacay.personNamePlaceholder': '輸入姓名', - 'vacay.color': '顏色', - 'vacay.add': '新增', - 'vacay.legend': '圖例', - 'vacay.publicHoliday': '公共假日', - 'vacay.companyHoliday': '公司假日', - 'vacay.weekend': '週末', - 'vacay.modeVacation': '休假', - 'vacay.modeCompany': '公司假日', - 'vacay.entitlement': '年假額度', - 'vacay.entitlementDays': '天', - 'vacay.used': '已用', - 'vacay.remaining': '剩餘', - 'vacay.carriedOver': '從 {year} 結轉', - 'vacay.blockWeekends': '鎖定週末', - 'vacay.blockWeekendsHint': '禁止在週六和週日安排假期', - 'vacay.weekendDays': '週末', - 'vacay.mon': '週一', - 'vacay.tue': '週二', - 'vacay.wed': '週三', - 'vacay.thu': '週四', - 'vacay.fri': '週五', - 'vacay.sat': '週六', - 'vacay.sun': '週日', - 'vacay.publicHolidays': '公共假日', - 'vacay.publicHolidaysHint': '在日曆中標記公共假日', - 'vacay.selectCountry': '選擇國家', - 'vacay.selectRegion': '選擇地區(可選)', - 'vacay.companyHolidays': '公司假日', - 'vacay.companyHolidaysHint': '允許標記公司統一休假日', - 'vacay.companyHolidaysNoDeduct': '公司假日不計入年假天數。', - 'vacay.weekStart': '每週開始於', - 'vacay.weekStartHint': '選擇日曆週從週一還是週日開始', - 'vacay.carryOver': '結轉', - 'vacay.carryOverHint': '自動將剩餘年假天數結轉到下一年', - 'vacay.sharing': '共享', - 'vacay.sharingHint': '與其他 TREK 使用者共享你的假期計劃', - 'vacay.owner': '所有者', - 'vacay.shareEmailPlaceholder': 'TREK 使用者郵箱', - 'vacay.shareSuccess': '計劃共享成功', - 'vacay.shareError': '無法共享計劃', - 'vacay.dissolve': '解除合併', - 'vacay.dissolveHint': '重新分離日曆。你的記錄將被保留。', - 'vacay.dissolveAction': '解除', - 'vacay.dissolved': '日曆已分離', - 'vacay.fusedWith': '已合併', - 'vacay.you': '你', - 'vacay.noData': '暫無資料', - 'vacay.changeColor': '更改顏色', - 'vacay.inviteUser': '邀請使用者', - 'vacay.inviteHint': '邀請其他 TREK 使用者共享合併的假期日曆。', - 'vacay.selectUser': '選擇使用者', - 'vacay.sendInvite': '傳送邀請', - 'vacay.inviteSent': '邀請已傳送', - 'vacay.inviteError': '無法傳送邀請', - 'vacay.pending': '待處理', - 'vacay.noUsersAvailable': '沒有可用使用者', - 'vacay.accept': '接受', - 'vacay.decline': '拒絕', - 'vacay.acceptFusion': '接受併合並', - 'vacay.inviteTitle': '合併請求', - 'vacay.inviteWantsToFuse': '想要與你共享假期日曆。', - 'vacay.fuseInfo1': '你們雙方將在一個共享日曆中看到所有假期記錄。', - 'vacay.fuseInfo2': '雙方都可以為對方建立和編輯記錄。', - 'vacay.fuseInfo3': '雙方都可以刪除記錄和修改年假額度。', - 'vacay.fuseInfo4': '公共假日和公司假日等設定將共享。', - 'vacay.fuseInfo5': '任何一方都可以隨時解除合併。你的記錄將被保留。', - 'vacay.addCalendar': '新增日曆', - 'vacay.calendarColor': '顏色', - 'vacay.calendarLabel': '標籤', - 'vacay.noCalendars': '無日曆', - 'nav.myTrips': '我的旅行', - - // Atlas addon - 'atlas.subtitle': '你的全球旅行足跡', - 'atlas.countries': '國家', - 'atlas.trips': '旅行', - 'atlas.places': '地點', - 'atlas.days': '天', - 'atlas.visitedCountries': '已訪問國家', - 'atlas.cities': '城市', - 'atlas.noData': '暫無旅行資料', - 'atlas.noDataHint': '建立旅行並新增地點以檢視世界地圖', - 'atlas.lastTrip': '上次旅行', - 'atlas.nextTrip': '下次旅行', - 'atlas.daysLeft': '天后出發', - 'atlas.streak': '連續', - 'atlas.year': '年', - 'atlas.years': '年', - 'atlas.yearInRow': '年連續', - 'atlas.yearsInRow': '年連續', - 'atlas.tripIn': '次旅行在', - 'atlas.tripsIn': '次旅行在', - 'atlas.since': '自', - 'atlas.europe': '歐洲', - 'atlas.asia': '亞洲', - 'atlas.northAmerica': '北美洲', - 'atlas.southAmerica': '南美洲', - 'atlas.africa': '非洲', - 'atlas.oceania': '大洋洲', - 'atlas.other': '其他', - 'atlas.firstVisit': '首次旅行', - 'atlas.lastVisitLabel': '最近旅行', - 'atlas.tripSingular': '次旅行', - 'atlas.tripPlural': '次旅行', - 'atlas.placeVisited': '個地點已訪問', - 'atlas.placesVisited': '個地點已訪問', - 'atlas.statsTab': '統計', - 'atlas.bucketTab': '心願單', - 'atlas.addBucket': '新增到心願單', - 'atlas.bucketNamePlaceholder': '地點或目的地...', - 'atlas.bucketNotesPlaceholder': '備註(可選)', - 'atlas.bucketEmpty': '你的心願單是空的', - 'atlas.bucketEmptyHint': '新增你夢想去的地方', - 'atlas.unmark': '移除', - 'atlas.confirmMark': '將此國家標記為已訪問?', - 'atlas.confirmUnmark': '從已訪問列表中移除此國家?', - 'atlas.confirmUnmarkRegion': '從已訪問列表中移除此地區?', - 'atlas.markVisited': '標記為已訪問', - 'atlas.markVisitedHint': '將此國家新增到已訪問列表', - 'atlas.markRegionVisitedHint': '將此地區新增到已訪問列表', - 'atlas.addToBucket': '新增到心願單', - 'atlas.addPoi': '新增地點', - 'atlas.searchCountry': '搜尋國家...', - 'atlas.month': '月份', - 'atlas.addToBucketHint': '儲存為想去的地方', - 'atlas.bucketWhen': '你計劃什麼時候去?', - - // Trip Planner - 'trip.tabs.plan': '計劃', - 'trip.tabs.transports': '交通', - 'trip.tabs.reservations': '預訂', - 'trip.tabs.reservationsShort': '預訂', - 'trip.tabs.packing': '行李清單', - 'trip.tabs.packingShort': '行李', - 'trip.tabs.lists': '清單', - 'trip.tabs.listsShort': '清單', - 'trip.tabs.budget': '預算', - 'trip.tabs.files': '檔案', - 'trip.loading': '載入旅行中...', - 'trip.loadingPhotos': '正在載入地點照片...', - 'trip.mobilePlan': '計劃', - 'trip.mobilePlaces': '地點', - 'trip.toast.placeUpdated': '地點已更新', - 'trip.toast.placeAdded': '地點已新增', - 'trip.toast.placeDeleted': '地點已刪除', - 'trip.toast.selectDay': '請先選擇一天', - 'trip.toast.assignedToDay': '地點已分配到當天', - 'trip.toast.reorderError': '排序失敗', - 'trip.toast.reservationUpdated': '預訂已更新', - 'trip.toast.reservationAdded': '預訂已新增', - 'trip.toast.deleted': '已刪除', - 'trip.confirm.deletePlace': '確定要刪除這個地點嗎?', - 'trip.confirm.deletePlaces': '刪除 {count} 個地點?', - 'trip.toast.placesDeleted': '已刪除 {count} 個地點', - - // Day Plan Sidebar - 'dayplan.emptyDay': '當天暫無計劃', - 'dayplan.addNote': '新增備註', - 'dayplan.editNote': '編輯備註', - 'dayplan.noteAdd': '新增備註', - 'dayplan.noteEdit': '編輯備註', - 'dayplan.noteTitle': '備註', - 'dayplan.noteSubtitle': '每日備註', - 'dayplan.totalCost': '總費用', - 'dayplan.days': '天', - 'dayplan.dayN': '第 {n} 天', - 'dayplan.calculating': '計算中...', - 'dayplan.route': '路線', - 'dayplan.optimize': '最佳化', - 'dayplan.optimized': '路線已最佳化', - 'dayplan.routeError': '路線計算失敗', - 'dayplan.toast.needTwoPlaces': '路線最佳化至少需要兩個地點', - 'dayplan.toast.routeOptimized': '路線已最佳化', - 'dayplan.toast.noGeoPlaces': '未找到有座標的地點用於路線計算', - 'dayplan.confirmed': '已確認', - 'dayplan.pendingRes': '待確認', - 'dayplan.pdf': 'PDF', - 'dayplan.pdfTooltip': '匯出當天計劃為 PDF', - 'dayplan.pdfError': 'PDF 匯出失敗', - 'dayplan.cannotReorderTransport': '有固定時間的預訂無法重新排序', - 'dayplan.confirmRemoveTimeTitle': '移除時間?', - 'dayplan.confirmRemoveTimeBody': '此地點有固定時間({time})。移動後將移除時間並允許自由排序。', - 'dayplan.confirmRemoveTimeAction': '移除時間並移動', - 'dayplan.cannotDropOnTimed': '無法將專案放置在有固定時間的條目之間', - 'dayplan.cannotBreakChronology': '這將打亂已計劃專案和預訂的時間順序', - - // Places Sidebar - 'places.addPlace': '新增地點/活動', - 'places.importFile': '匯入檔案', - 'places.sidebarDrop': '拖放以匯入', - 'places.importFileHint': '從 Google My Maps、Google Earth 或 GPS 追蹤器等工具匯入 .gpx、.kml 或 .kmz 檔案。', - 'places.importFileDropHere': '點選以選取檔案或拖放至此處', - 'places.importFileDropActive': '放開檔案以選取', - 'places.importFileUnsupported': '不支援的檔案類型,請使用 .gpx、.kml 或 .kmz。', - 'places.importFileTooLarge': '檔案過大。最大上傳大小為 {maxMb} MB。', - 'places.importFileError': '匯入失敗', - 'places.importAllSkipped': '所有地點已在行程中。', - 'places.gpxImported': '已從 GPX 匯入 {count} 個地點', - 'places.gpxImportTypes': '要匯入什麼?', - 'places.gpxImportWaypoints': '路點', - 'places.gpxImportRoutes': '路線', - 'places.gpxImportTracks': '軌跡(含路徑幾何)', - 'places.gpxImportNoneSelected': '請至少選擇一種匯入類型。', - 'places.kmlImportTypes': '要匯入什麼?', - 'places.kmlImportPoints': '點(Placemarks)', - 'places.kmlImportPaths': '路徑(LineStrings)', - 'places.kmlImportNoneSelected': '請至少選擇一種類型。', - 'places.selectionCount': '已選 {count} 項', - 'places.deleteSelected': '刪除所選', - 'places.kmlKmzImported': '已從 KMZ/KML 匯入 {count} 個地點', - 'places.urlResolved': '已從 URL 匯入地點', - 'places.importList': '列表匯入', - 'places.kmlKmzSummaryValues': 'Placemarks:{total} • 已匯入:{created} • 已略過:{skipped}', - 'places.importGoogleList': 'Google 列表', - 'places.importNaverList': 'Naver 列表', - 'places.googleListHint': '貼上共享的 Google Maps 列表連結以匯入所有地點。', - 'places.googleListImported': '已從"{list}"匯入 {count} 個地點', - 'places.googleListError': 'Google Maps 列表匯入失敗', - 'places.naverListHint': '貼上共享的 Naver Maps 列表連結以匯入所有地點。', - 'places.naverListImported': '已從"{list}"匯入 {count} 個地點', - 'places.naverListError': 'Naver Maps 列表匯入失敗', - 'places.viewDetails': '檢視詳情', - 'places.assignToDay': '新增到哪一天?', - 'places.all': '全部', - 'places.unplanned': '未規劃', - 'places.filterTracks': '路線', - 'places.search': '搜尋地點...', - 'places.allCategories': '所有分類', - 'places.categoriesSelected': '個分類', - 'places.clearFilter': '清除篩選', - 'places.count': '{count} 個地點', - 'places.countSingular': '1 個地點', - 'places.allPlanned': '所有地點已規劃', - 'places.noneFound': '未找到地點', - 'places.editPlace': '編輯地點', - 'places.formName': '名稱', - 'places.formNamePlaceholder': '如:埃菲爾鐵塔', - 'places.formDescription': '描述', - 'places.formDescriptionPlaceholder': '簡短描述...', - 'places.formAddress': '地址', - 'places.formAddressPlaceholder': '街道、城市、國家', - 'places.formLat': '緯度(如 48.8566)', - 'places.formLng': '經度(如 2.3522)', - 'places.formCategory': '分類', - 'places.noCategory': '無分類', - 'places.categoryNamePlaceholder': '分類名稱', - 'places.formTime': '時間', - 'places.startTime': '開始', - 'places.endTime': '結束', - 'places.endTimeBeforeStart': '結束時間早於開始時間', - 'places.timeCollision': '時間衝突:', - 'places.formWebsite': '網站', - 'places.formNotes': '備註', - 'places.formNotesPlaceholder': '個人備註...', - 'places.formReservation': '預訂', - 'places.reservationNotesPlaceholder': '預訂備註、確認號...', - 'places.mapsSearchPlaceholder': '搜尋地點...', - 'places.mapsSearchError': '地點搜尋失敗。', - 'places.loadingDetails': '正在載入地點詳情…', - 'places.osmHint': '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。', - 'places.osmActive': '透過 OpenStreetMap 搜尋(無照片、評分或營業時間)。在設定中新增 Google API 金鑰以獲取增強資料。', - 'places.categoryCreateError': '建立分類失敗', - 'places.nameRequired': '請輸入名稱', - 'places.saveError': '儲存失敗', - // Place Inspector - 'inspector.opened': '營業中', - 'inspector.closed': '已關閉', - 'inspector.openingHours': '營業時間', - 'inspector.showHours': '顯示營業時間', - 'inspector.files': '檔案', - 'inspector.filesCount': '{count} 個檔案', - 'inspector.removeFromDay': '從當天移除', - 'inspector.remove': '刪除', - 'inspector.addToDay': '新增到當天', - 'inspector.confirmedRes': '已確認預訂', - 'inspector.pendingRes': '待確認預訂', - 'inspector.google': '在 Google Maps 中開啟', - 'inspector.website': '開啟網站', - 'inspector.addRes': '預訂', - 'inspector.editRes': '編輯預訂', - 'inspector.participants': '參與者', - 'inspector.trackStats': '軌跡資料', - - // Reservations - 'reservations.title': '預訂', - 'reservations.empty': '暫無預訂', - 'reservations.emptyHint': '新增航班、酒店等預訂資訊', - 'reservations.add': '新增預訂', - 'reservations.addManual': '手動新增', - 'reservations.placeHint': '提示:建議從地點直接建立預訂,以便與日程計劃關聯。', - 'reservations.confirmed': '已確認', - 'reservations.pending': '待確認', - 'reservations.summary': '{confirmed} 已確認,{pending} 待確認', - 'reservations.fromPlan': '來自計劃', - 'reservations.showFiles': '檢視檔案', - 'reservations.editTitle': '編輯預訂', - 'reservations.status': '狀態', - 'reservations.datetime': '日期和時間', - 'reservations.startTime': '開始時間', - 'reservations.endTime': '結束時間', - 'reservations.date': '日期', - 'reservations.time': '時間', - 'reservations.timeAlt': '時間(備選,如 19:30)', - 'reservations.notes': '備註', - 'reservations.notesPlaceholder': '其他備註...', - 'reservations.meta.airline': '航空公司', - 'reservations.meta.flightNumber': '航班號', - 'reservations.meta.from': '出發', - 'reservations.meta.to': '到達', - 'reservations.needsReview': '待確認', - 'reservations.needsReviewHint': '無法自動匹配機場 — 請確認位置。', - 'reservations.searchLocation': '搜尋車站、港口、地址...', - 'airport.searchPlaceholder': '機場代碼或城市(例如 FRA)', - 'map.connections': '連接', - 'map.showConnections': '顯示預訂路線', - 'map.hideConnections': '隱藏預訂路線', - 'settings.bookingLabels': '預訂路線標籤', - 'settings.bookingLabelsHint': '在地圖上顯示車站 / 機場名稱。關閉時僅顯示圖示。', - 'reservations.meta.trainNumber': '車次', - 'reservations.meta.platform': '站臺', - 'reservations.meta.seat': '座位', - 'reservations.meta.checkIn': '入住', - 'reservations.meta.checkInUntil': '入住截止', - 'reservations.meta.checkOut': '退房', - 'reservations.meta.linkAccommodation': '住宿', - 'reservations.meta.pickAccommodation': '關聯住宿', - 'reservations.meta.noAccommodation': '無', - 'reservations.meta.hotelPlace': '住宿', - 'reservations.meta.pickHotel': '選擇住宿', - 'reservations.meta.fromDay': '從', - 'reservations.meta.toDay': '到', - 'reservations.meta.selectDay': '選擇日期', - 'reservations.type.flight': '航班', - 'reservations.type.hotel': '住宿', - 'reservations.type.restaurant': '餐廳', - 'reservations.type.train': '火車', - 'reservations.type.car': '汽車', - 'reservations.type.cruise': '郵輪', - 'reservations.type.event': '活動', - 'reservations.type.tour': '旅遊團', - 'reservations.type.other': '其他', - 'reservations.confirm.delete': '確定要刪除預訂「{name}」嗎?', - 'reservations.confirm.deleteTitle': '刪除預訂?', - 'reservations.confirm.deleteBody': '"{name}" 將被永久刪除。', - 'reservations.toast.updated': '預訂已更新', - 'reservations.toast.removed': '預訂已刪除', - 'reservations.toast.fileUploaded': '檔案已上傳', - 'reservations.toast.uploadError': '上傳失敗', - 'reservations.newTitle': '新建預訂', - 'reservations.bookingType': '預訂型別', - 'reservations.titleLabel': '標題', - 'reservations.titlePlaceholder': '如:漢莎 LH123、阿德隆酒店...', - 'reservations.locationAddress': '地點 / 地址', - 'reservations.locationPlaceholder': '地址、機場、酒店...', - 'reservations.confirmationCode': '預訂碼', - 'reservations.confirmationPlaceholder': '如:ABC12345', - 'reservations.day': '日期', - 'reservations.noDay': '無日期', - 'reservations.place': '地點', - 'reservations.noPlace': '無地點', - 'reservations.pendingSave': '將被儲存…', - 'reservations.uploading': '上傳中...', - 'reservations.attachFile': '附加檔案', - 'reservations.linkExisting': '關聯已有檔案', - 'reservations.toast.saveError': '儲存失敗', - 'reservations.toast.updateError': '更新失敗', - 'reservations.toast.deleteError': '刪除失敗', - 'reservations.confirm.remove': '移除「{name}」的預訂?', - 'reservations.linkAssignment': '關聯日程分配', - 'reservations.pickAssignment': '從計劃中選擇一個分配...', - 'reservations.noAssignment': '無關聯(獨立)', - 'reservations.price': '價格', - 'reservations.budgetCategory': '預算分類', - 'reservations.budgetCategoryPlaceholder': '如:交通、住宿', - 'reservations.budgetCategoryAuto': '自動(依預訂類型)', - 'reservations.budgetHint': '儲存時將自動建立預算條目。', - 'reservations.departureDate': '出發日期', - 'reservations.arrivalDate': '到達日期', - 'reservations.departureTime': '出發時間', - 'reservations.arrivalTime': '到達時間', - 'reservations.pickupDate': '取車日期', - 'reservations.returnDate': '還車日期', - 'reservations.pickupTime': '取車時間', - 'reservations.returnTime': '還車時間', - 'reservations.endDate': '結束日期', - 'reservations.meta.departureTimezone': '出發時區', - 'reservations.meta.arrivalTimezone': '到達時區', - 'reservations.span.departure': '出發', - 'reservations.span.arrival': '到達', - 'reservations.span.inTransit': '途中', - 'reservations.span.pickup': '取車', - 'reservations.span.return': '還車', - 'reservations.span.active': '進行中', - 'reservations.span.start': '開始', - 'reservations.span.end': '結束', - 'reservations.span.ongoing': '進行中', - 'reservations.validation.endBeforeStart': '結束日期/時間必須晚於開始日期/時間', - 'reservations.addBooking': '新增預訂', - - // Budget - 'budget.title': '預算', - 'budget.exportCsv': '匯出 CSV', - 'budget.emptyTitle': '尚未建立預算', - 'budget.emptyText': '建立分類和條目來規劃旅行預算', - 'budget.emptyPlaceholder': '輸入分類名稱...', - 'budget.createCategory': '建立分類', - 'budget.category': '分類', - 'budget.categoryName': '分類名稱', - 'budget.table.name': '名稱', - 'budget.table.total': '合計', - 'budget.table.persons': '人數', - 'budget.table.days': '天數', - 'budget.table.perPerson': '人均', - 'budget.table.perDay': '日均', - 'budget.table.perPersonDay': '人日均', - 'budget.table.note': '備註', - 'budget.table.date': '日期', - 'budget.newEntry': '新建條目', - 'budget.defaultEntry': '新建條目', - 'budget.defaultCategory': '新分類', - 'budget.total': '合計', - 'budget.totalBudget': '總預算', - 'budget.byCategory': '按分類', - 'budget.editTooltip': '點選編輯', - 'budget.linkedToReservation': '已連結至預訂——請在那裡編輯名稱', - 'budget.confirm.deleteCategory': '確定刪除分類「{name}」及其 {count} 個條目?', - 'budget.deleteCategory': '刪除分類', - 'budget.perPerson': '人均', - 'budget.paid': '已支付', - 'budget.open': '未支付', - 'budget.noMembers': '未分配成員', - 'budget.settlement': '結算', - 'budget.settlementInfo': '點選預算專案上的成員頭像將其標記為綠色——表示該成員已付款。結算會顯示誰欠誰多少。', - 'budget.netBalances': '淨餘額', - - // Files - 'files.title': '檔案', - 'files.pageTitle': '檔案與文件', - 'files.subtitle': '{trip} 的 {count} 個檔案', - 'files.download': '下載', - 'files.openError': '無法開啟檔案', - 'files.downloadPdf': '下載 PDF', - 'files.count': '{count} 個檔案', - 'files.countSingular': '1 個檔案', - 'files.uploaded': '已上傳 {count} 個', - 'files.uploadError': '上傳失敗', - 'files.dropzone': '將檔案拖放到此處', - 'files.dropzoneHint': '或點選瀏覽', - 'files.allowedTypes': '圖片、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大 50 MB', - 'files.uploading': '上傳中...', - 'files.filterAll': '全部', - 'files.filterPdf': 'PDF', - 'files.filterImages': '圖片', - 'files.filterDocs': '文件', - 'files.filterCollab': '協作筆記', - 'files.sourceCollab': '來自協作筆記', - 'files.empty': '暫無檔案', - 'files.emptyHint': '上傳檔案以附加到旅行中', - 'files.openTab': '在新標籤頁中開啟', - 'files.confirm.delete': '確定要刪除此檔案嗎?', - 'files.toast.deleted': '檔案已刪除', - 'files.toast.deleteError': '刪除檔案失敗', - 'files.sourcePlan': '日程計劃', - 'files.sourceBooking': '預訂', - 'files.sourceTransport': '交通', - 'files.attach': '附加', - 'files.pasteHint': '也可以從剪貼簿貼上圖片 (Ctrl+V)', - 'files.trash': '回收站', - 'files.trashEmpty': '回收站為空', - 'files.emptyTrash': '清空回收站', - 'files.restore': '恢復', - 'files.star': '收藏', - 'files.unstar': '取消收藏', - 'files.assign': '分配', - 'files.assignTitle': '分配檔案', - 'files.assignPlace': '地點', - 'files.assignBooking': '預訂', - 'files.assignTransport': '交通', - 'files.unassigned': '未分配', - 'files.unlink': '移除關聯', - 'files.toast.trashed': '已移至回收站', - 'files.toast.restored': '檔案已恢復', - 'files.toast.trashEmptied': '回收站已清空', - 'files.toast.assigned': '檔案已分配', - 'files.toast.assignError': '分配失敗', - 'files.toast.restoreError': '恢復失敗', - 'files.confirm.permanentDelete': '永久刪除此檔案?此操作無法撤銷。', - 'files.confirm.emptyTrash': '永久刪除回收站中的所有檔案?此操作無法撤銷。', - 'files.noteLabel': '備註', - 'files.notePlaceholder': '新增備註...', - - // Packing - 'packing.title': '行李清單', - 'packing.empty': '行李清單為空', - 'packing.import': '匯入', - 'packing.importTitle': '匯入裝箱清單', - 'packing.importHint': '每行一個物品。可選用逗號、分號或製表符分隔類別和數量:名稱, 類別, 數量', - 'packing.importPlaceholder': '牙刷\n防曬霜, 衛生\nT恤, 衣物, 5\n護照, 證件', - 'packing.importCsv': '載入 CSV/TXT', - 'packing.importAction': '匯入 {count}', - 'packing.importSuccess': '已匯入 {count} 項', - 'packing.importError': '匯入失敗', - 'packing.importEmpty': '沒有可匯入的專案', - 'packing.progress': '已打包 {packed}/{total}({percent}%)', - 'packing.clearChecked': '移除 {count} 個已勾選', - 'packing.clearCheckedShort': '移除 {count} 個', - 'packing.suggestions': '建議', - 'packing.suggestionsTitle': '新增建議', - 'packing.allSuggested': '所有建議已新增', - 'packing.allPacked': '全部打包完成!', - 'packing.addPlaceholder': '新增新物品...', - 'packing.categoryPlaceholder': '分類...', - 'packing.filterAll': '全部', - 'packing.filterOpen': '未完成', - 'packing.filterDone': '已完成', - 'packing.emptyTitle': '行李清單為空', - 'packing.emptyHint': '新增物品或使用建議', - 'packing.emptyFiltered': '沒有匹配的物品', - 'packing.menuRename': '重新命名', - 'packing.menuCheckAll': '全部勾選', - 'packing.menuUncheckAll': '取消全部勾選', - 'packing.menuDeleteCat': '刪除分類', - 'packing.addItem': '新增物品', - 'packing.addItemPlaceholder': '物品名稱...', - 'packing.addCategory': '新增分類', - 'packing.newCategoryPlaceholder': '分類名稱(如:衣物)', - 'packing.applyTemplate': '應用模板', - 'packing.template': '模板', - 'packing.templateApplied': '已從模板新增 {count} 個物品', - 'packing.templateError': '應用模板失敗', - 'packing.saveAsTemplate': '儲存為範本', - 'packing.templateName': '範本名稱', - 'packing.templateSaved': '行李清單已儲存為範本', - 'packing.noMembers': '無成員', - 'packing.bags': '行李', - 'packing.noBag': '未分配', - 'packing.totalWeight': '總重量', - 'packing.bagName': '名稱...', - 'packing.addBag': '新增行李', - 'packing.changeCategory': '更改分類', - 'packing.confirm.clearChecked': '確定移除 {count} 個已勾選的物品?', - 'packing.confirm.deleteCat': '確定刪除分類「{name}」及其 {count} 個物品?', - 'packing.defaultCategory': '其他', - 'packing.toast.saveError': '儲存失敗', - 'packing.toast.deleteError': '刪除失敗', - 'packing.toast.renameError': '重新命名失敗', - 'packing.toast.addError': '新增失敗', - - // Packing suggestions - 'packing.suggestions.items': [ - { name: '護照', category: '證件' }, - { name: '身份證', category: '證件' }, - { name: '旅行保險', category: '證件' }, - { name: '機票', category: '證件' }, - { name: '信用卡', category: '財務' }, - { name: '現金', category: '財務' }, - { name: '簽證', category: '證件' }, - { name: 'T恤', category: '衣物' }, - { name: '褲子', category: '衣物' }, - { name: '內衣', category: '衣物' }, - { name: '襪子', category: '衣物' }, - { name: '外套', category: '衣物' }, - { name: '睡衣', category: '衣物' }, - { name: '泳衣', category: '衣物' }, - { name: '雨衣', category: '衣物' }, - { name: '舒適的鞋子', category: '衣物' }, - { name: '牙刷', category: '洗漱用品' }, - { name: '牙膏', category: '洗漱用品' }, - { name: '洗髮水', category: '洗漱用品' }, - { name: '除臭劑', category: '洗漱用品' }, - { name: '防曬霜', category: '洗漱用品' }, - { name: '剃鬚刀', category: '洗漱用品' }, - { name: '充電器', category: '電子產品' }, - { name: '充電寶', category: '電子產品' }, - { name: '耳機', category: '電子產品' }, - { name: '旅行轉換插頭', category: '電子產品' }, - { name: '相機', category: '電子產品' }, - { name: '止痛藥', category: '健康' }, - { name: '創可貼', category: '健康' }, - { name: '消毒液', category: '健康' }, - ], - - // Members / Sharing - 'members.shareTrip': '分享旅行', - 'members.inviteUser': '邀請使用者', - 'members.selectUser': '選擇使用者…', - 'members.invite': '邀請', - 'members.allHaveAccess': '所有使用者均已擁有訪問許可權。', - 'members.access': '訪問許可權', - 'members.person': '人', - 'members.persons': '人', - 'members.you': '你', - 'members.owner': '所有者', - 'members.leaveTrip': '退出旅行', - 'members.removeAccess': '移除訪問許可權', - 'members.confirmLeave': '退出旅行?你將失去訪問許可權。', - 'members.confirmRemove': '移除該使用者的訪問許可權?', - 'members.loadError': '載入成員失敗', - 'members.added': '已新增', - 'members.addError': '新增失敗', - 'members.removed': '成員已移除', - 'members.removeError': '移除失敗', - - // Categories (Admin) - 'categories.title': '分類', - 'categories.subtitle': '管理地點分類', - 'categories.new': '新建分類', - 'categories.empty': '暫無分類', - 'categories.namePlaceholder': '分類名稱', - 'categories.icon': '圖示', - 'categories.color': '顏色', - 'categories.customColor': '選擇自定義顏色', - 'categories.preview': '預覽', - 'categories.defaultName': '分類', - 'categories.update': '更新', - 'categories.create': '建立', - 'categories.confirm.delete': '刪除分類?該分類下的地點不會被刪除。', - 'categories.toast.loadError': '載入分類失敗', - 'categories.toast.nameRequired': '請輸入名稱', - 'categories.toast.updated': '分類已更新', - 'categories.toast.created': '分類已建立', - 'categories.toast.saveError': '儲存失敗', - 'categories.toast.deleted': '分類已刪除', - 'categories.toast.deleteError': '刪除失敗', - - // Backup (Admin) - 'backup.title': '資料備份', - 'backup.subtitle': '資料庫和所有上傳檔案', - 'backup.refresh': '重新整理', - 'backup.upload': '上傳備份', - 'backup.uploading': '上傳中…', - 'backup.create': '建立備份', - 'backup.creating': '建立中…', - 'backup.empty': '暫無備份', - 'backup.createFirst': '建立第一個備份', - 'backup.download': '下載', - 'backup.restore': '恢復', - 'backup.confirm.restore': '恢復備份「{name}」?\n\n所有當前資料將被備份資料替換。', - 'backup.confirm.uploadRestore': '上傳並恢復備份檔案「{name}」?\n\n所有當前資料將被覆蓋。', - 'backup.confirm.delete': '刪除備份「{name}」?', - 'backup.toast.loadError': '載入備份失敗', - 'backup.toast.created': '備份建立成功', - 'backup.toast.createError': '建立備份失敗', - 'backup.toast.restored': '備份已恢復。頁面即將重新整理…', - 'backup.toast.restoreError': '恢復失敗', - 'backup.toast.uploadError': '上傳失敗', - 'backup.toast.deleted': '備份已刪除', - 'backup.toast.deleteError': '刪除失敗', - 'backup.toast.downloadError': '下載失敗', - 'backup.toast.settingsSaved': '自動備份設定已儲存', - 'backup.toast.settingsError': '儲存設定失敗', - 'backup.auto.title': '自動備份', - 'backup.auto.subtitle': '按計劃自動備份', - 'backup.auto.enable': '啟用自動備份', - 'backup.auto.enableHint': '將按所選計劃自動建立備份', - 'backup.auto.interval': '間隔', - 'backup.auto.hour': '執行時間', - 'backup.auto.hourHint': '伺服器本地時間({format} 格式)', - 'backup.auto.dayOfWeek': '星期幾', - 'backup.auto.dayOfMonth': '每月幾號', - 'backup.auto.dayOfMonthHint': '限 1–28 以相容所有月份', - 'backup.auto.scheduleSummary': '計劃', - 'backup.auto.summaryDaily': '每天 {hour}:00', - 'backup.auto.summaryWeekly': '每{day} {hour}:00', - 'backup.auto.summaryMonthly': '每月 {day} 號 {hour}:00', - 'backup.auto.envLocked': 'Docker', - 'backup.auto.envLockedHint': '自動備份透過 Docker 環境變數配置。要更改設定,請更新 docker-compose.yml 並重啟容器。', - 'backup.auto.copyEnv': '複製 Docker 環境變數', - 'backup.auto.envCopied': 'Docker 環境變數已複製到剪貼簿', - 'backup.auto.keepLabel': '自動刪除舊備份', - 'backup.dow.sunday': '週日', - 'backup.dow.monday': '週一', - 'backup.dow.tuesday': '週二', - 'backup.dow.wednesday': '週三', - 'backup.dow.thursday': '週四', - 'backup.dow.friday': '週五', - 'backup.dow.saturday': '週六', - 'backup.interval.hourly': '每小時', - 'backup.interval.daily': '每天', - 'backup.interval.weekly': '每週', - 'backup.interval.monthly': '每月', - 'backup.keep.1day': '1 天', - 'backup.keep.3days': '3 天', - 'backup.keep.7days': '7 天', - 'backup.keep.14days': '14 天', - 'backup.keep.30days': '30 天', - 'backup.keep.forever': '永久保留', - - // Photos - 'photos.title': '照片', - 'photos.subtitle': '{trip} 的 {count} 張照片', - 'photos.dropHere': '將照片拖放至此...', - 'photos.dropHereActive': '將照片拖放至此', - 'photos.captionForAll': '標題(所有)', - 'photos.captionPlaceholder': '可選標題...', - 'photos.addCaption': '新增標題...', - 'photos.allDays': '所有天', - 'photos.noPhotos': '暫無照片', - 'photos.uploadHint': '上傳你的旅行照片', - 'photos.clickToSelect': '或點選選擇', - 'photos.linkPlace': '關聯地點', - 'photos.noPlace': '無地點', - 'photos.uploadN': '上傳 {n} 張照片', - 'photos.linkDay': '關聯天數', - 'photos.noDay': '無天數', - 'photos.dayLabel': '第 {number} 天', - 'photos.photoSelected': '張照片已選擇', - 'photos.photosSelected': '張照片已選擇', - 'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 張照片', - - // Backup restore modal - 'backup.restoreConfirmTitle': '恢復備份?', - 'backup.restoreWarning': '所有當前資料(旅行、地點、使用者、上傳檔案)將被備份資料永久替換。此操作無法撤銷。', - 'backup.restoreTip': '提示:恢復前建議先備份當前狀態。', - 'backup.restoreConfirm': '確認恢復', - - // PDF - 'pdf.travelPlan': '旅行計劃', - 'pdf.planned': '已規劃', - 'pdf.costLabel': '費用 EUR', - 'pdf.preview': 'PDF 預覽', - 'pdf.saveAsPdf': '儲存為 PDF', - - // Planner - 'planner.places': '地點', - 'planner.bookings': '預訂', - 'planner.packingList': '行李清單', - 'planner.documents': '文件', - 'planner.dayPlan': '日程計劃', - 'planner.reservations': '預訂', - 'planner.minTwoPlaces': '至少需要 2 個有座標的地點', - 'planner.noGeoPlaces': '沒有有座標的地點', - 'planner.routeCalculated': '路線已計算', - 'planner.routeCalcFailed': '無法計算路線', - 'planner.routeError': '路線計算錯誤', - 'planner.icsExportFailed': 'ICS 匯出失敗', - 'planner.routeOptimized': '路線已最佳化', - 'planner.reservationUpdated': '預訂已更新', - 'planner.reservationAdded': '預訂已新增', - 'planner.confirmDeleteReservation': '刪除預訂?', - 'planner.reservationDeleted': '預訂已刪除', - 'planner.days': '天', - 'planner.allPlaces': '所有地點', - 'planner.totalPlaces': '共 {n} 個地點', - 'planner.noDaysPlanned': '尚未規劃天數', - 'planner.editTrip': '編輯旅行 \u2192', - 'planner.placeOne': '1 個地點', - 'planner.placeN': '{n} 個地點', - 'planner.addNote': '新增備註', - 'planner.noEntries': '當天無條目', - 'planner.addPlace': '新增地點/活動', - 'planner.addPlaceShort': '+ 新增地點/活動', - 'planner.resPending': '預訂待確認 · ', - 'planner.resConfirmed': '預訂已確認 · ', - 'planner.notePlaceholder': '備註…', - 'planner.noteTimePlaceholder': '時間(可選)', - 'planner.noteExamplePlaceholder': '如:14:30 從中央車站乘 S3,7 號碼頭渡輪,午餐休息…', - 'planner.totalCost': '總費用', - 'planner.searchPlaces': '搜尋地點…', - 'planner.allCategories': '所有分類', - 'planner.noPlacesFound': '未找到地點', - 'planner.addFirstPlace': '新增第一個地點', - 'planner.noReservations': '暫無預訂', - 'planner.addFirstReservation': '新增第一個預訂', - 'planner.new': '新建', - 'planner.addToDay': '+ 天', - 'planner.calculating': '計算中…', - 'planner.route': '路線', - 'planner.optimize': '最佳化', - 'planner.openGoogleMaps': '在 Google Maps 中開啟', - 'planner.selectDayHint': '從左側列表選擇一天以檢視日程計劃', - 'planner.noPlacesForDay': '當天暫無地點', - 'planner.addPlacesLink': '新增地點 \u2192', - 'planner.minTotal': '分鐘 合計', - 'planner.noReservation': '無預訂', - 'planner.removeFromDay': '從當天移除', - 'planner.addToThisDay': '新增到當天', - 'planner.overview': '概覽', - 'planner.noDays': '暫無天數', - 'planner.editTripToAddDays': '編輯旅行以新增天數', - 'planner.dayCount': '{n} 天', - 'planner.clickToUnlock': '點選解鎖', - 'planner.keepPosition': '路線最佳化時保持位置', - 'planner.dayDetails': '日程詳情', - 'planner.dayN': '第 {n} 天', - - // Dashboard Stats - 'stats.countries': '國家', - 'stats.cities': '城市', - 'stats.trips': '旅行', - 'stats.places': '地點', - 'stats.worldProgress': '全球進度', - 'stats.visited': '已訪問', - 'stats.remaining': '未訪問', - 'stats.visitedCountries': '已訪問國家', - - // Day Detail Panel - 'day.precipProb': '降水機率', - 'day.precipitation': '降水量', - 'day.wind': '風速', - 'day.sunrise': '日出', - 'day.sunset': '日落', - 'day.hourlyForecast': '逐小時預報', - 'day.climateHint': '歷史平均值——實際預報在該日期前 16 天內可用。', - 'day.noWeather': '無天氣資料。請新增有座標的地點。', - 'day.overview': '每日概覽', - 'day.accommodation': '住宿', - 'day.addAccommodation': '新增住宿', - 'day.hotelDayRange': '應用到天數', - 'day.noPlacesForHotel': '請先在旅行中新增地點', - 'day.allDays': '全部', - 'day.checkIn': '入住', - 'day.checkInUntil': '截止', - 'day.checkOut': '退房', - 'day.confirmation': '確認號', - 'day.editAccommodation': '編輯住宿', - 'day.reservations': '預訂', - - // Memories / Immich - 'memories.title': '照片', - 'memories.notConnected': '{provider_name} 未連線', - 'memories.notConnectedHint': '在設定中連線您的 {provider_name} 例項以在此旅行中新增照片。', - 'memories.notConnectedMultipleHint': '在設定中連線以下任一照片提供商:{provider_names} 以在此旅行中新增照片。', - 'memories.noDates': '為旅行新增日期以載入照片。', - 'memories.noPhotos': '未找到照片', - 'memories.noPhotosHint': '{provider_name} 中未找到此旅行日期範圍內的照片。', - 'memories.photosFound': '張照片', - 'memories.fromOthers': '來自他人', - 'memories.sharePhotos': '分享照片', - 'memories.sharing': '分享中', - 'memories.reviewTitle': '審查您的照片', - 'memories.reviewHint': '點選照片以將其從分享中排除。', - 'memories.shareCount': '分享 {count} 張照片', - 'memories.providerUrl': '伺服器 URL', - 'memories.providerApiKey': 'API 金鑰', - 'memories.providerUsername': '使用者名稱', - 'memories.providerPassword': '密碼', - 'memories.providerOTP': 'MFA 驗證碼(如已啟用)', - 'memories.skipSSLVerification': '跳過 SSL 憑證驗證', - 'memories.immichAutoUpload': '上傳 Journey 照片時同步到 Immich', - 'memories.providerUrlHintSynology': '在網址中包含照片應用程式路徑,例如 https://nas:5001/photo', - 'memories.testConnection': '測試連線', - 'memories.testShort': '測試', - 'memories.testFirst': '請先測試連線', - 'memories.connected': '已連線', - 'memories.disconnected': '未連線', - 'memories.connectionSuccess': '已連線到 {provider_name}', - 'memories.connectionError': '無法連線到 {provider_name}', - 'memories.saved': '{provider_name} 設定已儲存', - 'memories.providerDisconnectedBanner': '您與 {provider_name} 的連線已中斷。請在設定中重新連線以查看照片。', - 'memories.saveError': '無法儲存 {provider_name} 設定', - 'memories.oldest': '最早優先', - 'memories.newest': '最新優先', - 'memories.allLocations': '所有地點', - 'memories.addPhotos': '新增照片', - 'memories.linkAlbum': '關聯相簿', - 'memories.selectAlbum': '選擇 {provider_name} 相簿', - 'memories.selectAlbumMultiple': '選擇相簿', - 'memories.noAlbums': '未找到相簿', - 'memories.syncAlbum': '同步相簿', - 'memories.unlinkAlbum': '取消關聯', - 'memories.photos': '張照片', - 'memories.selectPhotos': '從 {provider_name} 選擇照片', - 'memories.selectPhotosMultiple': '選擇照片', - 'memories.selectHint': '點選照片以選擇。', - 'memories.selected': '已選擇', - 'memories.addSelected': '新增 {count} 張照片', - 'memories.alreadyAdded': '已新增', - 'memories.private': '私密', - 'memories.stopSharing': '停止分享', - 'memories.tripDates': '旅行日期', - 'memories.allPhotos': '所有照片', - 'memories.confirmShareTitle': '與旅行成員分享?', - 'memories.confirmShareHint': '{count} 張照片將對本次旅行的所有成員可見。你可以稍後將單張照片設為私密。', - 'memories.confirmShareButton': '分享照片', - - // Collab Addon - 'collab.tabs.chat': '聊天', - 'collab.tabs.notes': '筆記', - 'collab.tabs.polls': '投票', - 'collab.whatsNext.title': '接下來', - 'collab.whatsNext.today': '今天', - 'collab.whatsNext.tomorrow': '明天', - 'collab.whatsNext.empty': '暫無活動', - 'collab.whatsNext.until': '至', - 'collab.whatsNext.emptyHint': '有時間安排的活動將顯示在此', - 'collab.chat.send': '傳送', - 'collab.chat.placeholder': '輸入訊息...', - 'collab.chat.empty': '開始對話', - 'collab.chat.emptyHint': '訊息對所有旅行成員可見', - 'collab.chat.emptyDesc': '與旅伴分享想法、計劃和動態', - 'collab.chat.today': '今天', - 'collab.chat.yesterday': '昨天', - 'collab.chat.deletedMessage': '刪除了一條訊息', - 'collab.chat.reply': '回覆', - 'collab.chat.loadMore': '載入更早的訊息', - 'collab.chat.justNow': '剛剛', - 'collab.chat.minutesAgo': '{n} 分鐘前', - 'collab.chat.hoursAgo': '{n} 小時前', - 'collab.notes.title': '筆記', - 'collab.notes.new': '新建筆記', - 'collab.notes.empty': '暫無筆記', - 'collab.notes.emptyHint': '開始記錄想法和計劃', - 'collab.notes.all': '全部', - 'collab.notes.titlePlaceholder': '筆記標題', - 'collab.notes.contentPlaceholder': '寫點什麼...', - 'collab.notes.categoryPlaceholder': '分類', - 'collab.notes.newCategory': '新建分類...', - 'collab.notes.category': '分類', - 'collab.notes.noCategory': '無分類', - 'collab.notes.color': '顏色', - 'collab.notes.save': '儲存', - 'collab.notes.cancel': '取消', - 'collab.notes.edit': '編輯', - 'collab.notes.delete': '刪除', - 'collab.notes.pin': '置頂', - 'collab.notes.unpin': '取消置頂', - 'collab.notes.daysAgo': '{n} 天前', - 'collab.notes.categorySettings': '管理分類', - 'collab.notes.create': '建立', - 'collab.notes.website': '網站', - 'collab.notes.websitePlaceholder': 'https://...', - 'collab.notes.attachFiles': '附加檔案', - 'collab.notes.noCategoriesYet': '暫無分類', - 'collab.notes.emptyDesc': '建立一個筆記開始吧', - 'collab.polls.title': '投票', - 'collab.polls.new': '新建投票', - 'collab.polls.empty': '暫無投票', - 'collab.polls.emptyHint': '向團隊提問並一起投票', - 'collab.polls.question': '問題', - 'collab.polls.questionPlaceholder': '我們應該做什麼?', - 'collab.polls.addOption': '+ 新增選項', - 'collab.polls.optionPlaceholder': '選項 {n}', - 'collab.polls.create': '建立投票', - 'collab.polls.close': '關閉', - 'collab.polls.closed': '已關閉', - 'collab.polls.votes': '{n} 票', - 'collab.polls.vote': '{n} 票', - 'collab.polls.multipleChoice': '多選', - 'collab.polls.multiChoice': '多選', - 'collab.polls.deadline': '截止時間', - 'collab.polls.option': '選項', - 'collab.polls.options': '選項', - 'collab.polls.delete': '刪除', - 'collab.polls.closedSection': '已關閉', - - // Permissions - 'admin.tabs.permissions': '許可權', - 'perm.title': '許可權設定', - 'perm.subtitle': '控制誰可以在應用中執行操作', - 'perm.saved': '許可權設定已儲存', - 'perm.resetDefaults': '恢復預設', - 'perm.customized': '已自定義', - 'perm.level.admin': '僅管理員', - 'perm.level.tripOwner': '旅行所有者', - 'perm.level.tripMember': '旅行成員', - 'perm.level.everybody': '所有人', - 'perm.cat.trip': '旅行管理', - 'perm.cat.members': '成員管理', - 'perm.cat.files': '檔案', - 'perm.cat.content': '內容與日程', - 'perm.cat.extras': '預算、行李與協作', - 'perm.action.trip_create': '建立旅行', - 'perm.action.trip_edit': '編輯旅行詳情', - 'perm.action.trip_delete': '刪除旅行', - 'perm.action.trip_archive': '歸檔 / 取消歸檔旅行', - 'perm.action.trip_cover_upload': '上傳封面圖片', - 'perm.action.member_manage': '新增 / 移除成員', - 'perm.action.file_upload': '上傳檔案', - 'perm.action.file_edit': '編輯檔案後設資料', - 'perm.action.file_delete': '刪除檔案', - 'perm.action.place_edit': '新增 / 編輯 / 刪除地點', - 'perm.action.day_edit': '編輯日程、備註與分配', - 'perm.action.reservation_edit': '管理預訂', - 'perm.action.budget_edit': '管理預算', - 'perm.action.packing_edit': '管理行李清單', - 'perm.action.collab_edit': '協作(筆記、投票、聊天)', - 'perm.action.share_manage': '管理分享連結', - 'perm.actionHint.trip_create': '誰可以建立新旅行', - 'perm.actionHint.trip_edit': '誰可以更改旅行名稱、日期、描述和貨幣', - 'perm.actionHint.trip_delete': '誰可以永久刪除旅行', - 'perm.actionHint.trip_archive': '誰可以歸檔或取消歸檔旅行', - 'perm.actionHint.trip_cover_upload': '誰可以上傳或更改封面圖片', - 'perm.actionHint.member_manage': '誰可以邀請或移除旅行成員', - 'perm.actionHint.file_upload': '誰可以向旅行上傳檔案', - 'perm.actionHint.file_edit': '誰可以編輯檔案描述和連結', - 'perm.actionHint.file_delete': '誰可以將檔案移至回收站或永久刪除', - 'perm.actionHint.place_edit': '誰可以新增、編輯或刪除地點', - 'perm.actionHint.day_edit': '誰可以編輯日程、日程備註和地點分配', - 'perm.actionHint.reservation_edit': '誰可以建立、編輯或刪除預訂', - 'perm.actionHint.budget_edit': '誰可以建立、編輯或刪除預算專案', - 'perm.actionHint.packing_edit': '誰可以管理行李物品和包袋', - 'perm.actionHint.collab_edit': '誰可以建立筆記、投票和傳送訊息', - 'perm.actionHint.share_manage': '誰可以建立或刪除公開分享連結', - // Undo - 'undo.button': '撤銷', - 'undo.tooltip': '撤銷:{action}', - 'undo.assignPlace': '地點已分配至某天', - 'undo.removeAssignment': '地點已從某天移除', - 'undo.reorder': '地點已重新排序', - 'undo.optimize': '路線已最佳化', - 'undo.deletePlace': '地點已刪除', - 'undo.deletePlaces': '地點已刪除', - 'undo.moveDay': '地點已移至另一天', - 'undo.lock': '地點鎖定已切換', - 'undo.importGpx': 'GPX 匯入', - 'undo.importKeyholeMarkup': 'KMZ/KML 匯入', - 'undo.importGoogleList': 'Google 地圖匯入', - 'undo.importNaverList': 'Naver 地圖匯入', - - // Todo - 'todo.subtab.packing': '行李清單', - 'todo.subtab.todo': '待辦事項', - 'todo.completed': '已完成', - 'todo.filter.all': '全部', - 'todo.filter.open': '未完成', - 'todo.filter.done': '已完成', - 'todo.uncategorized': '未分類', - 'todo.namePlaceholder': '任務名稱', - 'todo.descriptionPlaceholder': '說明(可選)', - 'todo.unassigned': '未指派', - 'todo.noCategory': '無分類', - 'todo.hasDescription': '有說明', - 'todo.addItem': '新增任務', - 'todo.sidebar.sortBy': '排序方式', - 'todo.priority': '優先順序', - 'todo.newCategoryLabel': '新增', - 'budget.categoriesLabel': '類別', - 'todo.newCategory': '分類名稱', - 'todo.addCategory': '新增分類', - 'todo.newItem': '新任務', - 'todo.empty': '尚無任務。新增任務以開始!', - 'todo.filter.my': '我的任務', - 'todo.filter.overdue': '已逾期', - 'todo.sidebar.tasks': '任務', - 'todo.sidebar.categories': '分類', - 'todo.detail.title': '任務', - 'todo.detail.description': '說明', - 'todo.detail.category': '分類', - 'todo.detail.dueDate': '到期日', - 'todo.detail.assignedTo': '指派給', - 'todo.detail.delete': '刪除', - 'todo.detail.save': '儲存變更', - 'todo.sortByPrio': '優先順序', - 'todo.detail.priority': '優先順序', - 'todo.detail.noPriority': '無', - 'todo.detail.create': '建立任務', - - // Notifications - 'notifications.title': '通知', - 'notifications.markAllRead': '全部標為已讀', - 'notifications.deleteAll': '全部刪除', - 'notifications.showAll': '檢視所有通知', - 'notifications.empty': '暫無通知', - 'notifications.emptyDescription': '您已全部查閱!', - 'notifications.all': '全部', - 'notifications.unreadOnly': '未讀', - 'notifications.markRead': '標為已讀', - 'notifications.markUnread': '標為未讀', - 'notifications.delete': '刪除', - 'notifications.system': '系統', - 'notifications.synologySessionCleared.title': 'Synology Photos 已斷線', - 'notifications.synologySessionCleared.text': '您的伺服器或帳號已更改 — 請前往設定重新測試連線。', - 'memories.error.loadAlbums': '載入相簿失敗', - 'memories.error.linkAlbum': '關聯相簿失敗', - 'memories.error.unlinkAlbum': '取消關聯相簿失敗', - 'memories.error.syncAlbum': '同步相簿失敗', - 'memories.error.loadPhotos': '載入照片失敗', - 'memories.error.addPhotos': '新增照片失敗', - 'memories.error.removePhoto': '刪除照片失敗', - 'memories.error.toggleSharing': '更新共享設定失敗', - 'undo.addPlace': '地點已新增', - 'undo.done': '已撤銷:{action}', - 'notifications.test.title': '來自 {actor} 的測試通知', - 'notifications.test.text': '這是一條簡單的測試通知。', - 'notifications.test.booleanTitle': '{actor} 請求您的審批', - 'notifications.test.booleanText': '測試布林通知。', - 'notifications.test.accept': '批准', - 'notifications.test.decline': '拒絕', - 'notifications.test.navigateTitle': '檢視詳情', - 'notifications.test.navigateText': '測試跳轉通知。', - 'notifications.test.goThere': '前往', - 'notifications.test.adminTitle': '管理員廣播', - 'notifications.test.adminText': '{actor} 向所有管理員傳送了測試通知。', - 'notifications.test.tripTitle': '{actor} 在您的行程中發帖', - 'notifications.test.tripText': '行程"{trip}"的測試通知。', - - // Journey, Dashboard, Nav, DayPlan - 'common.justNow': '剛剛', - 'common.hoursAgo': '{count}小時前', - 'common.daysAgo': '{count}天前', - 'memories.saveRouteNotConfigured': '此提供商未設定儲存路由', - 'memories.testRouteNotConfigured': '此提供商未設定測試路由', - 'memories.fillRequiredFields': '請填寫所有必填欄位', - 'journey.search.placeholder': '搜尋旅程…', - 'journey.search.noResults': '沒有符合「{query}」的旅程', - 'journey.title': '旅程', - 'journey.subtitle': '即時記錄你的旅行', - 'journey.new': '新建旅程', - 'journey.create': '建立', - 'journey.titlePlaceholder': '你要去哪裡?', - 'journey.empty': '還沒有旅程', - 'journey.emptyHint': '開始記錄你的下一次旅行', - 'journey.deleted': '旅程已刪除', - 'journey.createError': '無法建立旅程', - 'journey.deleteError': '無法刪除旅程', - 'journey.deleteConfirmTitle': '刪除', - 'journey.deleteConfirmMessage': '刪除「{title}」?此操作無法復原。', - 'journey.deleteConfirmGeneric': '確定要刪除嗎?', - 'journey.notFound': '未找到旅程', - 'journey.photos': '照片', - 'journey.timelineEmpty': '還沒有行程', - 'journey.timelineEmptyHint': '新增一個打卡或寫一篇日誌開始記錄', - 'journey.status.draft': '草稿', - 'journey.status.active': '進行中', - 'journey.status.completed': '已完成', - 'journey.status.upcoming': '即將開始', - 'journey.status.archived': '已封存', - 'journey.checkin.add': '打卡', - 'journey.checkin.namePlaceholder': '地點名稱', - 'journey.checkin.notesPlaceholder': '備註(可選)', - 'journey.checkin.save': '儲存', - 'journey.checkin.error': '無法儲存打卡', - 'journey.entry.add': '日誌', - 'journey.entry.edit': '編輯條目', - 'journey.entry.titlePlaceholder': '標題(可選)', - 'journey.entry.bodyPlaceholder': '今天發生了什麼?', - 'journey.entry.save': '儲存', - 'journey.entry.error': '無法儲存條目', - 'journey.photo.add': '照片', - 'journey.photo.uploadError': '上傳失敗', - 'journey.share.share': '分享', - 'journey.share.public': '公開', - 'journey.share.linkCopied': '公開連結已複製', - 'journey.share.disabled': '已關閉公開分享', - 'journey.editor.titlePlaceholder': '給這個瞬間起個名字...', - 'journey.editor.bodyPlaceholder': '講述這一天的故事...', - 'journey.editor.placePlaceholder': '地點(可選)', - 'journey.editor.tagsPlaceholder': '標籤:隱藏寶藏、最佳美食、值得再訪...', - 'journey.visibility.private': '私密', - 'journey.visibility.shared': '共享', - 'journey.visibility.public': '公開', - 'journey.emptyState.title': '你的故事從這裡開始', - 'journey.emptyState.subtitle': '在某個地方打卡或寫下你的第一篇日誌', - 'journey.frontpage.subtitle': '將旅行變成永遠不會忘記的故事', - 'journey.frontpage.createJourney': '建立旅程', - 'journey.frontpage.activeJourney': '進行中的旅程', - 'journey.frontpage.allJourneys': '所有旅程', - 'journey.frontpage.journeys': '個旅程', - 'journey.frontpage.createNew': '建立新旅程', - 'journey.frontpage.createNewSub': '選擇旅行、寫故事、分享你的冒險', - 'journey.frontpage.live': '即時', - 'journey.frontpage.synced': '已同步', - 'journey.frontpage.continueWriting': '繼續撰寫', - 'journey.frontpage.updated': '更新於 {time}', - 'journey.frontpage.suggestionLabel': '旅行剛結束', - 'journey.frontpage.suggestionText': '將 {title} 變成一段旅程', - 'journey.frontpage.dismiss': '忽略', - 'journey.frontpage.journeyName': '旅程名稱', - 'journey.frontpage.namePlaceholder': '例如 東南亞 2026', - 'journey.frontpage.selectTrips': '選擇旅行', - 'journey.frontpage.tripsSelected': '個旅行已選擇', - 'journey.frontpage.trips': '個旅行', - 'journey.frontpage.placesImported': '個地點將被匯入', - 'journey.frontpage.places': '個地點', - 'journey.detail.backToJourney': '返回旅程', - 'journey.detail.syncedWithTrips': '已與旅行同步', - 'journey.detail.addEntry': '新增條目', - 'journey.detail.newEntry': '新建條目', - 'journey.detail.editEntry': '編輯條目', - 'journey.detail.noEntries': '還沒有條目', - 'journey.detail.noEntriesHint': '新增一個旅行以產生骨架條目', - 'journey.detail.noPhotos': '還沒有照片', - 'journey.detail.noPhotosHint': '上傳照片到條目或瀏覽你的 Immich/Synology 相簿', - 'journey.detail.journeyStats': '旅程統計', - 'journey.detail.syncedTrips': '已同步的旅行', - 'journey.detail.noTripsLinked': '尚未關聯旅行', - 'journey.detail.contributors': '貢獻者', - 'journey.detail.readMore': '閱讀更多', - 'journey.detail.prosCons': '優缺點', - 'journey.detail.photos': '照片', - 'journey.detail.day': '第{number}天', - 'journey.detail.places': '個地點', - 'journey.stats.days': '天', - 'journey.stats.cities': '城市', - 'journey.stats.entries': '條目', - 'journey.stats.photos': '照片', - 'journey.stats.places': '地點', - 'journey.skeletons.show': '顯示建議', - 'journey.skeletons.hide': '隱藏建議', - 'journey.verdict.lovedIt': '非常喜歡', - 'journey.verdict.couldBeBetter': '有待改進', - 'journey.synced.places': '個地點', - 'journey.synced.synced': '已同步', - 'journey.editor.discardChangesConfirm': '您有未儲存的變更。要放棄嗎?', - 'journey.editor.uploadFailed': '照片上傳失敗', - 'journey.editor.uploadPhotos': '上傳照片', - 'journey.editor.uploading': '上傳中...', - 'journey.editor.uploadingProgress': '上傳中 {done}/{total}…', - 'journey.editor.uploadPartialFailed': '{total} 張中有 {failed} 張上傳失敗 — 再次儲存以重試', - 'journey.editor.fromGallery': '從相簿', - 'journey.editor.allPhotosAdded': '所有照片已新增', - 'journey.editor.writeStory': '寫下你的故事...', - 'journey.editor.prosCons': '優缺點', - 'journey.editor.pros': '優點', - 'journey.editor.cons': '缺點', - 'journey.editor.proPlaceholder': '好的方面...', - 'journey.editor.conPlaceholder': '不好的方面...', - 'journey.editor.addAnother': '再新增一個', - 'journey.editor.date': '日期', - 'journey.editor.location': '地點', - 'journey.editor.searchLocation': '搜尋地點...', - 'journey.editor.mood': '心情', - 'journey.editor.weather': '天氣', - 'journey.editor.photoFirst': '第1張', - 'journey.editor.makeFirst': '設為第1張', - 'journey.editor.searching': '搜尋中...', - 'journey.mood.amazing': '太棒了', - 'journey.mood.good': '不錯', - 'journey.mood.neutral': '一般', - 'journey.mood.rough': '糟糕', - 'journey.weather.sunny': '晴天', - 'journey.weather.partly': '多雲', - 'journey.weather.cloudy': '陰天', - 'journey.weather.rainy': '雨天', - 'journey.weather.stormy': '暴風雨', - 'journey.weather.cold': '雪天', - 'journey.trips.linkTrip': '關聯旅行', - 'journey.trips.searchTrip': '搜尋旅行', - 'journey.trips.searchPlaceholder': '旅行名稱或目的地...', - 'journey.trips.noTripsAvailable': '沒有可用的旅行', - 'journey.trips.link': '關聯', - 'journey.trips.tripLinked': '旅行已關聯', - 'journey.trips.linkFailed': '關聯旅行失敗', - 'journey.trips.addTrip': '新增旅行', - 'journey.trips.unlinkTrip': '取消關聯旅行', - 'journey.trips.unlinkMessage': '取消關聯「{title}」?此旅行中所有已同步的條目和照片將被永久刪除。此操作無法復原。', - 'journey.trips.unlink': '取消關聯', - 'journey.trips.tripUnlinked': '旅行已取消關聯', - 'journey.trips.unlinkFailed': '取消關聯失敗', - 'journey.trips.noTripsLinkedSettings': '未關聯旅行', - 'journey.contributors.invite': '邀請貢獻者', - 'journey.contributors.searchUser': '搜尋使用者', - 'journey.contributors.searchPlaceholder': '使用者名稱或郵箱...', - 'journey.contributors.noUsers': '未找到使用者', - 'journey.contributors.role': '角色', - 'journey.contributors.added': '貢獻者已新增', - 'journey.contributors.addFailed': '新增貢獻者失敗', - 'journey.share.publicShare': '公開分享', - 'journey.share.createLink': '建立分享連結', - 'journey.share.linkCreated': '分享連結已建立', - 'journey.share.createFailed': '建立連結失敗', - 'journey.share.copy': '複製', - 'journey.share.copied': '已複製!', - 'journey.share.timeline': '時間線', - 'journey.share.gallery': '圖庫', - 'journey.share.map': '地圖', - 'journey.share.removeLink': '移除分享連結', - 'journey.share.linkDeleted': '分享連結已刪除', - 'journey.share.deleteFailed': '刪除失敗', - 'journey.share.updateFailed': '更新失敗', - - // Journey — Invite - 'journey.invite.role': '角色', - 'journey.invite.viewer': '檢視者', - 'journey.invite.editor': '編輯者', - 'journey.invite.invite': '邀請', - 'journey.invite.inviting': '邀請中...', - 'journey.settings.title': '旅程設定', - 'journey.settings.coverImage': '封面圖片', - 'journey.settings.changeCover': '更換封面', - 'journey.settings.addCover': '新增封面圖片', - 'journey.settings.name': '名稱', - 'journey.settings.subtitle': '副標題', - 'journey.settings.subtitlePlaceholder': '例如 泰國、越南和柬埔寨', - 'journey.settings.endJourney': '封存旅程', - 'journey.settings.reopenJourney': '還原旅程', - 'journey.settings.archived': '旅程已封存', - 'journey.settings.reopened': '旅程已重新開啟', - 'journey.settings.endDescription': '隱藏直播標記。您可以隨時重新開啟。', - 'journey.settings.delete': '刪除', - 'journey.settings.deleteJourney': '刪除旅程', - 'journey.settings.deleteMessage': '刪除「{title}」?所有條目和照片將遺失。', - 'journey.settings.saved': '設定已儲存', - 'journey.settings.saveFailed': '儲存失敗', - 'journey.settings.coverUpdated': '封面已更新', - 'journey.settings.coverFailed': '上傳失敗', - 'journey.settings.failedToDelete': '刪除失敗', - 'journey.entries.deleteTitle': '刪除條目', - 'journey.photosUploaded': '{count} 張照片已上傳', - 'journey.photosUploadFailed': '部分照片上傳失敗', - 'journey.photosAdded': '{count} 張照片已新增', - 'journey.public.notFound': '未找到', - 'journey.public.notFoundMessage': '此旅程不存在或連結已過期。', - 'journey.public.readOnly': '唯讀 · 公開旅程', - 'journey.public.tagline': '旅行資源與探索工具包', - 'journey.public.sharedVia': '分享自', - 'journey.public.madeWith': '由', - 'journey.pdf.journeyBook': '旅程手冊', - 'journey.pdf.madeWith': '由 TREK 製作', - 'journey.pdf.day': '第', - 'journey.pdf.theEnd': '終', - 'journey.pdf.saveAsPdf': '儲存為 PDF', - 'journey.pdf.pages': '頁', - 'journey.picker.tripPeriod': '旅行期間', - 'journey.picker.dateRange': '日期範圍', - 'journey.picker.allPhotos': '所有照片', - 'journey.picker.albums': '相簿', - 'journey.picker.selected': '已選擇', - 'journey.picker.addTo': '新增至', - 'journey.picker.newGallery': '新相簿', - 'journey.picker.selectAll': '全選', - 'journey.picker.deselectAll': '取消全選', - 'journey.picker.noAlbums': '未找到相簿', - 'journey.picker.selectDate': '選擇日期', - 'journey.picker.search': '搜尋', - 'dashboard.greeting.morning': '早安,', - 'dashboard.greeting.afternoon': '午安,', - 'dashboard.greeting.evening': '晚安,', - 'dashboard.mobile.liveNow': '進行中', - 'dashboard.mobile.tripProgress': '旅行進度', - 'dashboard.mobile.daysLeft': '還剩 {count} 天', - 'dashboard.mobile.places': '地點', - 'dashboard.mobile.buddies': '旅伴', - 'dashboard.mobile.newTrip': '新建旅行', - 'dashboard.mobile.currency': '貨幣', - 'dashboard.mobile.timezone': '時區', - 'dashboard.mobile.upcomingTrips': '即將到來的旅行', - 'dashboard.mobile.yourTrips': '我的旅行', - 'dashboard.mobile.trips': '個旅行', - 'dashboard.mobile.starts': '出發', - 'dashboard.mobile.duration': '時長', - 'dashboard.mobile.day': '天', - 'dashboard.mobile.days': '天', - 'dashboard.mobile.ongoing': '進行中', - 'dashboard.mobile.startsToday': '今天出發', - 'dashboard.mobile.tomorrow': '明天', - 'dashboard.mobile.inDays': '{count} 天後', - 'dashboard.mobile.inMonths': '{count} 個月後', - 'dashboard.mobile.completed': '已完成', - 'dashboard.mobile.currencyConverter': '匯率轉換', - 'nav.profile': '個人資料', - 'nav.bottomSettings': '設定', - 'nav.bottomAdmin': '管理設定', - 'nav.bottomLogout': '退出登入', - 'nav.bottomAdminBadge': '管理員', - 'dayplan.mobile.addPlace': '新增地點', - 'dayplan.mobile.searchPlaces': '搜尋地點...', - 'dayplan.mobile.allAssigned': '所有地點已分配', - 'dayplan.mobile.noMatch': '無匹配', - 'dayplan.mobile.createNew': '建立新地點', - 'admin.addons.catalog.journey.name': '旅程', - 'admin.addons.catalog.journey.description': '旅行追蹤與旅行日誌,包含打卡、照片和每日故事', - 'notifications.versionAvailable.title': '有可用更新', - 'notifications.versionAvailable.text': 'TREK {version} 現已推出。', - 'notifications.versionAvailable.button': '查看詳情', - 'notif.test.title': '[測試] 通知', - 'notif.test.simple.text': '這是一則簡單的測試通知。', - 'notif.test.boolean.text': '你是否接受這則測試通知?', - 'notif.test.navigate.text': '點擊下方前往儀表板。', - 'notif.trip_invite.title': '旅行邀請', - 'notif.trip_invite.text': '{actor} 邀請你加入 {trip}', - 'notif.booking_change.title': '預訂已更新', - 'notif.booking_change.text': '{actor} 更新了 {trip} 中的預訂', - 'notif.trip_reminder.title': '旅行提醒', - 'notif.trip_reminder.text': '你的旅行 {trip} 即將開始!', - 'notif.todo_due.title': '待辦事項即將到期', - 'notif.todo_due.text': '{trip} 中的 {todo} 將於 {due} 到期', - 'notif.vacay_invite.title': 'Vacay Fusion 邀請', - 'notif.vacay_invite.text': '{actor} 邀請你合併假期計畫', - 'notif.photos_shared.title': '照片已分享', - 'notif.photos_shared.text': '{actor} 在 {trip} 中分享了 {count} 張照片', - 'notif.collab_message.title': '新訊息', - 'notif.collab_message.text': '{actor} 在 {trip} 中傳送了一則訊息', - 'notif.packing_tagged.title': '打包指派', - 'notif.packing_tagged.text': '{actor} 在 {trip} 中將 {category} 指派給你', - 'notif.version_available.title': '有新版本可用', - 'notif.version_available.text': 'TREK {version} 現已推出', - 'notif.action.view_trip': '查看旅行', - 'notif.action.view_collab': '查看訊息', - 'notif.action.view_packing': '查看打包', - 'notif.action.view_photos': '查看照片', - 'notif.action.view_vacay': '查看 Vacay', - 'notif.action.view_admin': '前往管理', - - // Notifications — dev test events - - // Notifications - 'notif.action.view': '查看', - 'notif.action.accept': '接受', - 'notif.action.decline': '拒絕', - 'notif.generic.title': '通知', - 'notif.generic.text': '你有一則新通知', - 'notif.dev.unknown_event.title': '[DEV] 未知事件', - 'notif.dev.unknown_event.text': '事件類型「{event}」未在 EVENT_NOTIFICATION_CONFIG 中註冊', - - // OAuth scope groups - 'oauth.scope.group.trips': '行程', - 'oauth.scope.group.places': '地點', - 'oauth.scope.group.atlas': 'Atlas', - 'oauth.scope.group.packing': '行李', - 'oauth.scope.group.todos': '待辦事項', - 'oauth.scope.group.budget': '預算', - 'oauth.scope.group.reservations': '預訂', - 'oauth.scope.group.collab': '協作', - 'oauth.scope.group.notifications': '通知', - 'oauth.scope.group.vacay': '假期', - 'oauth.scope.group.geo': 'Geo', - 'oauth.scope.group.weather': '天氣', - 'oauth.scope.group.journey': '旅程', - - // OAuth scope labels & descriptions - 'oauth.scope.trips:read.label': '檢視行程與旅遊計畫', - 'oauth.scope.trips:read.description': '讀取行程、天數、每日筆記及成員', - 'oauth.scope.trips:write.label': '編輯行程與旅遊計畫', - 'oauth.scope.trips:write.description': '建立及更新行程、天數、筆記並管理成員', - 'oauth.scope.trips:delete.label': '刪除行程', - 'oauth.scope.trips:delete.description': '永久刪除整個行程——此操作無法復原', - 'oauth.scope.trips:share.label': '管理分享連結', - 'oauth.scope.trips:share.description': '建立、更新及撤銷行程的公開分享連結', - 'oauth.scope.places:read.label': '檢視地點與地圖資料', - 'oauth.scope.places:read.description': '讀取地點、每日指派、標籤及類別', - 'oauth.scope.places:write.label': '管理地點', - 'oauth.scope.places:write.description': '建立、更新及刪除地點、指派及標籤', - 'oauth.scope.atlas:read.label': '檢視 Atlas', - 'oauth.scope.atlas:read.description': '讀取已造訪的國家、地區及願望清單', - 'oauth.scope.atlas:write.label': '管理 Atlas', - 'oauth.scope.atlas:write.description': '標記已造訪的國家及地區,管理願望清單', - 'oauth.scope.packing:read.label': '檢視行李清單', - 'oauth.scope.packing:read.description': '讀取行李物品、行李袋及類別負責人', - 'oauth.scope.packing:write.label': '管理行李清單', - 'oauth.scope.packing:write.description': '新增、更新、刪除、勾選及重新排序行李物品和行李袋', - 'oauth.scope.todos:read.label': '檢視待辦清單', - 'oauth.scope.todos:read.description': '讀取行程待辦事項及類別負責人', - 'oauth.scope.todos:write.label': '管理待辦清單', - 'oauth.scope.todos:write.description': '建立、更新、勾選、刪除及重新排序待辦事項', - 'oauth.scope.budget:read.label': '檢視預算', - 'oauth.scope.budget:read.description': '讀取預算項目及費用明細', - 'oauth.scope.budget:write.label': '管理預算', - 'oauth.scope.budget:write.description': '建立、更新及刪除預算項目', - 'oauth.scope.reservations:read.label': '檢視預訂', - 'oauth.scope.reservations:read.description': '讀取預訂及住宿詳情', - 'oauth.scope.reservations:write.label': '管理預訂', - 'oauth.scope.reservations:write.description': '建立、更新、刪除及重新排序預訂', - 'oauth.scope.collab:read.label': '檢視協作', - 'oauth.scope.collab:read.description': '讀取協作筆記、投票及訊息', - 'oauth.scope.collab:write.label': '管理協作', - 'oauth.scope.collab:write.description': '建立、更新及刪除協作筆記、投票及訊息', - 'oauth.scope.notifications:read.label': '檢視通知', - 'oauth.scope.notifications:read.description': '讀取應用程式通知及未讀數量', - 'oauth.scope.notifications:write.label': '管理通知', - 'oauth.scope.notifications:write.description': '將通知標為已讀並回覆', - 'oauth.scope.vacay:read.label': '檢視假期計畫', - 'oauth.scope.vacay:read.description': '讀取假期計畫資料、項目及統計', - 'oauth.scope.vacay:write.label': '管理假期計畫', - 'oauth.scope.vacay:write.description': '建立及管理假期項目、節假日及團隊計畫', - 'oauth.scope.geo:read.label': '地圖與地理編碼', - 'oauth.scope.geo:read.description': '搜尋地點、解析地圖 URL 及反向地理編碼坐標', - 'oauth.scope.weather:read.label': '天氣預報', - 'oauth.scope.weather:read.description': '取得行程地點及日期的天氣預報', - 'oauth.scope.journey:read.label': '檢視旅程', - 'oauth.scope.journey:read.description': '讀取旅程、條目及貢獻者清單', - 'oauth.scope.journey:write.label': '管理旅程', - 'oauth.scope.journey:write.description': '建立、更新及刪除旅程及其條目', - 'oauth.scope.journey:share.label': '管理旅程連結', - 'oauth.scope.journey:share.description': '建立、更新及撤銷旅程的公開分享連結', - - // System notices - 'system_notice.welcome_v1.title': '歡迎使用 TREK', - 'system_notice.welcome_v1.body': '您的全方位旅遊規劃器。建立行程、與朋友分享旅遊,隨時保持條理分明——無論線上或離線皆可。', - 'system_notice.welcome_v1.cta_label': '規劃行程', - 'system_notice.welcome_v1.hero_alt': '風景優美的旅遊目的地與 TREK 介面', - 'system_notice.welcome_v1.highlight_plan': '逐日行程規劃', - 'system_notice.welcome_v1.highlight_share': '與旅伴協作規劃', - 'system_notice.welcome_v1.highlight_offline': '行動裝置支援離線使用', - 'system_notice.dev_test_modal.title': '[Dev] Test notice', - 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', - 'system_notice.pager.prev': '上一則通知', - 'system_notice.pager.next': '下一則通知', - 'system_notice.pager.counter': '{current} / {total}', - 'system_notice.pager.goto': '前往通知 {n}', - 'system_notice.pager.position': '通知 {current}/{total}', - // System notices — 3.0.0 upgrade - 'system_notice.v3_photos.title': '3.0 版相片已移至', - 'system_notice.v3_photos.body': '行程規劃器中的​**相片**標籤已被移除。您的相片安全— TREK 從未修改您的 Immich 或 Synology 相簿。\n\n相片現在位於 **Journey** 附加元件中。Journey 為選用 — 若尚未啟用,請聯絡管理員於 Admin → 附加元件 中開啟。', - 'system_notice.v3_journey.title': '認識 Journey — 旅行日記', - 'system_notice.v3_journey.body': '將您的旅程記錄為具有時間軸、相片畫庫與互動地圖的豐富旅行故事。', - 'system_notice.v3_journey.cta_label': '開啟 Journey', - 'system_notice.v3_journey.highlight_timeline': '每日時間軸與畫庫', - 'system_notice.v3_journey.highlight_photos': '從 Immich 或 Synology 匯入', - 'system_notice.v3_journey.highlight_share': '公開分享 — 無需登入', - 'system_notice.v3_journey.highlight_export': '匯出為 PDF 相簿书', - 'system_notice.v3_features.title': '3.0 版更多亮點', - 'system_notice.v3_features.body': '這個版本還有一些其他專項值得了解。', - 'system_notice.v3_features.highlight_dashboard': '行動先行儀表板重設計', - 'system_notice.v3_features.highlight_offline': '作為 PWA 的完整離線模式', - 'system_notice.v3_features.highlight_search': '地點搜尋即時自動補全', - 'system_notice.v3_features.highlight_import': '從 KMZ/KML 檔案匯入地點', - - // System notices — MCP OAuth 2.1 upgrade - 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1 升級', - 'system_notice.v3_mcp.body': 'MCP 整合已全面重構。OAuth 2.1 現為建議的身份驗證方式。靜態令牌(trek_…)已棄用,將於未來版本移除。', - 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 建議(mcp-remote)', - 'system_notice.v3_mcp.highlight_scopes': '24 個細粒度權限範圍', - 'system_notice.v3_mcp.highlight_deprecated': '靜態 trek_ 令牌已棄用', - 'system_notice.v3_mcp.highlight_tools': '擴展工具集與提示詞', - - // System notices — personal thank you - 'system_notice.v3_thankyou.title': '來自我的一封私人信', - 'system_notice.v3_thankyou.body': '在你繼續之前——我想停下來說幾句。\n\nTREK 最初只是我為自己的旅行而做的一個業餘專案。我從未想過它會成長為 4,000 人信賴的冒險規劃工具。每一顆星標、每一個 issue、每一個功能請求——我都會讀,它們在全職工作和大學學業之間的深夜裡支撐著我繼續前行。\n\n我想讓你們知道:TREK 將永遠開源,永遠可自託管,永遠屬於你們。沒有追蹤,沒有訂閱,沒有任何附加條件。只是一個熱愛旅行的人為同樣熱愛旅行的你們打造的工具。\n\n特別感謝 [jubnl](https://github.com/jubnl)——你已經成為一位不可思議的合作者。3.0 版本中許多精彩之處都留下了你的印記。感謝你在這個專案還很粗糙的時候就選擇了相信它。\n\n也感謝你們每一位——回報了 bug、翻譯了文字、向朋友分享了 TREK,或者只是用它規劃了一次旅行——**謝謝你們**。你們是這一切存在的原因。\n\n願我們一起踏上更多的冒險旅程。\n\n— Maurice\n\n---\n\n[加入 Discord 社群](https://discord.gg/7Q6M6jDwzf)\n\n如果 TREK 讓你的旅行更美好,一杯[小小的咖啡](https://ko-fi.com/mauriceboe)能讓這盞燈一直亮著。', - // System notices — 3.0.14 - 'system_notice.v3014_whitespace_collision.title': '需要操作:使用者帳戶衝突', - 'system_notice.v3014_whitespace_collision.body': '3.0.14 版本升級偵測到一個或多個由儲存帳戶中前後空白字元引發的使用者名稱或電子郵件衝突。受影響的帳戶已自動重新命名。請檢查伺服器日誌中以 **[migration] WHITESPACE COLLISION** 開頭的行,以確認哪些帳戶需要審查。', - 'transport.addTransport': '新增交通', - 'transport.modalTitle.create': '新增交通', - 'transport.modalTitle.edit': '編輯交通', - 'transport.title': '交通', - 'transport.addManual': '手動新增交通', -} - -export default zhTw \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f0895856..deddae2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5971,6 +5971,275 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", @@ -16782,6 +17051,19 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -17140,6 +17422,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/tz-lookup": { "version": "6.1.25", "resolved": "https://registry.npmjs.org/tz-lookup/-/tz-lookup-6.1.25.tgz", @@ -19485,6 +19791,7 @@ "prettier-plugin-organize-imports": "^4.3.0", "tsup": "^8.5.1", "typescript": "^6.0.2", + "typescript-eslint": "^8.58.2", "vitest": "^3.2.4" } } diff --git a/scripts/migrate-i18n.mts b/scripts/migrate-i18n.mts new file mode 100644 index 00000000..fc26ae95 --- /dev/null +++ b/scripts/migrate-i18n.mts @@ -0,0 +1,117 @@ +#!/usr/bin/env node +/** + * Extracts client locale files into per-namespace files under shared/src/i18n/{locale}/. + * Run with: npx tsx scripts/migrate-i18n.mts + * + * Safe to re-run — locale dirs are cleaned first. Hand-authored files + * (types.ts, languages.ts, index.ts) in shared/src/i18n/ are never touched. + */ +import { mkdir, rm, writeFile } from 'fs/promises' +import { dirname, join } from 'path' +import { fileURLToPath, pathToFileURL } from 'url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const ROOT = join(__dirname, '..') +const TRANSLATIONS_DIR = join(ROOT, 'client/src/i18n/translations') +const I18N_OUT = join(ROOT, 'shared/src/i18n') + +// Maps locale code → source filename (without .ts) in client/src/i18n/translations/ +const LOCALE_FILE_MAP: Record = { + de: 'de', en: 'en', es: 'es', fr: 'fr', hu: 'hu', + it: 'it', tr: 'tr', ru: 'ru', zh: 'zh', 'zh-TW': 'zhTw', + nl: 'nl', id: 'id', ar: 'ar', br: 'br', cs: 'cs', + pl: 'pl', ja: 'ja', ko: 'ko', uk: 'uk', +} + +type TranslationValue = string | { name: string; category: string }[] +type LocaleStrings = Record + +async function loadLocale(code: string): Promise { + const filename = LOCALE_FILE_MAP[code] + if (!filename) throw new Error(`Unknown locale code: ${code}`) + const file = join(TRANSLATIONS_DIR, `${filename}.ts`) + const mod = await import(pathToFileURL(file).href) + return mod.default as LocaleStrings +} + +function serializeValue(value: TranslationValue, innerIndent: string): string { + if (Array.isArray(value)) { + // Pretty-print the array then re-indent each line after the first + const lines = JSON.stringify(value, null, 2).split('\n') + return lines.map((l, i) => (i === 0 ? l : innerIndent + l)).join('\n') + } + return JSON.stringify(value) +} + +async function writeLocaleDir(code: string, strings: LocaleStrings): Promise { + const outDir = join(I18N_OUT, code) + await mkdir(outDir, { recursive: true }) + + // Group keys by top-level namespace prefix (everything before the first dot) + const namespaces = new Map>() + for (const [key, value] of Object.entries(strings)) { + const ns = key.split('.')[0] ?? key + if (!namespaces.has(ns)) namespaces.set(ns, []) + namespaces.get(ns)!.push([key, value]) + } + + // Write one file per namespace + for (const [ns, entries] of namespaces) { + const lines: string[] = [ + `import type { TranslationStrings } from '../types'`, + ``, + `const ${ns}: TranslationStrings = {`, + ...entries.map(([k, v]) => ` ${JSON.stringify(k)}: ${serializeValue(v, ' ')},`), + `}`, + `export default ${ns}`, + ] + await writeFile(join(outDir, `${ns}.ts`), lines.join('\n') + '\n') + } + + // Write index.ts that merges all namespace files into a single locale object + const nsNames = [...namespaces.keys()] + const indexLines: string[] = [ + ...nsNames.map(ns => `import ${ns} from './${ns}'`), + ``, + `const locale = {`, + ...nsNames.map(ns => ` ...${ns},`), + `}`, + `export default locale`, + ] + await writeFile(join(outDir, 'index.ts'), indexLines.join('\n') + '\n') +} + +async function main(): Promise { + console.log('Loading English base...') + const en = await loadLocale('en') + const codes = Object.keys(LOCALE_FILE_MAP) + + // Clean existing locale dirs; leave hand-authored files (types.ts, languages.ts, index.ts) alone + await Promise.all(codes.map(code => rm(join(I18N_OUT, code), { recursive: true, force: true }))) + + for (const code of codes) { + process.stdout.write(`Processing ${code}...`) + let strings = await loadLocale(code) + + if (code === 'ar') { + // ar.ts spreads en — keep only keys that ar actually translates (value differs from en) + const pruned: LocaleStrings = {} + for (const [key, val] of Object.entries(strings)) { + if (JSON.stringify(val) !== JSON.stringify(en[key])) { + pruned[key] = val + } + } + strings = pruned + console.log(` ${Object.keys(strings).length} own keys (pruned from ${Object.keys(en).length} en total)`) + } else { + const nsCount = new Set(Object.keys(strings).map(k => k.split('.')[0])).size + console.log(` ${Object.keys(strings).length} keys, ${nsCount} namespaces`) + } + + await writeLocaleDir(code, strings) + } + + console.log('\nDone! Run: cd shared && npm run build') +} + +main().catch(err => { console.error(err); process.exit(1) }) diff --git a/server/src/config.ts b/server/src/config.ts index c9255cc9..59f7079e 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -1,6 +1,7 @@ import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; +import { SUPPORTED_LANGUAGE_CODES as SUPPORTED_LANG_CODES } from '@trek/shared'; const dataDir = path.resolve(__dirname, '../data'); @@ -101,10 +102,6 @@ export const ENCRYPTION_KEY = _encryptionKey; // DEFAULT_LANGUAGE sets the language shown on the login page before the user // selects one. Only applies when the user has no saved language preference. -// Supported values: de, en, es, fr, hu, nl, br, cs, pl, ru, zh, zh-TW, it, ar -// Must stay in sync with client/src/i18n/supportedLanguages.ts (canonical source). -// Kept duplicated here because server and client are separate npm packages. -const SUPPORTED_LANG_CODES = ['de', 'en', 'es', 'fr', 'hu', 'nl', 'br', 'cs', 'pl', 'ru', 'zh', 'zh-TW', 'it', 'ar']; const rawDefaultLang = process.env.DEFAULT_LANGUAGE?.toLowerCase() || 'en'; if (!SUPPORTED_LANG_CODES.includes(rawDefaultLang)) { console.warn(`DEFAULT_LANGUAGE="${rawDefaultLang}" is not supported. Falling back to "en". Supported: ${SUPPORTED_LANG_CODES.join(', ')}`); diff --git a/server/src/services/notifications.ts b/server/src/services/notifications.ts index 94b6b34c..a59e53be 100644 --- a/server/src/services/notifications.ts +++ b/server/src/services/notifications.ts @@ -7,6 +7,13 @@ import { checkSsrf, createPinnedDispatcher } from '../utils/ssrfGuard'; // ── Types ────────────────────────────────────────────────────────────────── import type { NotifEventType } from './notificationPreferencesService'; +import { EMAIL_I18N as I18N, EVENT_TEXTS, PASSWORD_RESET_I18N } from '@trek/shared/i18n/externalNotifications'; +import type { EmailStrings, EventText, PasswordResetStrings, NotificationEventKey } from '@trek/shared/i18n/externalNotifications'; + +// Compile-time guard: shared NotificationEventKey and server NotifEventType must stay in sync. +type _EvtFwd = NotifEventType extends NotificationEventKey ? true : never +type _EvtBwd = NotificationEventKey extends NotifEventType ? true : never +const _eventKeyDriftGuard: [_EvtFwd, _EvtBwd] = [true, true] interface SmtpConfig { host: string; @@ -103,208 +110,9 @@ export function getAdminWebhookUrl(): string | null { return value ? decrypt_api_key(value) : null; } -// ── Email i18n strings ───────────────────────────────────────────────────── +// ── Email i18n strings — imported from @trek/shared/i18n/externalNotifications ── -interface EmailStrings { footer: string; manage: string; madeWith: string; openTrek: string } - -const I18N: Record = { - en: { footer: 'You received this because you have notifications enabled in TREK.', manage: 'Manage preferences in Settings', madeWith: 'Made with', openTrek: 'Open TREK' }, - de: { footer: 'Du erhältst diese E-Mail, weil du Benachrichtigungen in TREK aktiviert hast.', manage: 'Einstellungen verwalten', madeWith: 'Made with', openTrek: 'TREK öffnen' }, - fr: { footer: 'Vous recevez cet e-mail car les notifications sont activées dans TREK.', manage: 'Gérer les préférences', madeWith: 'Made with', openTrek: 'Ouvrir TREK' }, - es: { footer: 'Recibiste esto porque tienes las notificaciones activadas en TREK.', manage: 'Gestionar preferencias', madeWith: 'Made with', openTrek: 'Abrir TREK' }, - nl: { footer: 'Je ontvangt dit omdat je meldingen hebt ingeschakeld in TREK.', manage: 'Voorkeuren beheren', madeWith: 'Made with', openTrek: 'TREK openen' }, - ru: { footer: 'Вы получили это, потому что у вас включены уведомления в TREK.', manage: 'Управление настройками', madeWith: 'Made with', openTrek: 'Открыть TREK' }, - zh: { footer: '您收到此邮件是因为您在 TREK 中启用了通知。', manage: '管理偏好设置', madeWith: 'Made with', openTrek: '打开 TREK' }, - 'zh-TW': { footer: '您收到這封郵件是因為您在 TREK 中啟用了通知。', manage: '管理偏好設定', madeWith: 'Made with', openTrek: '開啟 TREK' }, - ar: { footer: 'تلقيت هذا لأنك قمت بتفعيل الإشعارات في TREK.', manage: 'إدارة التفضيلات', madeWith: 'Made with', openTrek: 'فتح TREK' }, - id: { footer: 'Anda menerima ini karena Anda telah mengaktifkan notifikasi di TREK.', manage: 'Kelola preferensi di Pengaturan', madeWith: 'Dibuat dengan', openTrek: 'Buka TREK' }, -}; - -// Translated notification texts per event type -interface EventText { title: string; body: string } -type EventTextFn = (params: Record) => EventText - -const EVENT_TEXTS: Record> = { - en: { - trip_invite: p => ({ title: `Trip invite: "${p.trip}"`, body: `${p.actor} invited ${p.invitee || 'a member'} to the trip "${p.trip}".` }), - booking_change: p => ({ title: `New booking: ${p.booking}`, body: `${p.actor} added a new ${p.type} "${p.booking}" to "${p.trip}".` }), - trip_reminder: p => ({ title: `Trip reminder: ${p.trip}`, body: `Your trip "${p.trip}" is coming up soon!` }), - todo_due: p => ({ title: `To-do due: ${p.todo}`, body: `"${p.todo}" in "${p.trip}" is due on ${p.due}.` }), - vacay_invite: p => ({ title: 'Vacay Fusion Invite', body: `${p.actor} invited you to fuse vacation plans. Open TREK to accept or decline.` }), - photos_shared: p => ({ title: `${p.count} photos shared`, body: `${p.actor} shared ${p.count} photo(s) in "${p.trip}".` }), - collab_message: p => ({ title: `New message in "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Packing: ${p.category}`, body: `${p.actor} assigned you to the "${p.category}" packing category in "${p.trip}".` }), - version_available: p => ({ title: 'New TREK version available', body: `TREK ${p.version} is now available. Visit the admin panel to update.` }), - synology_session_cleared: () => ({ title: 'Synology session cleared', body: 'Your Synology account or URL changed. You have been logged out of Synology Photos.' }), - }, - de: { - trip_invite: p => ({ title: `Einladung zu "${p.trip}"`, body: `${p.actor} hat ${p.invitee || 'ein Mitglied'} zur Reise "${p.trip}" eingeladen.` }), - booking_change: p => ({ title: `Neue Buchung: ${p.booking}`, body: `${p.actor} hat eine neue Buchung "${p.booking}" (${p.type}) zu "${p.trip}" hinzugefügt.` }), - trip_reminder: p => ({ title: `Reiseerinnerung: ${p.trip}`, body: `Deine Reise "${p.trip}" steht bald an!` }), - todo_due: p => ({ title: `Aufgabe fällig: ${p.todo}`, body: `"${p.todo}" in "${p.trip}" ist am ${p.due} fällig.` }), - vacay_invite: p => ({ title: 'Vacay Fusion-Einladung', body: `${p.actor} hat dich eingeladen, Urlaubspläne zu fusionieren. Öffne TREK um anzunehmen oder abzulehnen.` }), - photos_shared: p => ({ title: `${p.count} Fotos geteilt`, body: `${p.actor} hat ${p.count} Foto(s) in "${p.trip}" geteilt.` }), - collab_message: p => ({ title: `Neue Nachricht in "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Packliste: ${p.category}`, body: `${p.actor} hat dich der Kategorie "${p.category}" in der Packliste von "${p.trip}" zugewiesen.` }), - version_available: p => ({ title: 'Neue TREK-Version verfügbar', body: `TREK ${p.version} ist jetzt verfügbar. Besuche das Admin-Panel zum Aktualisieren.` }), - synology_session_cleared: () => ({ title: 'Synology-Sitzung beendet', body: 'Dein Synology-Konto oder die URL hat sich geändert. Du wurdest von Synology Photos abgemeldet.' }), - }, - fr: { - trip_invite: p => ({ title: `Invitation à "${p.trip}"`, body: `${p.actor} a invité ${p.invitee || 'un membre'} au voyage "${p.trip}".` }), - booking_change: p => ({ title: `Nouvelle réservation : ${p.booking}`, body: `${p.actor} a ajouté une réservation "${p.booking}" (${p.type}) à "${p.trip}".` }), - trip_reminder: p => ({ title: `Rappel de voyage : ${p.trip}`, body: `Votre voyage "${p.trip}" approche !` }), - todo_due: p => ({ title: `Tâche à échéance : ${p.todo}`, body: `"${p.todo}" dans "${p.trip}" est due le ${p.due}.` }), - vacay_invite: p => ({ title: 'Invitation Vacay Fusion', body: `${p.actor} vous invite à fusionner les plans de vacances. Ouvrez TREK pour accepter ou refuser.` }), - photos_shared: p => ({ title: `${p.count} photos partagées`, body: `${p.actor} a partagé ${p.count} photo(s) dans "${p.trip}".` }), - collab_message: p => ({ title: `Nouveau message dans "${p.trip}"`, body: `${p.actor} : ${p.preview}` }), - packing_tagged: p => ({ title: `Bagages : ${p.category}`, body: `${p.actor} vous a assigné à la catégorie "${p.category}" dans "${p.trip}".` }), - version_available: p => ({ title: 'Nouvelle version TREK disponible', body: `TREK ${p.version} est maintenant disponible. Rendez-vous dans le panneau d'administration pour mettre à jour.` }), - synology_session_cleared: () => ({ title: 'Session Synology effacée', body: 'Votre compte ou URL Synology a changé. Vous avez été déconnecté de Synology Photos.' }), - }, - es: { - trip_invite: p => ({ title: `Invitación a "${p.trip}"`, body: `${p.actor} invitó a ${p.invitee || 'un miembro'} al viaje "${p.trip}".` }), - booking_change: p => ({ title: `Nueva reserva: ${p.booking}`, body: `${p.actor} añadió una reserva "${p.booking}" (${p.type}) a "${p.trip}".` }), - trip_reminder: p => ({ title: `Recordatorio: ${p.trip}`, body: `¡Tu viaje "${p.trip}" se acerca!` }), - todo_due: p => ({ title: `Tarea pendiente: ${p.todo}`, body: `"${p.todo}" en "${p.trip}" vence el ${p.due}.` }), - vacay_invite: p => ({ title: 'Invitación Vacay Fusion', body: `${p.actor} te invitó a fusionar planes de vacaciones. Abre TREK para aceptar o rechazar.` }), - photos_shared: p => ({ title: `${p.count} fotos compartidas`, body: `${p.actor} compartió ${p.count} foto(s) en "${p.trip}".` }), - collab_message: p => ({ title: `Nuevo mensaje en "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Equipaje: ${p.category}`, body: `${p.actor} te asignó a la categoría "${p.category}" en "${p.trip}".` }), - version_available: p => ({ title: 'Nueva versión de TREK disponible', body: `TREK ${p.version} ya está disponible. Visita el panel de administración para actualizar.` }), - synology_session_cleared: () => ({ title: 'Sesión de Synology cerrada', body: 'Tu cuenta o URL de Synology ha cambiado. Has cerrado sesión en Synology Photos.' }), - }, - nl: { - trip_invite: p => ({ title: `Uitnodiging voor "${p.trip}"`, body: `${p.actor} heeft ${p.invitee || 'een lid'} uitgenodigd voor de reis "${p.trip}".` }), - booking_change: p => ({ title: `Nieuwe boeking: ${p.booking}`, body: `${p.actor} heeft een boeking "${p.booking}" (${p.type}) toegevoegd aan "${p.trip}".` }), - trip_reminder: p => ({ title: `Reisherinnering: ${p.trip}`, body: `Je reis "${p.trip}" komt eraan!` }), - todo_due: p => ({ title: `Taak verloopt: ${p.todo}`, body: `"${p.todo}" in "${p.trip}" verloopt op ${p.due}.` }), - vacay_invite: p => ({ title: 'Vacay Fusion uitnodiging', body: `${p.actor} nodigt je uit om vakantieplannen te fuseren. Open TREK om te accepteren of af te wijzen.` }), - photos_shared: p => ({ title: `${p.count} foto's gedeeld`, body: `${p.actor} heeft ${p.count} foto('s) gedeeld in "${p.trip}".` }), - collab_message: p => ({ title: `Nieuw bericht in "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Paklijst: ${p.category}`, body: `${p.actor} heeft je toegewezen aan de categorie "${p.category}" in "${p.trip}".` }), - version_available: p => ({ title: 'Nieuwe TREK-versie beschikbaar', body: `TREK ${p.version} is nu beschikbaar. Bezoek het beheerderspaneel om bij te werken.` }), - synology_session_cleared: () => ({ title: 'Synology-sessie gewist', body: 'Je Synology-account of URL is gewijzigd. Je bent uitgelogd bij Synology Photos.' }), - }, - ru: { - trip_invite: p => ({ title: `Приглашение в "${p.trip}"`, body: `${p.actor} пригласил ${p.invitee || 'участника'} в поездку "${p.trip}".` }), - booking_change: p => ({ title: `Новое бронирование: ${p.booking}`, body: `${p.actor} добавил бронирование "${p.booking}" (${p.type}) в "${p.trip}".` }), - trip_reminder: p => ({ title: `Напоминание: ${p.trip}`, body: `Ваша поездка "${p.trip}" скоро начнётся!` }), - todo_due: p => ({ title: `Задача к сроку: ${p.todo}`, body: `"${p.todo}" в поездке "${p.trip}" — срок ${p.due}.` }), - vacay_invite: p => ({ title: 'Приглашение Vacay Fusion', body: `${p.actor} приглашает вас объединить планы отпуска. Откройте TREK для подтверждения.` }), - photos_shared: p => ({ title: `${p.count} фото`, body: `${p.actor} поделился ${p.count} фото в "${p.trip}".` }), - collab_message: p => ({ title: `Новое сообщение в "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Список вещей: ${p.category}`, body: `${p.actor} назначил вас в категорию "${p.category}" в "${p.trip}".` }), - version_available: p => ({ title: 'Доступна новая версия TREK', body: `TREK ${p.version} теперь доступен. Перейдите в панель администратора для обновления.` }), - synology_session_cleared: () => ({ title: 'Сессия Synology сброшена', body: 'Ваш аккаунт или URL Synology изменился. Вы вышли из Synology Photos.' }), - }, - zh: { - trip_invite: p => ({ title: `邀请加入"${p.trip}"`, body: `${p.actor} 邀请了 ${p.invitee || '成员'} 加入旅行"${p.trip}"。` }), - booking_change: p => ({ title: `新预订:${p.booking}`, body: `${p.actor} 在"${p.trip}"中添加了预订"${p.booking}"(${p.type})。` }), - trip_reminder: p => ({ title: `旅行提醒:${p.trip}`, body: `你的旅行"${p.trip}"即将开始!` }), - todo_due: p => ({ title: `待办事项即将到期:${p.todo}`, body: `"${p.trip}" 中的"${p.todo}"将于 ${p.due} 到期。` }), - vacay_invite: p => ({ title: 'Vacay 融合邀请', body: `${p.actor} 邀请你合并假期计划。打开 TREK 接受或拒绝。` }), - photos_shared: p => ({ title: `${p.count} 张照片已分享`, body: `${p.actor} 在"${p.trip}"中分享了 ${p.count} 张照片。` }), - collab_message: p => ({ title: `"${p.trip}"中的新消息`, body: `${p.actor}:${p.preview}` }), - packing_tagged: p => ({ title: `行李清单:${p.category}`, body: `${p.actor} 将你分配到"${p.trip}"中的"${p.category}"类别。` }), - version_available: p => ({ title: '新版 TREK 可用', body: `TREK ${p.version} 现已可用。请前往管理面板进行更新。` }), - synology_session_cleared: () => ({ title: 'Synology 会话已清除', body: '您的 Synology 账户或 URL 已更改,您已退出 Synology Photos。' }), - }, - 'zh-TW': { - trip_invite: p => ({ title: `邀請加入「${p.trip}」`, body: `${p.actor} 邀請了 ${p.invitee || '成員'} 加入行程「${p.trip}」。` }), - booking_change: p => ({ title: `新預訂:${p.booking}`, body: `${p.actor} 在「${p.trip}」中新增了預訂「${p.booking}」(${p.type})。` }), - trip_reminder: p => ({ title: `行程提醒:${p.trip}`, body: `您的行程「${p.trip}」即將開始!` }), - todo_due: p => ({ title: `待辦事項即將到期:${p.todo}`, body: `「${p.trip}」中的「${p.todo}」將於 ${p.due} 到期。` }), - vacay_invite: p => ({ title: 'Vacay 融合邀請', body: `${p.actor} 邀請您合併假期計畫。開啟 TREK 以接受或拒絕。` }), - photos_shared: p => ({ title: `已分享 ${p.count} 張照片`, body: `${p.actor} 在「${p.trip}」中分享了 ${p.count} 張照片。` }), - collab_message: p => ({ title: `「${p.trip}」中的新訊息`, body: `${p.actor}:${p.preview}` }), - packing_tagged: p => ({ title: `打包清單:${p.category}`, body: `${p.actor} 已將您指派到「${p.trip}」中的「${p.category}」分類。` }), - version_available: p => ({ title: '新版 TREK 可用', body: `TREK ${p.version} 現已可用。請前往管理面板進行更新。` }), - synology_session_cleared: () => ({ title: 'Synology 工作階段已清除', body: '您的 Synology 帳戶或 URL 已變更,您已登出 Synology Photos。' }), - }, - ar: { - trip_invite: p => ({ title: `دعوة إلى "${p.trip}"`, body: `${p.actor} دعا ${p.invitee || 'عضو'} إلى الرحلة "${p.trip}".` }), - booking_change: p => ({ title: `حجز جديد: ${p.booking}`, body: `${p.actor} أضاف حجز "${p.booking}" (${p.type}) إلى "${p.trip}".` }), - trip_reminder: p => ({ title: `تذكير: ${p.trip}`, body: `رحلتك "${p.trip}" تقترب!` }), - todo_due: p => ({ title: `مهمة مستحقة: ${p.todo}`, body: `"${p.todo}" في "${p.trip}" مستحقة في ${p.due}.` }), - vacay_invite: p => ({ title: 'دعوة دمج الإجازة', body: `${p.actor} يدعوك لدمج خطط الإجازة. افتح TREK للقبول أو الرفض.` }), - photos_shared: p => ({ title: `${p.count} صور مشتركة`, body: `${p.actor} شارك ${p.count} صورة في "${p.trip}".` }), - collab_message: p => ({ title: `رسالة جديدة في "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `قائمة التعبئة: ${p.category}`, body: `${p.actor} عيّنك في فئة "${p.category}" في "${p.trip}".` }), - version_available: p => ({ title: 'إصدار TREK جديد متاح', body: `TREK ${p.version} متاح الآن. تفضل بزيارة لوحة الإدارة للتحديث.` }), - synology_session_cleared: () => ({ title: 'تمت إعادة تعيين جلسة Synology', body: 'تغيّر حسابك أو رابط Synology. تم تسجيل خروجك من Synology Photos.' }), - }, - br: { - trip_invite: p => ({ title: `Convite para "${p.trip}"`, body: `${p.actor} convidou ${p.invitee || 'um membro'} para a viagem "${p.trip}".` }), - booking_change: p => ({ title: `Nova reserva: ${p.booking}`, body: `${p.actor} adicionou uma reserva "${p.booking}" (${p.type}) em "${p.trip}".` }), - trip_reminder: p => ({ title: `Lembrete: ${p.trip}`, body: `Sua viagem "${p.trip}" está chegando!` }), - todo_due: p => ({ title: `Tarefa com vencimento: ${p.todo}`, body: `"${p.todo}" em "${p.trip}" vence em ${p.due}.` }), - vacay_invite: p => ({ title: 'Convite Vacay Fusion', body: `${p.actor} convidou você para fundir planos de férias. Abra o TREK para aceitar ou recusar.` }), - photos_shared: p => ({ title: `${p.count} fotos compartilhadas`, body: `${p.actor} compartilhou ${p.count} foto(s) em "${p.trip}".` }), - collab_message: p => ({ title: `Nova mensagem em "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Bagagem: ${p.category}`, body: `${p.actor} atribuiu você à categoria "${p.category}" em "${p.trip}".` }), - version_available: p => ({ title: 'Nova versão do TREK disponível', body: `O TREK ${p.version} está disponível. Acesse o painel de administração para atualizar.` }), - synology_session_cleared: () => ({ title: 'Sessão Synology encerrada', body: 'Sua conta ou URL do Synology foi alterada. Você foi desconectado do Synology Photos.' }), - }, - cs: { - trip_invite: p => ({ title: `Pozvánka do "${p.trip}"`, body: `${p.actor} pozval ${p.invitee || 'člena'} na výlet "${p.trip}".` }), - booking_change: p => ({ title: `Nová rezervace: ${p.booking}`, body: `${p.actor} přidal rezervaci "${p.booking}" (${p.type}) k "${p.trip}".` }), - trip_reminder: p => ({ title: `Připomínka výletu: ${p.trip}`, body: `Váš výlet "${p.trip}" se blíží!` }), - todo_due: p => ({ title: `Úkol se blíží: ${p.todo}`, body: `"${p.todo}" ve výletě "${p.trip}" má termín ${p.due}.` }), - vacay_invite: p => ({ title: 'Pozvánka Vacay Fusion', body: `${p.actor} vás pozval ke spojení dovolenkových plánů. Otevřete TREK pro přijetí nebo odmítnutí.` }), - photos_shared: p => ({ title: `${p.count} sdílených fotek`, body: `${p.actor} sdílel ${p.count} foto v "${p.trip}".` }), - collab_message: p => ({ title: `Nová zpráva v "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Balení: ${p.category}`, body: `${p.actor} vás přiřadil do kategorie "${p.category}" v "${p.trip}".` }), - version_available: p => ({ title: 'Nová verze TREK dostupná', body: `TREK ${p.version} je nyní dostupný. Navštivte administrátorský panel pro aktualizaci.` }), - synology_session_cleared: () => ({ title: 'Relace Synology byla zrušena', body: 'Váš účet nebo URL Synology se změnil. Byli jste odhlášeni ze Synology Photos.' }), - }, - hu: { - trip_invite: p => ({ title: `Meghívó a(z) "${p.trip}" utazásra`, body: `${p.actor} meghívta ${p.invitee || 'egy tagot'} a(z) "${p.trip}" utazásra.` }), - booking_change: p => ({ title: `Új foglalás: ${p.booking}`, body: `${p.actor} hozzáadott egy "${p.booking}" (${p.type}) foglalást a(z) "${p.trip}" utazáshoz.` }), - trip_reminder: p => ({ title: `Utazás emlékeztető: ${p.trip}`, body: `A(z) "${p.trip}" utazás hamarosan kezdődik!` }), - todo_due: p => ({ title: `Teendő esedékes: ${p.todo}`, body: `"${p.todo}" (${p.trip}) határideje: ${p.due}.` }), - vacay_invite: p => ({ title: 'Vacay Fusion meghívó', body: `${p.actor} meghívott a nyaralási tervek összevonásához. Nyissa meg a TREK-et az elfogadáshoz vagy elutasításhoz.` }), - photos_shared: p => ({ title: `${p.count} fotó megosztva`, body: `${p.actor} ${p.count} fotót osztott meg a(z) "${p.trip}" utazásban.` }), - collab_message: p => ({ title: `Új üzenet a(z) "${p.trip}" utazásban`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Csomagolás: ${p.category}`, body: `${p.actor} hozzárendelte Önt a "${p.category}" csomagolási kategóriához a(z) "${p.trip}" utazásban.` }), - version_available: p => ({ title: 'Új TREK verzió érhető el', body: `A TREK ${p.version} elérhető. Látogasson el az adminisztrációs panelre a frissítéshez.` }), - synology_session_cleared: () => ({ title: 'Synology munkamenet törölve', body: 'A Synology fiókja vagy URL-je megváltozott. Kijelentkeztek a Synology Photos-ból.' }), - }, - it: { - trip_invite: p => ({ title: `Invito a "${p.trip}"`, body: `${p.actor} ha invitato ${p.invitee || 'un membro'} al viaggio "${p.trip}".` }), - booking_change: p => ({ title: `Nuova prenotazione: ${p.booking}`, body: `${p.actor} ha aggiunto una prenotazione "${p.booking}" (${p.type}) a "${p.trip}".` }), - trip_reminder: p => ({ title: `Promemoria viaggio: ${p.trip}`, body: `Il tuo viaggio "${p.trip}" si avvicina!` }), - todo_due: p => ({ title: `Attività in scadenza: ${p.todo}`, body: `"${p.todo}" in "${p.trip}" scade il ${p.due}.` }), - vacay_invite: p => ({ title: 'Invito Vacay Fusion', body: `${p.actor} ti ha invitato a fondere i piani vacanza. Apri TREK per accettare o rifiutare.` }), - photos_shared: p => ({ title: `${p.count} foto condivise`, body: `${p.actor} ha condiviso ${p.count} foto in "${p.trip}".` }), - collab_message: p => ({ title: `Nuovo messaggio in "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Bagagli: ${p.category}`, body: `${p.actor} ti ha assegnato alla categoria "${p.category}" in "${p.trip}".` }), - version_available: p => ({ title: 'Nuova versione TREK disponibile', body: `TREK ${p.version} è ora disponibile. Visita il pannello di amministrazione per aggiornare.` }), - synology_session_cleared: () => ({ title: 'Sessione Synology rimossa', body: 'Il tuo account o URL Synology è cambiato. Sei stato disconnesso da Synology Photos.' }), - }, - pl: { - trip_invite: p => ({ title: `Zaproszenie do "${p.trip}"`, body: `${p.actor} zaprosił ${p.invitee || 'członka'} do podróży "${p.trip}".` }), - booking_change: p => ({ title: `Nowa rezerwacja: ${p.booking}`, body: `${p.actor} dodał rezerwację "${p.booking}" (${p.type}) do "${p.trip}".` }), - trip_reminder: p => ({ title: `Przypomnienie o podróży: ${p.trip}`, body: `Twoja podróż "${p.trip}" zbliża się!` }), - todo_due: p => ({ title: `Zadanie z terminem: ${p.todo}`, body: `"${p.todo}" w "${p.trip}" — termin ${p.due}.` }), - vacay_invite: p => ({ title: 'Zaproszenie Vacay Fusion', body: `${p.actor} zaprosił Cię do połączenia planów urlopowych. Otwórz TREK, aby zaakceptować lub odrzucić.` }), - photos_shared: p => ({ title: `${p.count} zdjęć udostępnionych`, body: `${p.actor} udostępnił ${p.count} zdjęcie/zdjęcia w "${p.trip}".` }), - collab_message: p => ({ title: `Nowa wiadomość w "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Pakowanie: ${p.category}`, body: `${p.actor} przypisał Cię do kategorii "${p.category}" w "${p.trip}".` }), - version_available: p => ({ title: 'Nowa wersja TREK dostępna', body: `TREK ${p.version} jest teraz dostępny. Odwiedź panel administracyjny, aby zaktualizować.` }), - synology_session_cleared: () => ({ title: 'Sesja Synology wyczyszczona', body: 'Twoje konto lub URL Synology uległo zmianie. Zostałeś wylogowany z Synology Photos.' }), - }, - id: { - trip_invite: p => ({ title: `Undangan perjalanan: "${p.trip}"`, body: `${p.actor} mengundang ${p.invitee || 'seorang anggota'} ke perjalanan "${p.trip}".` }), - booking_change: p => ({ title: `Pemesanan baru: ${p.booking}`, body: `${p.actor} menambahkan "${p.booking}" (${p.type}) baru ke "${p.trip}".` }), - trip_reminder: p => ({ title: `Pengingat perjalanan: ${p.trip}`, body: `Perjalanan Anda "${p.trip}" akan segera tiba!` }), - todo_due: p => ({ title: `Tugas jatuh tempo: ${p.todo}`, body: `"${p.todo}" di "${p.trip}" jatuh tempo pada ${p.due}.` }), - vacay_invite: p => ({ title: 'Undangan Penggabungan Vacay', body: `${p.actor} mengundang Anda untuk menggabungkan rencana liburan. Buka TREK untuk menerima atau menolak.` }), - photos_shared: p => ({ title: `${p.count} foto dibagikan`, body: `${p.actor} membagikan ${p.count} foto di "${p.trip}".` }), - collab_message: p => ({ title: `Pesan baru di "${p.trip}"`, body: `${p.actor}: ${p.preview}` }), - packing_tagged: p => ({ title: `Pengepakan: ${p.category}`, body: `${p.actor} menugaskan Anda ke kategori "${p.category}" di "${p.trip}".` }), - version_available: p => ({ title: 'Versi TREK baru tersedia', body: `TREK ${p.version} sekarang tersedia. Kunjungi panel admin untuk memperbarui.` }), - }, -}; +// EVENT_TEXTS imported from @trek/shared/i18n/externalNotifications // Get localized event text export function getEventText(lang: string, event: NotifEventType, params: Record): EventText { @@ -362,24 +170,7 @@ export function buildEmailHtml(subject: string, body: string, lang: string, navi // ── Password reset email ─────────────────────────────────────────────────── -interface PasswordResetStrings { subject: string; greeting: string; body: string; ctaIntro: string; expiry: string; ignore: string } - -const PASSWORD_RESET_I18N: Record = { - en: { subject: 'Reset your password', greeting: 'Hi', body: 'We received a request to reset the password for your TREK account. Click the button below to set a new password.', ctaIntro: 'Reset password', expiry: 'This link expires in 60 minutes.', ignore: "If you didn't request this, you can safely ignore this email — your password won't change." }, - de: { subject: 'Passwort zurücksetzen', greeting: 'Hallo', body: 'Wir haben eine Anfrage erhalten, das Passwort für dein TREK-Konto zurückzusetzen. Klicke auf den Button unten, um ein neues Passwort festzulegen.', ctaIntro: 'Passwort zurücksetzen', expiry: 'Dieser Link ist 60 Minuten gültig.', ignore: 'Wenn du das nicht warst, ignoriere diese E-Mail — dein Passwort bleibt unverändert.' }, - fr: { subject: 'Réinitialisez votre mot de passe', greeting: 'Bonjour', body: 'Nous avons reçu une demande de réinitialisation du mot de passe de votre compte TREK. Cliquez sur le bouton ci-dessous pour définir un nouveau mot de passe.', ctaIntro: 'Réinitialiser le mot de passe', expiry: 'Ce lien expire dans 60 minutes.', ignore: "Si vous n'êtes pas à l'origine de cette demande, ignorez cet e-mail — votre mot de passe ne changera pas." }, - es: { subject: 'Restablecer tu contraseña', greeting: 'Hola', body: 'Recibimos una solicitud para restablecer la contraseña de tu cuenta de TREK. Haz clic en el botón de abajo para establecer una nueva contraseña.', ctaIntro: 'Restablecer contraseña', expiry: 'Este enlace caduca en 60 minutos.', ignore: 'Si no solicitaste esto, puedes ignorar este correo — tu contraseña no cambiará.' }, - it: { subject: 'Reimposta la tua password', greeting: 'Ciao', body: 'Abbiamo ricevuto una richiesta di reimpostazione della password per il tuo account TREK. Clicca il pulsante qui sotto per impostare una nuova password.', ctaIntro: 'Reimposta password', expiry: 'Questo link scade tra 60 minuti.', ignore: 'Se non hai richiesto questa operazione, ignora questa email — la tua password non cambierà.' }, - nl: { subject: 'Reset je wachtwoord', greeting: 'Hallo', body: 'We hebben een verzoek ontvangen om het wachtwoord voor je TREK-account te resetten. Klik op de knop hieronder om een nieuw wachtwoord in te stellen.', ctaIntro: 'Wachtwoord resetten', expiry: 'Deze link verloopt over 60 minuten.', ignore: 'Als jij dit niet hebt aangevraagd, kun je deze e-mail negeren — je wachtwoord blijft ongewijzigd.' }, - ru: { subject: 'Сброс пароля', greeting: 'Здравствуйте', body: 'Мы получили запрос на сброс пароля вашего аккаунта TREK. Нажмите кнопку ниже, чтобы установить новый пароль.', ctaIntro: 'Сбросить пароль', expiry: 'Ссылка действительна 60 минут.', ignore: 'Если вы не запрашивали сброс — просто проигнорируйте это письмо, пароль останется прежним.' }, - zh: { subject: '重置您的密码', greeting: '您好', body: '我们收到了重置您的 TREK 账户密码的请求。点击下方按钮设置新密码。', ctaIntro: '重置密码', expiry: '此链接将在 60 分钟后失效。', ignore: '如果这不是您本人的请求,可以忽略本邮件 — 您的密码不会改变。' }, - 'zh-TW': { subject: '重設您的密碼', greeting: '您好', body: '我們收到了重設您 TREK 帳號密碼的請求。點擊下方按鈕以設定新密碼。', ctaIntro: '重設密碼', expiry: '此連結將於 60 分鐘後失效。', ignore: '若非您本人發起的請求,請忽略此郵件 — 您的密碼不會變更。' }, - hu: { subject: 'Jelszó visszaállítása', greeting: 'Szia', body: 'Kérést kaptunk a TREK-fiókod jelszavának visszaállítására. Kattints az alábbi gombra az új jelszó beállításához.', ctaIntro: 'Jelszó visszaállítása', expiry: 'Ez a link 60 perc után lejár.', ignore: 'Ha nem te kérted ezt, nyugodtan hagyd figyelmen kívül ezt az e-mailt — a jelszavad változatlan marad.' }, - ar: { subject: 'إعادة تعيين كلمة المرور', greeting: 'مرحبا', body: 'تلقينا طلبًا لإعادة تعيين كلمة المرور لحسابك في TREK. انقر على الزر أدناه لتعيين كلمة مرور جديدة.', ctaIntro: 'إعادة تعيين كلمة المرور', expiry: 'تنتهي صلاحية هذا الرابط خلال 60 دقيقة.', ignore: 'إذا لم تطلب هذا، يمكنك تجاهل هذه الرسالة — لن تتغير كلمة المرور الخاصة بك.' }, - br: { subject: 'Redefinir sua senha', greeting: 'Olá', body: 'Recebemos um pedido para redefinir a senha da sua conta TREK. Clique no botão abaixo para definir uma nova senha.', ctaIntro: 'Redefinir senha', expiry: 'Este link expira em 60 minutos.', ignore: 'Se você não solicitou isto, pode ignorar este e-mail — sua senha não será alterada.' }, - cs: { subject: 'Obnovení hesla', greeting: 'Ahoj', body: 'Obdrželi jsme žádost o obnovení hesla k tvému účtu TREK. Klikni na tlačítko níže a nastav nové heslo.', ctaIntro: 'Obnovit heslo', expiry: 'Odkaz vyprší za 60 minut.', ignore: 'Pokud jsi o obnovení nežádal/a, tento e-mail ignoruj — heslo zůstane beze změny.' }, - pl: { subject: 'Zresetuj hasło', greeting: 'Cześć', body: 'Otrzymaliśmy prośbę o zresetowanie hasła do Twojego konta TREK. Kliknij przycisk poniżej, aby ustawić nowe hasło.', ctaIntro: 'Zresetuj hasło', expiry: 'Link wygaśnie za 60 minut.', ignore: 'Jeśli to nie Ty, zignoruj tę wiadomość — Twoje hasło pozostanie bez zmian.' }, -}; +// PASSWORD_RESET_I18N imported from @trek/shared/i18n/externalNotifications function buildPasswordResetHtml(subject: string, strings: PasswordResetStrings, recipient: string, resetUrl: string, lang: string): string { const safeGreeting = escapeHtml(`${strings.greeting}, ${recipient}`); diff --git a/shared/eslint.config.mjs b/shared/eslint.config.mjs new file mode 100644 index 00000000..079e0b09 --- /dev/null +++ b/shared/eslint.config.mjs @@ -0,0 +1,29 @@ +import js from '@eslint/js'; + +import gitignore from 'eslint-config-flat-gitignore'; +import eslintConfigPrettier from 'eslint-config-prettier'; +import eslintPluginPrettier from 'eslint-plugin-prettier'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + gitignore({ strict: false }), + js.configs.recommended, + ...tseslint.configs.recommended, + eslintConfigPrettier, + { + plugins: { + prettier: eslintPluginPrettier, + }, + rules: { + 'prettier/prettier': 'error', + '@typescript-eslint/no-unused-vars': [ + 'error', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-explicit-any': 'error', + }, + }, + { + ignores: ['node_modules'], + }, +); diff --git a/shared/package.json b/shared/package.json index 053096d3..05bb224b 100644 --- a/shared/package.json +++ b/shared/package.json @@ -12,6 +12,26 @@ "types": "./dist/index.d.ts", "import": "./dist/index.js", "require": "./dist/index.cjs" + }, + "./i18n": { + "types": "./dist/i18n/index.d.ts", + "import": "./dist/i18n/index.js", + "require": "./dist/i18n/index.cjs" + }, + "./i18n/*": { + "types": "./dist/i18n/*/index.d.ts", + "import": "./dist/i18n/*/index.js", + "require": "./dist/i18n/*/index.cjs" + } + }, + "typesVersions": { + "*": { + "i18n": [ + "./dist/i18n/index.d.ts" + ], + "i18n/*": [ + "./dist/i18n/*/index.d.ts" + ] } }, "scripts": { @@ -38,6 +58,7 @@ "eslint-config-prettier": "^10.1.8", "eslint-plugin-prettier": "^5.5.5", "prettier": "3.8.3", - "prettier-plugin-organize-imports": "^4.3.0" + "prettier-plugin-organize-imports": "^4.3.0", + "typescript-eslint": "^8.58.2" } } diff --git a/shared/src/common/primitives.schema.spec.ts b/shared/src/common/primitives.schema.spec.ts index fb819ddf..c3557aaf 100644 --- a/shared/src/common/primitives.schema.spec.ts +++ b/shared/src/common/primitives.schema.spec.ts @@ -1,6 +1,12 @@ -import { describe, it, expect } from 'vitest'; -import { idSchema, idParamSchema, nonEmptyString, isoDateTime } from './primitives.schema'; import { paginationQuerySchema } from './pagination.schema'; +import { + idSchema, + idParamSchema, + nonEmptyString, + isoDateTime, +} from './primitives.schema'; + +import { describe, it, expect } from 'vitest'; describe('@trek/shared primitives', () => { it('idSchema accepts positive integers, rejects others', () => { @@ -29,11 +35,16 @@ describe('@trek/shared primitives', () => { describe('@trek/shared pagination', () => { it('applies defaults and coerces', () => { expect(paginationQuerySchema.parse({})).toEqual({ page: 1, perPage: 50 }); - expect(paginationQuerySchema.parse({ page: '2', perPage: '10' })).toEqual({ page: 2, perPage: 10 }); + expect(paginationQuerySchema.parse({ page: '2', perPage: '10' })).toEqual({ + page: 2, + perPage: 10, + }); }); it('enforces bounds', () => { expect(paginationQuerySchema.safeParse({ perPage: 0 }).success).toBe(false); - expect(paginationQuerySchema.safeParse({ perPage: 999 }).success).toBe(false); + expect(paginationQuerySchema.safeParse({ perPage: 999 }).success).toBe( + false, + ); }); }); diff --git a/shared/src/i18n/ar/admin.ts b/shared/src/i18n/ar/admin.ts new file mode 100644 index 00000000..7fd39133 --- /dev/null +++ b/shared/src/i18n/ar/admin.ts @@ -0,0 +1,319 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'الإشعارات', + 'admin.notifications.hint': + 'اختر قناة إشعارات واحدة. يمكن تفعيل واحدة فقط في كل مرة.', + 'admin.notifications.none': 'معطّل', + 'admin.notifications.email': 'البريد الإلكتروني (SMTP)', + 'admin.ntfy.hint': + 'تسمح للمستخدمين بإعداد موضوعات ntfy الخاصة لتلقي إشعارات الدفع. قم بتعيين الخادم الافتراضي أدناه لملء إعدادات المستخدم مسبقًا.', + 'admin.notifications.save': 'حفظ إعدادات الإشعارات', + 'admin.notifications.saved': 'تم حفظ إعدادات الإشعارات', + 'admin.notifications.testWebhook': 'إرسال webhook تجريبي', + 'admin.notifications.testWebhookSuccess': 'تم إرسال webhook التجريبي بنجاح', + 'admin.notifications.testWebhookFailed': 'فشل إرسال webhook التجريبي', + 'admin.notifications.testNtfy': 'إرسال Ntfy تجريبي', + 'admin.notifications.testNtfySuccess': 'تم إرسال Ntfy التجريبي بنجاح', + 'admin.notifications.testNtfyFailed': 'فشل إرسال Ntfy التجريبي', + 'admin.notifications.inappPanel.hint': + 'الإشعارات داخل التطبيق نشطة دائمًا ولا يمكن تعطيلها بشكل عام.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook المسؤول', + 'admin.notifications.adminWebhookPanel.hint': + 'يُستخدم هذا الـ Webhook حصريًا لإشعارات المسؤول (مثل تنبيهات الإصدارات). وهو مستقل عن Webhooks المستخدمين ويُرسل تلقائيًا عند تعيين رابط URL.', + 'admin.notifications.adminWebhookPanel.saved': 'تم حفظ رابط Webhook المسؤول', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'تم إرسال Webhook الاختباري بنجاح', + 'admin.notifications.adminWebhookPanel.testFailed': + 'فشل إرسال Webhook الاختباري', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'يُرسل Webhook المسؤول تلقائيًا عند تعيين رابط URL', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy المسؤول', + 'admin.notifications.adminNtfyPanel.hint': + 'يُستخدم موضوع Ntfy هذا حصريًا لإشعارات المسؤول (مثل تنبيهات الإصدارات). وهو مستقل عن مواضيع المستخدمين ويُرسل دائمًا عند تهيئته.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'عنوان URL خادم Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'يُستخدم أيضًا كخادم افتراضي لإشعارات ntfy للمستخدمين. اتركه فارغًا لاستخدام ntfy.sh. يمكن للمستخدمين تغييره في إعداداتهم الخاصة.', + 'admin.notifications.adminNtfyPanel.topicLabel': 'موضوع المسؤول', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'رمز الوصول (اختياري)', + 'admin.notifications.adminNtfyPanel.tokenCleared': 'تم مسح رمز وصول المسؤول', + 'admin.notifications.adminNtfyPanel.saved': 'تم حفظ إعدادات Ntfy للمسؤول', + 'admin.notifications.adminNtfyPanel.test': 'إرسال Ntfy تجريبي', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'تم إرسال Ntfy التجريبي بنجاح', + 'admin.notifications.adminNtfyPanel.testFailed': 'فشل إرسال Ntfy التجريبي', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'يُرسل Ntfy للمسؤول دائمًا عند تهيئة موضوع', + 'admin.notifications.adminNotificationsHint': + 'حدد القنوات التي تُسلّم إشعارات المسؤول (مثل تنبيهات الإصدارات). يُرسل الـ Webhook تلقائيًا عند تعيين رابط URL لـ Webhook المسؤول.', + 'admin.notifications.tripReminders.title': 'تذكيرات الرحلات', + 'admin.notifications.tripReminders.hint': + 'إرسال تذكير قبل بدء الرحلة (يتطلب تعيين أيام التذكير على الرحلة).', + 'admin.notifications.tripReminders.enabled': 'تم تفعيل تذكيرات الرحلات', + 'admin.notifications.tripReminders.disabled': 'تم تعطيل تذكيرات الرحلات', + 'admin.smtp.title': 'البريد والإشعارات', + 'admin.smtp.hint': 'تكوين SMTP لإرسال إشعارات البريد الإلكتروني.', + 'admin.smtp.testButton': 'إرسال بريد تجريبي', + 'admin.webhook.hint': + 'إرسال الإشعارات إلى webhook خارجي (Discord، Slack، إلخ).', + 'admin.smtp.testSuccess': 'تم إرسال البريد التجريبي بنجاح', + 'admin.smtp.testFailed': 'فشل إرسال البريد التجريبي', + 'admin.title': 'الإدارة', + 'admin.subtitle': 'إدارة المستخدمين وإعدادات النظام', + 'admin.tabs.users': 'المستخدمون', + 'admin.tabs.categories': 'الفئات', + 'admin.tabs.backup': 'النسخ الاحتياطي', + 'admin.tabs.notifications': 'الإشعارات', + 'admin.tabs.audit': 'تدقيق', + 'admin.stats.users': 'المستخدمون', + 'admin.stats.trips': 'الرحلات', + 'admin.stats.places': 'الأماكن', + 'admin.stats.photos': 'الصور', + 'admin.stats.files': 'الملفات', + 'admin.table.user': 'المستخدم', + 'admin.table.email': 'البريد الإلكتروني', + 'admin.table.role': 'الدور', + 'admin.table.created': 'تم الإنشاء', + 'admin.table.lastLogin': 'آخر تسجيل دخول', + 'admin.table.actions': 'الإجراءات', + 'admin.you': '(أنت)', + 'admin.editUser': 'تعديل المستخدم', + 'admin.newPassword': 'كلمة مرور جديدة', + 'admin.newPasswordHint': 'اتركه فارغًا للاحتفاظ بالحالية', + 'admin.deleteUser': 'حذف المستخدم "{name}"؟ سيتم حذف جميع الرحلات نهائيًا.', + 'admin.deleteUserTitle': 'حذف المستخدم', + 'admin.newPasswordPlaceholder': 'أدخل كلمة مرور جديدة…', + 'admin.toast.loadError': 'فشل تحميل بيانات الإدارة', + 'admin.toast.userUpdated': 'تم تحديث المستخدم', + 'admin.toast.updateError': 'فشل التحديث', + 'admin.toast.userDeleted': 'تم حذف المستخدم', + 'admin.toast.deleteError': 'فشل الحذف', + 'admin.toast.cannotDeleteSelf': 'لا يمكنك حذف حسابك الخاص', + 'admin.toast.userCreated': 'تم إنشاء المستخدم', + 'admin.toast.createError': 'فشل إنشاء المستخدم', + 'admin.toast.fieldsRequired': + 'اسم المستخدم والبريد الإلكتروني وكلمة المرور مطلوبة', + 'admin.createUser': 'إنشاء مستخدم', + 'admin.invite.title': 'روابط الدعوة', + 'admin.invite.subtitle': 'إنشاء روابط تسجيل للاستخدام المحدود', + 'admin.invite.create': 'إنشاء رابط', + 'admin.invite.createAndCopy': 'إنشاء ونسخ', + 'admin.invite.empty': 'لم يتم إنشاء روابط دعوة بعد', + 'admin.invite.maxUses': 'الحد الأقصى للاستخدام', + 'admin.invite.expiry': 'تنتهي بعد', + 'admin.invite.uses': 'مستخدم', + 'admin.invite.expiresAt': 'تنتهي في', + 'admin.invite.createdBy': 'بواسطة', + 'admin.invite.active': 'نشط', + 'admin.invite.expired': 'منتهي', + 'admin.invite.usedUp': 'مستنفد', + 'admin.invite.copied': 'تم نسخ رابط الدعوة', + 'admin.invite.copyLink': 'نسخ الرابط', + 'admin.invite.deleted': 'تم حذف رابط الدعوة', + 'admin.invite.createError': 'فشل إنشاء رابط الدعوة', + 'admin.invite.deleteError': 'فشل حذف رابط الدعوة', + 'admin.tabs.settings': 'الإعدادات', + 'admin.allowRegistration': 'السماح بالتسجيل', + 'admin.allowRegistrationHint': 'يمكن للمستخدمين الجدد التسجيل بأنفسهم', + 'admin.requireMfa': 'فرض المصادقة الثنائية (2FA)', + 'admin.requireMfaHint': + 'يجب على المستخدمين الذين لا يملكون 2FA إكمال الإعداد في الإعدادات قبل استخدام التطبيق.', + 'admin.apiKeys': 'مفاتيح API', + 'admin.apiKeysHint': + 'اختياري. يُفعّل بيانات الأماكن الموسعة مثل الصور والطقس.', + 'admin.mapsKey': 'مفتاح Google Maps API', + 'admin.mapsKeyHint': + 'مطلوب للبحث عن الأماكن. احصل عليه من console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'بدون مفتاح API، يُستخدم OpenStreetMap للبحث. مع مفتاح Google يمكن تحميل الصور والتقييمات وساعات العمل أيضًا. احصل عليه من console.cloud.google.com.', + 'admin.recommended': 'مُوصى به', + 'admin.weatherKey': 'مفتاح OpenWeatherMap API', + 'admin.weatherKeyHint': 'لبيانات الطقس. مجاني من openweathermap.org', + 'admin.validateKey': 'اختبار', + 'admin.keyValid': 'متصل', + 'admin.keyInvalid': 'غير صالح', + 'admin.keySaved': 'تم حفظ مفاتيح API', + 'admin.oidcTitle': 'تسجيل الدخول الموحد (OIDC)', + 'admin.oidcSubtitle': + 'السماح بتسجيل الدخول عبر مزودين خارجيين مثل Google أو Apple أو Authentik أو Keycloak.', + 'admin.oidcDisplayName': 'الاسم المعروض', + 'admin.oidcIssuer': 'عنوان URL للمُصدر', + 'admin.oidcIssuerHint': + 'عنوان OpenID Connect Issuer URL للمزود. مثال: https://accounts.google.com', + 'admin.oidcSaved': 'تم حفظ إعدادات OIDC', + 'admin.oidcOnlyMode': 'تعطيل المصادقة بكلمة المرور', + 'admin.oidcOnlyModeHint': + 'عند التفعيل، يُسمح فقط بتسجيل الدخول عبر SSO. سيتم حظر تسجيل الدخول والتسجيل بكلمة المرور.', + 'admin.fileTypes': 'أنواع الملفات المسموح بها', + 'admin.fileTypesHint': 'حدد أنواع الملفات التي يمكن للمستخدمين رفعها.', + 'admin.fileTypesFormat': + 'امتدادات مفصولة بفواصل (مثل jpg,png,pdf,doc). استخدم * للسماح بجميع الأنواع.', + 'admin.fileTypesSaved': 'تم حفظ إعدادات أنواع الملفات', + 'admin.placesPhotos.title': 'صور الأماكن', + 'admin.placesPhotos.subtitle': + 'جلب الصور من Google Places API. عطّلها للحفاظ على حصة API. صور Wikimedia غير متأثرة.', + 'admin.placesAutocomplete.title': 'الإكمال التلقائي للأماكن', + 'admin.placesAutocomplete.subtitle': + 'استخدام Google Places API لاقتراحات البحث. عطّلها للحفاظ على حصة API.', + 'admin.placesDetails.title': 'تفاصيل الأماكن', + 'admin.placesDetails.subtitle': + 'جلب معلومات تفصيلية عن الأماكن (الساعات، التقييم، الموقع) من Google Places API. عطّلها للحفاظ على حصة API.', + 'admin.bagTracking.title': 'تتبع الأمتعة', + 'admin.bagTracking.subtitle': 'تفعيل الوزن وتعيين الأمتعة للعناصر', + 'admin.collab.chat.title': 'الدردشة', + 'admin.collab.chat.subtitle': 'المراسلة في الوقت الفعلي للتعاون', + 'admin.collab.notes.title': 'الملاحظات', + 'admin.collab.notes.subtitle': 'ملاحظات ومستندات مشتركة', + 'admin.collab.polls.title': 'الاستطلاعات', + 'admin.collab.polls.subtitle': 'استطلاعات وتصويت جماعي', + 'admin.collab.whatsnext.title': 'ما التالي', + 'admin.collab.whatsnext.subtitle': 'اقتراحات الأنشطة والخطوات التالية', + 'admin.tabs.config': 'التخصيص', + 'admin.tabs.defaults': 'الإعدادات الافتراضية', + 'admin.defaultSettings.title': 'إعدادات المستخدم الافتراضية', + 'admin.defaultSettings.description': + 'تعيين الإعدادات الافتراضية على مستوى النظام. سيرى المستخدمون الذين لم يغيروا إعدادًا هذه القيم. تحظى تغييراتهم دائمًا بالأولوية.', + 'admin.defaultSettings.saved': 'تم حفظ الإعداد الافتراضي', + 'admin.defaultSettings.reset': 'إعادة التعيين إلى الإعداد الافتراضي المدمج', + 'admin.defaultSettings.resetToBuiltIn': 'إعادة تعيين', + 'admin.tabs.templates': 'قوالب التعبئة', + 'admin.packingTemplates.title': 'قوالب التعبئة', + 'admin.packingTemplates.subtitle': 'إنشاء قوائم تعبئة قابلة لإعادة الاستخدام', + 'admin.packingTemplates.create': 'قالب جديد', + 'admin.packingTemplates.namePlaceholder': 'اسم القالب (مثال: عطلة شاطئية)', + 'admin.packingTemplates.empty': 'لم يتم إنشاء قوالب بعد', + 'admin.packingTemplates.items': 'عناصر', + 'admin.packingTemplates.categories': 'فئات', + 'admin.packingTemplates.itemName': 'اسم العنصر', + 'admin.packingTemplates.itemCategory': 'الفئة', + 'admin.packingTemplates.categoryName': 'اسم الفئة (مثال: ملابس)', + 'admin.packingTemplates.addCategory': 'إضافة فئة', + 'admin.packingTemplates.created': 'تم إنشاء القالب', + 'admin.packingTemplates.deleted': 'تم حذف القالب', + 'admin.packingTemplates.loadError': 'فشل تحميل القوالب', + 'admin.packingTemplates.createError': 'فشل إنشاء القالب', + 'admin.packingTemplates.deleteError': 'فشل حذف القالب', + 'admin.packingTemplates.saveError': 'فشل الحفظ', + 'admin.tabs.addons': 'الإضافات', + 'admin.addons.title': 'الإضافات', + 'admin.addons.subtitle': 'فعّل أو عطّل الميزات لتخصيص تجربة TREK.', + 'admin.addons.catalog.packing.name': 'القوائم', + 'admin.addons.catalog.packing.description': 'قوائم التعبئة والمهام لرحلاتك', + 'admin.addons.catalog.budget.name': 'الميزانية', + 'admin.addons.catalog.budget.description': 'تتبع النفقات وخطط ميزانية الرحلة', + 'admin.addons.catalog.documents.name': 'المستندات', + 'admin.addons.catalog.documents.description': 'حفظ وإدارة وثائق السفر', + 'admin.addons.catalog.vacay.name': 'الإجازة', + 'admin.addons.catalog.vacay.description': 'مخطط إجازات شخصي مع عرض تقويم', + 'admin.addons.catalog.atlas.name': 'الأطلس', + 'admin.addons.catalog.atlas.description': + 'خريطة العالم مع الدول التي تمت زيارتها وإحصائيات السفر', + 'admin.addons.catalog.collab.name': 'التعاون', + 'admin.addons.catalog.collab.description': + 'ملاحظات واستطلاعات ودردشة لحظية لتخطيط الرحلة', + 'admin.addons.catalog.memories.name': 'صور (Immich)', + 'admin.addons.catalog.memories.description': 'شارك صور رحلتك عبر Immich', + 'admin.addons.catalog.mcp.description': + 'بروتوكول سياق النموذج لتكامل مساعد الذكاء الاصطناعي', + 'admin.addons.subtitleBefore': 'فعّل أو عطّل الميزات لتخصيص تجربة ', + 'admin.addons.subtitleAfter': '.', + 'admin.addons.enabled': 'مفعّل', + 'admin.addons.disabled': 'معطّل', + 'admin.addons.type.trip': 'رحلة', + 'admin.addons.type.global': 'عام', + 'admin.addons.type.integration': 'تكامل', + 'admin.addons.tripHint': 'متاح كعلامة تبويب داخل كل رحلة', + 'admin.addons.globalHint': 'متاح كقسم مستقل في التنقل الرئيسي', + 'admin.addons.integrationHint': + 'خدمات الواجهة الخلفية وتكاملات API بدون صفحة مخصصة', + 'admin.addons.toast.updated': 'تم تحديث الإضافة', + 'admin.addons.toast.error': 'فشل تحديث الإضافة', + 'admin.addons.noAddons': 'لا توجد إضافات متاحة', + 'admin.weather.title': 'بيانات الطقس', + 'admin.weather.badge': 'منذ 24 مارس 2026', + 'admin.weather.description': + 'يستخدم TREK خدمة Open-Meteo كمصدر لبيانات الطقس. وهي خدمة مجانية ومفتوحة المصدر ولا تتطلب مفتاح API.', + 'admin.weather.forecast': 'توقعات 16 يومًا', + 'admin.weather.forecastDesc': 'سابقًا 5 أيام (OpenWeatherMap)', + 'admin.weather.climate': 'بيانات المناخ التاريخية', + 'admin.weather.climateDesc': + 'متوسطات آخر 85 سنة للأيام بعد توقعات الـ 16 يومًا', + 'admin.weather.requests': '10,000 طلب / يوم', + 'admin.weather.requestsDesc': 'مجاني، بدون مفتاح API', + 'admin.weather.locationHint': + 'يعتمد الطقس على أول مكان بإحداثيات في كل يوم. إذا لم يكن هناك مكان مخصص ليوم ما، يُستخدم أي مكان من قائمة الأماكن كمرجع.', + 'admin.tabs.mcpTokens': 'وصول MCP', + 'admin.mcpTokens.title': 'وصول MCP', + 'admin.mcpTokens.subtitle': 'إدارة جلسات OAuth ورموز API لجميع المستخدمين', + 'admin.mcpTokens.sectionTitle': 'رموز API', + 'admin.mcpTokens.owner': 'المالك', + 'admin.mcpTokens.tokenName': 'اسم الرمز', + 'admin.mcpTokens.created': 'تاريخ الإنشاء', + 'admin.mcpTokens.lastUsed': 'آخر استخدام', + 'admin.mcpTokens.never': 'أبداً', + 'admin.mcpTokens.empty': 'لم يتم إنشاء أي رموز MCP بعد', + 'admin.mcpTokens.deleteTitle': 'حذف الرمز', + 'admin.mcpTokens.deleteMessage': + 'سيتم إلغاء هذا الرمز فوراً. سيفقد المستخدم وصوله إلى MCP عبر هذا الرمز.', + 'admin.mcpTokens.deleteSuccess': 'تم حذف الرمز', + 'admin.mcpTokens.deleteError': 'فشل حذف الرمز', + 'admin.mcpTokens.loadError': 'فشل تحميل الرموز', + 'admin.oauthSessions.sectionTitle': 'جلسات OAuth', + 'admin.oauthSessions.clientName': 'العميل', + 'admin.oauthSessions.owner': 'المالك', + 'admin.oauthSessions.scopes': 'الصلاحيات', + 'admin.oauthSessions.created': 'تاريخ الإنشاء', + 'admin.oauthSessions.empty': 'لا توجد جلسات OAuth نشطة', + 'admin.oauthSessions.revokeTitle': 'إلغاء الجلسة', + 'admin.oauthSessions.revokeMessage': + 'سيتم إلغاء جلسة OAuth هذه فوراً. سيفقد العميل وصوله إلى MCP.', + 'admin.oauthSessions.revokeSuccess': 'تم إلغاء الجلسة', + 'admin.oauthSessions.revokeError': 'فشل إلغاء الجلسة', + 'admin.oauthSessions.loadError': 'فشل تحميل جلسات OAuth', + 'admin.audit.subtitle': + 'أحداث الأمان والإدارة (النسخ الاحتياطية، المستخدمون، المصادقة الثنائية، الإعدادات).', + 'admin.audit.empty': 'لا توجد سجلات تدقيق بعد.', + 'admin.audit.refresh': 'تحديث', + 'admin.audit.loadMore': 'تحميل المزيد', + 'admin.audit.showing': 'تم تحميل {count} · الإجمالي {total}', + 'admin.audit.col.time': 'الوقت', + 'admin.audit.col.user': 'المستخدم', + 'admin.audit.col.action': 'الإجراء', + 'admin.audit.col.resource': 'المورد', + 'admin.audit.col.ip': 'عنوان IP', + 'admin.audit.col.details': 'التفاصيل', + 'admin.github.title': 'سجل الإصدارات', + 'admin.github.subtitle': 'آخر التحديثات من {repo}', + 'admin.github.latest': 'الأحدث', + 'admin.github.prerelease': 'إصدار تجريبي', + 'admin.github.showDetails': 'إظهار التفاصيل', + 'admin.github.hideDetails': 'إخفاء التفاصيل', + 'admin.github.loadMore': 'تحميل المزيد', + 'admin.github.loading': 'جارٍ التحميل...', + 'admin.github.error': 'فشل تحميل الإصدارات', + 'admin.github.by': 'بواسطة', + 'admin.github.support': 'يساعدني في تطوير TREK', + 'admin.update.available': 'يتوفر تحديث', + 'admin.update.text': 'TREK {version} متوفر. أنت تستخدم {current}.', + 'admin.update.button': 'عرض على GitHub', + 'admin.update.install': 'تثبيت التحديث', + 'admin.update.confirmTitle': 'تثبيت التحديث؟', + 'admin.update.confirmText': + 'سيتم تحديث TREK من {current} إلى {version}. سيُعاد تشغيل الخادم تلقائيًا بعد ذلك.', + 'admin.update.dataInfo': + 'جميع بياناتك (الرحلات، المستخدمون، مفاتيح API، المرفوعات، الإجازة، الأطلس، الميزانيات) ستبقى محفوظة.', + 'admin.update.warning': + 'سيكون التطبيق غير متاح لفترة وجيزة أثناء إعادة التشغيل.', + 'admin.update.confirm': 'حدّث الآن', + 'admin.update.installing': 'جارٍ التحديث…', + 'admin.update.success': 'تم تثبيت التحديث. ستتم إعادة تشغيل الخادم…', + 'admin.update.failed': 'فشل التحديث', + 'admin.update.backupHint': 'نوصي بإنشاء نسخة احتياطية قبل التحديث.', + 'admin.update.backupLink': 'الذهاب إلى النسخ الاحتياطي', + 'admin.update.howTo': 'كيفية التحديث', + 'admin.update.dockerText': + 'يعمل TREK الخاص بك في Docker. للتحديث إلى {version}، نفّذ الأوامر التالية على الخادم:', + 'admin.update.reloadHint': 'يرجى إعادة تحميل الصفحة بعد بضع ثوانٍ.', + 'admin.tabs.permissions': 'الصلاحيات', +}; +export default admin; diff --git a/shared/src/i18n/ar/airport.ts b/shared/src/i18n/ar/airport.ts new file mode 100644 index 00000000..88028e6d --- /dev/null +++ b/shared/src/i18n/ar/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'رمز المطار أو المدينة (مثل FRA)', +}; +export default airport; diff --git a/shared/src/i18n/ar/atlas.ts b/shared/src/i18n/ar/atlas.ts new file mode 100644 index 00000000..946e9645 --- /dev/null +++ b/shared/src/i18n/ar/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'بصمتك السفرية حول العالم', + 'atlas.countries': 'الدول', + 'atlas.trips': 'الرحلات', + 'atlas.places': 'الأماكن', + 'atlas.unmark': 'إزالة', + 'atlas.confirmMark': 'تعيين هذا البلد كمُزار؟', + 'atlas.confirmUnmark': 'إزالة هذا البلد من قائمة المُزارة؟', + 'atlas.confirmUnmarkRegion': 'إزالة هذه المنطقة من قائمة المُزارة؟', + 'atlas.markVisited': 'تعيين كمُزار', + 'atlas.markVisitedHint': 'إضافة هذا البلد إلى قائمة المُزارة', + 'atlas.markRegionVisitedHint': 'إضافة هذه المنطقة إلى قائمة المُزارة', + 'atlas.addToBucket': 'إضافة إلى قائمة الأمنيات', + 'atlas.addPoi': 'إضافة مكان', + 'atlas.searchCountry': 'ابحث عن دولة...', + 'atlas.bucketNamePlaceholder': 'الاسم (بلد، مدينة، مكان…)', + 'atlas.month': 'الشهر', + 'atlas.year': 'السنة', + 'atlas.addToBucketHint': 'حفظ كمكان تريد زيارته', + 'atlas.bucketWhen': 'متى تخطط للزيارة؟', + 'atlas.statsTab': 'الإحصائيات', + 'atlas.bucketTab': 'قائمة الأمنيات', + 'atlas.addBucket': 'إضافة إلى قائمة الأمنيات', + 'atlas.bucketNotesPlaceholder': 'ملاحظات (اختياري)', + 'atlas.bucketEmpty': 'قائمة أمنياتك فارغة', + 'atlas.bucketEmptyHint': 'أضف أماكن تحلم بزيارتها', + 'atlas.days': 'الأيام', + 'atlas.visitedCountries': 'الدول التي تمت زيارتها', + 'atlas.cities': 'المدن', + 'atlas.noData': 'لا توجد بيانات سفر بعد', + 'atlas.noDataHint': 'أنشئ رحلة وأضف أماكن لرؤية خريطتك العالمية', + 'atlas.lastTrip': 'آخر رحلة', + 'atlas.nextTrip': 'الرحلة القادمة', + 'atlas.daysLeft': 'يوم متبقٍ', + 'atlas.streak': 'سلسلة', + 'atlas.years': 'سنوات', + 'atlas.yearInRow': 'سنة متتالية', + 'atlas.yearsInRow': 'سنوات متتالية', + 'atlas.tripIn': 'رحلة في', + 'atlas.tripsIn': 'رحلات في', + 'atlas.since': 'منذ', + 'atlas.europe': 'أوروبا', + 'atlas.asia': 'آسيا', + 'atlas.northAmerica': 'أمريكا الشمالية', + 'atlas.southAmerica': 'أمريكا الجنوبية', + 'atlas.africa': 'أفريقيا', + 'atlas.oceania': 'أوقيانوسيا', + 'atlas.other': 'أخرى', + 'atlas.firstVisit': 'أول رحلة', + 'atlas.lastVisitLabel': 'آخر رحلة', + 'atlas.tripSingular': 'رحلة', + 'atlas.tripPlural': 'رحلات', + 'atlas.placeVisited': 'مكان تمت زيارته', + 'atlas.placesVisited': 'أماكن تمت زيارتها', +}; +export default atlas; diff --git a/shared/src/i18n/ar/backup.ts b/shared/src/i18n/ar/backup.ts new file mode 100644 index 00000000..24ea0d10 --- /dev/null +++ b/shared/src/i18n/ar/backup.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'النسخ الاحتياطي', + 'backup.subtitle': 'قاعدة البيانات وجميع الملفات المرفوعة', + 'backup.refresh': 'تحديث', + 'backup.upload': 'رفع نسخة احتياطية', + 'backup.uploading': 'جارٍ الرفع…', + 'backup.create': 'إنشاء نسخة', + 'backup.creating': 'جارٍ الإنشاء…', + 'backup.empty': 'لا توجد نسخ احتياطية بعد', + 'backup.createFirst': 'إنشاء أول نسخة', + 'backup.download': 'تنزيل', + 'backup.restore': 'استعادة', + 'backup.confirm.restore': + 'استعادة النسخة "{name}"؟\n\nسيتم استبدال جميع البيانات الحالية بالنسخة.', + 'backup.confirm.uploadRestore': + 'رفع واستعادة النسخة "{name}"؟\n\nسيتم الكتابة فوق جميع البيانات الحالية.', + 'backup.confirm.delete': 'حذف النسخة "{name}"؟', + 'backup.toast.loadError': 'فشل تحميل النسخ الاحتياطية', + 'backup.toast.created': 'تم إنشاء النسخة الاحتياطية بنجاح', + 'backup.toast.createError': 'فشل إنشاء النسخة', + 'backup.toast.restored': 'تمت الاستعادة. ستُعاد تحميل الصفحة…', + 'backup.toast.restoreError': 'فشلت الاستعادة', + 'backup.toast.uploadError': 'فشل الرفع', + 'backup.toast.deleted': 'تم حذف النسخة', + 'backup.toast.deleteError': 'فشل الحذف', + 'backup.toast.downloadError': 'فشل التنزيل', + 'backup.toast.settingsSaved': 'تم حفظ إعدادات النسخ الاحتياطي التلقائي', + 'backup.toast.settingsError': 'فشل حفظ الإعدادات', + 'backup.auto.title': 'النسخ الاحتياطي التلقائي', + 'backup.auto.subtitle': 'نسخ احتياطي تلقائي وفق جدول زمني', + 'backup.auto.enable': 'تفعيل النسخ التلقائي', + 'backup.auto.enableHint': + 'سيتم إنشاء نسخ احتياطية تلقائيًا وفق الجدول المختار', + 'backup.auto.interval': 'الفترة', + 'backup.auto.hour': 'التنفيذ في الساعة', + 'backup.auto.hourHint': 'التوقيت المحلي للخادم (تنسيق {format})', + 'backup.auto.dayOfWeek': 'يوم الأسبوع', + 'backup.auto.dayOfMonth': 'يوم الشهر', + 'backup.auto.dayOfMonthHint': 'محدود بين 1–28 للتوافق مع جميع الأشهر', + 'backup.auto.scheduleSummary': 'الجدول', + 'backup.auto.summaryDaily': 'كل يوم الساعة {hour}:00', + 'backup.auto.summaryWeekly': 'كل {day} الساعة {hour}:00', + 'backup.auto.summaryMonthly': 'اليوم {day} من كل شهر الساعة {hour}:00', + 'backup.auto.envLockedHint': + 'النسخ الاحتياطي التلقائي مُعدّ عبر متغيرات بيئة Docker. لتعديل الإعدادات، حدّث docker-compose.yml وأعد تشغيل الحاوية.', + 'backup.auto.copyEnv': 'نسخ متغيرات بيئة Docker', + 'backup.auto.envCopied': 'تم نسخ متغيرات بيئة Docker إلى الحافظة', + 'backup.auto.keepLabel': 'حذف النسخ القديمة بعد', + 'backup.dow.sunday': 'أحد', + 'backup.dow.monday': 'إثن', + 'backup.dow.tuesday': 'ثلا', + 'backup.dow.wednesday': 'أرب', + 'backup.dow.thursday': 'خمي', + 'backup.dow.friday': 'جمع', + 'backup.dow.saturday': 'سبت', + 'backup.interval.hourly': 'كل ساعة', + 'backup.interval.daily': 'يوميًا', + 'backup.interval.weekly': 'أسبوعيًا', + 'backup.interval.monthly': 'شهريًا', + 'backup.keep.1day': 'يوم واحد', + 'backup.keep.3days': '3 أيام', + 'backup.keep.7days': '7 أيام', + 'backup.keep.14days': '14 يومًا', + 'backup.keep.30days': '30 يومًا', + 'backup.keep.forever': 'الاحتفاظ للأبد', + 'backup.restoreConfirmTitle': 'استعادة النسخة الاحتياطية؟', + 'backup.restoreWarning': + 'سيتم استبدال جميع البيانات الحالية (الرحلات، الأماكن، المستخدمون، المرفوعات) بالنسخة نهائيًا. لا يمكن التراجع عن ذلك.', + 'backup.restoreTip': + 'نصيحة: أنشئ نسخة احتياطية للحالة الحالية قبل الاستعادة.', + 'backup.restoreConfirm': 'نعم، استعادة', +}; +export default backup; diff --git a/shared/src/i18n/ar/budget.ts b/shared/src/i18n/ar/budget.ts new file mode 100644 index 00000000..6ee60f0c --- /dev/null +++ b/shared/src/i18n/ar/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'الميزانية', + 'budget.exportCsv': 'تصدير CSV', + 'budget.emptyTitle': 'لم يتم إنشاء ميزانية بعد', + 'budget.emptyText': 'أنشئ فئات وإدخالات لتخطيط ميزانية سفرك', + 'budget.emptyPlaceholder': 'أدخل اسم الفئة...', + 'budget.createCategory': 'إنشاء فئة', + 'budget.category': 'الفئة', + 'budget.categoryName': 'اسم الفئة', + 'budget.table.name': 'الاسم', + 'budget.table.total': 'الإجمالي', + 'budget.table.persons': 'الأشخاص', + 'budget.table.days': 'الأيام', + 'budget.table.perPerson': 'لكل شخص', + 'budget.table.perDay': 'لكل يوم', + 'budget.table.perPersonDay': 'لكل شخص / يوم', + 'budget.table.note': 'ملاحظة', + 'budget.table.date': 'التاريخ', + 'budget.newEntry': 'إدخال جديد', + 'budget.defaultEntry': 'إدخال جديد', + 'budget.defaultCategory': 'فئة جديدة', + 'budget.total': 'الإجمالي', + 'budget.totalBudget': 'إجمالي الميزانية', + 'budget.byCategory': 'حسب الفئة', + 'budget.editTooltip': 'انقر للتعديل', + 'budget.linkedToReservation': 'مرتبط بحجز — عدّل الاسم هناك', + 'budget.confirm.deleteCategory': + 'هل تريد حذف الفئة "{name}" مع {count} إدخالات؟', + 'budget.deleteCategory': 'حذف الفئة', + 'budget.perPerson': 'لكل شخص', + 'budget.paid': 'مدفوع', + 'budget.open': 'مفتوح', + 'budget.noMembers': 'لا أعضاء معينون', + 'budget.settlement': 'التسوية', + 'budget.settlementInfo': + 'انقر على صورة العضو في بند الميزانية لتحديده باللون الأخضر — وهذا يعني أنه دفع. ثم تُظهر التسوية من يدين لمن وبكم.', + 'budget.netBalances': 'الأرصدة الصافية', + 'budget.categoriesLabel': 'فئات', +}; +export default budget; diff --git a/shared/src/i18n/ar/categories.ts b/shared/src/i18n/ar/categories.ts new file mode 100644 index 00000000..cfb0123e --- /dev/null +++ b/shared/src/i18n/ar/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'الفئات', + 'categories.subtitle': 'إدارة فئات الأماكن', + 'categories.new': 'فئة جديدة', + 'categories.empty': 'لا توجد فئات بعد', + 'categories.namePlaceholder': 'اسم الفئة', + 'categories.icon': 'الأيقونة', + 'categories.color': 'اللون', + 'categories.customColor': 'اختيار لون مخصص', + 'categories.preview': 'معاينة', + 'categories.defaultName': 'فئة', + 'categories.update': 'تحديث', + 'categories.create': 'إنشاء', + 'categories.confirm.delete': + 'حذف الفئة؟ لن يتم حذف الأماكن التابعة لهذه الفئة.', + 'categories.toast.loadError': 'فشل تحميل الفئات', + 'categories.toast.nameRequired': 'يرجى إدخال اسم', + 'categories.toast.updated': 'تم تحديث الفئة', + 'categories.toast.created': 'تم إنشاء الفئة', + 'categories.toast.saveError': 'فشل الحفظ', + 'categories.toast.deleted': 'تم حذف الفئة', + 'categories.toast.deleteError': 'فشل الحذف', +}; +export default categories; diff --git a/shared/src/i18n/ar/collab.ts b/shared/src/i18n/ar/collab.ts new file mode 100644 index 00000000..ddfc600d --- /dev/null +++ b/shared/src/i18n/ar/collab.ts @@ -0,0 +1,72 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'الدردشة', + 'collab.tabs.notes': 'الملاحظات', + 'collab.tabs.polls': 'الاستطلاعات', + 'collab.whatsNext.title': 'ما التالي', + 'collab.whatsNext.today': 'اليوم', + 'collab.whatsNext.tomorrow': 'غدًا', + 'collab.whatsNext.empty': 'لا توجد أنشطة قادمة', + 'collab.whatsNext.until': 'إلى', + 'collab.whatsNext.emptyHint': 'ستظهر الأنشطة التي لها وقت هنا', + 'collab.chat.send': 'إرسال', + 'collab.chat.placeholder': 'اكتب رسالة...', + 'collab.chat.empty': 'ابدأ المحادثة', + 'collab.chat.emptyHint': 'تتم مشاركة الرسائل مع جميع أعضاء الرحلة', + 'collab.chat.emptyDesc': 'شارك الأفكار والخطط والتحديثات مع مجموعة السفر', + 'collab.chat.today': 'اليوم', + 'collab.chat.yesterday': 'أمس', + 'collab.chat.deletedMessage': 'حذف رسالة', + 'collab.chat.reply': 'رد', + 'collab.chat.loadMore': 'تحميل الرسائل الأقدم', + 'collab.chat.justNow': 'الآن', + 'collab.chat.minutesAgo': 'منذ {n} د', + 'collab.chat.hoursAgo': 'منذ {n} س', + 'collab.notes.title': 'الملاحظات', + 'collab.notes.new': 'ملاحظة جديدة', + 'collab.notes.empty': 'لا توجد ملاحظات بعد', + 'collab.notes.emptyHint': 'ابدأ بتسجيل الأفكار والخطط', + 'collab.notes.all': 'الكل', + 'collab.notes.titlePlaceholder': 'عنوان الملاحظة', + 'collab.notes.contentPlaceholder': 'اكتب شيئًا...', + 'collab.notes.categoryPlaceholder': 'الفئة', + 'collab.notes.newCategory': 'فئة جديدة...', + 'collab.notes.category': 'الفئة', + 'collab.notes.noCategory': 'بلا فئة', + 'collab.notes.color': 'اللون', + 'collab.notes.save': 'حفظ', + 'collab.notes.cancel': 'إلغاء', + 'collab.notes.edit': 'تعديل', + 'collab.notes.delete': 'حذف', + 'collab.notes.pin': 'تثبيت', + 'collab.notes.unpin': 'إلغاء التثبيت', + 'collab.notes.daysAgo': 'منذ {n} يوم', + 'collab.notes.categorySettings': 'إدارة الفئات', + 'collab.notes.create': 'إنشاء', + 'collab.notes.website': 'الموقع الإلكتروني', + 'collab.notes.attachFiles': 'إرفاق ملفات', + 'collab.notes.noCategoriesYet': 'لا توجد فئات بعد', + 'collab.notes.emptyDesc': 'أنشئ ملاحظة للبدء', + 'collab.polls.title': 'الاستطلاعات', + 'collab.polls.new': 'استطلاع جديد', + 'collab.polls.empty': 'لا توجد استطلاعات بعد', + 'collab.polls.emptyHint': 'اسأل المجموعة وصوّتوا معًا', + 'collab.polls.question': 'السؤال', + 'collab.polls.questionPlaceholder': 'ماذا ينبغي أن نفعل؟', + 'collab.polls.addOption': '+ إضافة خيار', + 'collab.polls.optionPlaceholder': 'الخيار {n}', + 'collab.polls.create': 'إنشاء استطلاع', + 'collab.polls.close': 'إغلاق', + 'collab.polls.closed': 'مغلق', + 'collab.polls.votes': '{n} أصوات', + 'collab.polls.vote': '{n} صوت', + 'collab.polls.multipleChoice': 'اختيار متعدد', + 'collab.polls.multiChoice': 'اختيار متعدد', + 'collab.polls.deadline': 'الموعد النهائي', + 'collab.polls.option': 'خيار', + 'collab.polls.options': 'الخيارات', + 'collab.polls.delete': 'حذف', + 'collab.polls.closedSection': 'مغلق', +}; +export default collab; diff --git a/shared/src/i18n/ar/common.ts b/shared/src/i18n/ar/common.ts new file mode 100644 index 00000000..0f665c12 --- /dev/null +++ b/shared/src/i18n/ar/common.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'حفظ', + 'common.showMore': 'عرض المزيد', + 'common.showLess': 'عرض أقل', + 'common.cancel': 'إلغاء', + 'common.clear': 'مسح', + 'common.delete': 'حذف', + 'common.edit': 'تعديل', + 'common.add': 'إضافة', + 'common.loading': 'جارٍ التحميل...', + 'common.import': 'استيراد', + 'common.select': 'تحديد', + 'common.selectAll': 'تحديد الكل', + 'common.deselectAll': 'إلغاء تحديد الكل', + 'common.error': 'خطأ', + 'common.unknownError': 'خطأ غير معروف', + 'common.tooManyAttempts': 'محاولات كثيرة جدًا. يرجى المحاولة لاحقًا.', + 'common.back': 'رجوع', + 'common.all': 'الكل', + 'common.close': 'إغلاق', + 'common.open': 'فتح', + 'common.upload': 'رفع', + 'common.search': 'بحث', + 'common.confirm': 'تأكيد', + 'common.ok': 'حسنًا', + 'common.yes': 'نعم', + 'common.no': 'لا', + 'common.or': 'أو', + 'common.none': 'لا شيء', + 'common.date': 'التاريخ', + 'common.rename': 'إعادة تسمية', + 'common.discardChanges': 'تجاهل التغييرات', + 'common.discard': 'تجاهل', + 'common.name': 'الاسم', + 'common.email': 'البريد الإلكتروني', + 'common.password': 'كلمة المرور', + 'common.saving': 'جارٍ الحفظ...', + 'common.saved': 'تم الحفظ', + 'common.update': 'تحديث', + 'common.change': 'تغيير', + 'common.uploading': 'جارٍ الرفع...', + 'common.backToPlanning': 'العودة إلى التخطيط', + 'common.reset': 'إعادة تعيين', + 'common.expand': 'توسيع', + 'common.collapse': 'طي', + 'common.copy': 'نسخ', + 'common.copied': 'تم النسخ', +}; +export default common; diff --git a/shared/src/i18n/ar/dashboard.ts b/shared/src/i18n/ar/dashboard.ts new file mode 100644 index 00000000..566b1215 --- /dev/null +++ b/shared/src/i18n/ar/dashboard.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'رحلاتي', + 'dashboard.subtitle.loading': 'جارٍ تحميل الرحلات...', + 'dashboard.subtitle.trips': '{count} رحلة ({archived} مؤرشفة)', + 'dashboard.subtitle.empty': 'ابدأ رحلتك الأولى', + 'dashboard.subtitle.activeOne': '{count} رحلة نشطة', + 'dashboard.subtitle.activeMany': '{count} رحلات نشطة', + 'dashboard.subtitle.archivedSuffix': ' · {count} مؤرشفة', + 'dashboard.newTrip': 'رحلة جديدة', + 'dashboard.gridView': 'عرض شبكي', + 'dashboard.listView': 'عرض قائمة', + 'dashboard.currency': 'العملة', + 'dashboard.timezone': 'المناطق الزمنية', + 'dashboard.localTime': 'المحلي', + 'dashboard.timezoneCustomTitle': 'منطقة زمنية مخصصة', + 'dashboard.timezoneCustomLabelPlaceholder': 'الاسم (اختياري)', + 'dashboard.timezoneCustomTzPlaceholder': 'مثال: Asia/Riyadh', + 'dashboard.timezoneCustomAdd': 'إضافة', + 'dashboard.timezoneCustomErrorEmpty': 'أدخل معرّف منطقة زمنية', + 'dashboard.timezoneCustomErrorInvalid': + 'منطقة زمنية غير صالحة. استخدم صيغة مثل Asia/Riyadh', + 'dashboard.timezoneCustomErrorDuplicate': 'مضافة بالفعل', + 'dashboard.emptyTitle': 'لا توجد رحلات بعد', + 'dashboard.emptyText': 'أنشئ رحلتك الأولى وابدأ التخطيط', + 'dashboard.emptyButton': 'إنشاء أول رحلة', + 'dashboard.nextTrip': 'الرحلة القادمة', + 'dashboard.shared': 'مشتركة', + 'dashboard.sharedBy': 'شاركها {name}', + 'dashboard.days': 'الأيام', + 'dashboard.places': 'الأماكن', + 'dashboard.members': 'ال חברים', + 'dashboard.archive': 'أرشفة', + 'dashboard.copyTrip': 'نسخ', + 'dashboard.copySuffix': 'نسخة', + 'dashboard.restore': 'استعادة', + 'dashboard.archived': 'مؤرشفة', + 'dashboard.status.ongoing': 'جارية', + 'dashboard.status.today': 'اليوم', + 'dashboard.status.tomorrow': 'غدًا', + 'dashboard.status.past': 'منتهية', + 'dashboard.status.daysLeft': 'متبقي {count} يوم', + 'dashboard.toast.loadError': 'فشل تحميل الرحلات', + 'dashboard.toast.created': 'تم إنشاء الرحلة بنجاح', + 'dashboard.toast.createError': 'فشل إنشاء الرحلة', + 'dashboard.toast.updated': 'تم تحديث الرحلة', + 'dashboard.toast.updateError': 'فشل تحديث الرحلة', + 'dashboard.toast.deleted': 'تم حذف الرحلة', + 'dashboard.toast.deleteError': 'فشل حذف الرحلة', + 'dashboard.toast.archived': 'تمت أرشفة الرحلة', + 'dashboard.toast.archiveError': 'فشل الأرشفة', + 'dashboard.toast.restored': 'تمت استعادة الرحلة', + 'dashboard.toast.restoreError': 'فشل الاستعادة', + 'dashboard.toast.copied': 'تم نسخ الرحلة!', + 'dashboard.toast.copyError': 'فشل نسخ الرحلة', + 'dashboard.confirm.delete': + 'حذف الرحلة "{title}"؟ سيتم حذف جميع الأماكن والخطط نهائيًا.', + 'dashboard.editTrip': 'تعديل الرحلة', + 'dashboard.createTrip': 'إنشاء رحلة جديدة', + 'dashboard.tripTitle': 'العنوان', + 'dashboard.tripTitlePlaceholder': 'مثال: صيف في اليابان', + 'dashboard.tripDescription': 'الوصف', + 'dashboard.tripDescriptionPlaceholder': 'عمّ تتحدث هذه الرحلة؟', + 'dashboard.startDate': 'تاريخ البداية', + 'dashboard.endDate': 'تاريخ النهاية', + 'dashboard.dayCount': 'عدد الأيام', + 'dashboard.dayCountHint': + 'عدد الأيام المراد التخطيط لها عندما لا يتم تحديد تواريخ السفر.', + 'dashboard.noDateHint': + 'لا يوجد تاريخ محدد. سيتم إنشاء 7 أيام افتراضية ويمكنك تغيير ذلك لاحقًا.', + 'dashboard.coverImage': 'صورة الغلاف', + 'dashboard.addCoverImage': 'إضافة صورة غلاف', + 'dashboard.addMembers': 'رفاق السفر', + 'dashboard.addMember': 'إضافة عضو', + 'dashboard.coverSaved': 'تم حفظ صورة الغلاف', + 'dashboard.coverUploadError': 'فشل الرفع', + 'dashboard.coverRemoveError': 'فشل الإزالة', + 'dashboard.titleRequired': 'العنوان مطلوب', + 'dashboard.endDateError': 'يجب أن يكون تاريخ النهاية بعد البداية', +}; +export default dashboard; diff --git a/shared/src/i18n/ar/day.ts b/shared/src/i18n/ar/day.ts new file mode 100644 index 00000000..e19a16a6 --- /dev/null +++ b/shared/src/i18n/ar/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'احتمال هطول الأمطار', + 'day.precipitation': 'الهطول', + 'day.wind': 'الرياح', + 'day.sunrise': 'شروق الشمس', + 'day.sunset': 'غروب الشمس', + 'day.hourlyForecast': 'التوقعات بالساعة', + 'day.climateHint': + 'متوسطات تاريخية — التوقعات الفعلية متاحة خلال 16 يومًا من هذا التاريخ.', + 'day.noWeather': 'لا تتوفر بيانات طقس. أضف مكانًا بإحداثيات.', + 'day.overview': 'ملخص اليوم', + 'day.accommodation': 'الإقامة', + 'day.addAccommodation': 'إضافة إقامة', + 'day.hotelDayRange': 'تطبيق على الأيام', + 'day.noPlacesForHotel': 'أضف أماكن إلى رحلتك أولًا', + 'day.allDays': 'الكل', + 'day.checkIn': 'تسجيل الوصول', + 'day.checkInUntil': 'حتى', + 'day.checkOut': 'تسجيل المغادرة', + 'day.confirmation': 'التأكيد', + 'day.editAccommodation': 'تعديل الإقامة', + 'day.reservations': 'الحجوزات', +}; +export default day; diff --git a/shared/src/i18n/ar/dayplan.ts b/shared/src/i18n/ar/dayplan.ts new file mode 100644 index 00000000..42df2f5a --- /dev/null +++ b/shared/src/i18n/ar/dayplan.ts @@ -0,0 +1,38 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'تصدير التقويم (ICS)', + 'dayplan.emptyDay': 'لا توجد أماكن مخططة لهذا اليوم', + 'dayplan.cannotReorderTransport': + 'لا يمكن إعادة ترتيب الحجوزات ذات الوقت الثابت', + 'dayplan.confirmRemoveTimeTitle': 'إزالة الوقت؟', + 'dayplan.confirmRemoveTimeBody': + 'هذا المكان له وقت ثابت ({time}). نقله سيزيل الوقت ويسمح بالترتيب الحر.', + 'dayplan.confirmRemoveTimeAction': 'إزالة الوقت ونقل', + 'dayplan.cannotDropOnTimed': + 'لا يمكن وضع العناصر بين الإدخالات المرتبطة بوقت', + 'dayplan.cannotBreakChronology': + 'سيؤدي هذا إلى كسر الترتيب الزمني للعناصر والحجوزات المجدولة', + 'dayplan.addNote': 'إضافة ملاحظة', + 'dayplan.editNote': 'تعديل الملاحظة', + 'dayplan.noteAdd': 'إضافة ملاحظة', + 'dayplan.noteEdit': 'تعديل الملاحظة', + 'dayplan.noteTitle': 'ملاحظة', + 'dayplan.noteSubtitle': 'ملاحظة يومية', + 'dayplan.totalCost': 'إجمالي التكلفة', + 'dayplan.days': 'الأيام', + 'dayplan.dayN': 'اليوم {n}', + 'dayplan.calculating': 'جارٍ الحساب...', + 'dayplan.route': 'المسار', + 'dayplan.optimize': 'تحسين', + 'dayplan.optimized': 'تم تحسين المسار', + 'dayplan.routeError': 'فشل حساب المسار', + 'dayplan.toast.needTwoPlaces': 'يلزم مكانان على الأقل لتحسين المسار', + 'dayplan.toast.routeOptimized': 'تم تحسين المسار', + 'dayplan.toast.noGeoPlaces': 'لم يتم العثور على أماكن بإحداثيات لحساب المسار', + 'dayplan.confirmed': 'مؤكد', + 'dayplan.pendingRes': 'قيد الانتظار', + 'dayplan.pdfTooltip': 'تصدير خطة اليوم بصيغة PDF', + 'dayplan.pdfError': 'فشل تصدير PDF', +}; +export default dayplan; diff --git a/shared/src/i18n/ar/externalNotifications.ts b/shared/src/i18n/ar/externalNotifications.ts new file mode 100644 index 00000000..caeecabd --- /dev/null +++ b/shared/src/i18n/ar/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const ar: NotificationLocale = { + email: { + footer: 'تلقيت هذا لأنك قمت بتفعيل الإشعارات في TREK.', + manage: 'إدارة التفضيلات', + madeWith: 'Made with', + openTrek: 'فتح TREK', + }, + events: { + trip_invite: (p) => ({ + title: `دعوة إلى "${p.trip}"`, + body: `${p.actor} دعا ${p.invitee || 'عضو'} إلى الرحلة "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `حجز جديد: ${p.booking}`, + body: `${p.actor} أضاف حجز "${p.booking}" (${p.type}) إلى "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `تذكير: ${p.trip}`, + body: `رحلتك "${p.trip}" تقترب!`, + }), + todo_due: (p) => ({ + title: `مهمة مستحقة: ${p.todo}`, + body: `"${p.todo}" في "${p.trip}" مستحقة في ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'دعوة دمج الإجازة', + body: `${p.actor} يدعوك لدمج خطط الإجازة. افتح TREK للقبول أو الرفض.`, + }), + photos_shared: (p) => ({ + title: `${p.count} صور مشتركة`, + body: `${p.actor} شارك ${p.count} صورة في "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `رسالة جديدة في "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `قائمة التعبئة: ${p.category}`, + body: `${p.actor} عيّنك في فئة "${p.category}" في "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'إصدار TREK جديد متاح', + body: `TREK ${p.version} متاح الآن. تفضل بزيارة لوحة الإدارة للتحديث.`, + }), + synology_session_cleared: () => ({ + title: 'تمت إعادة تعيين جلسة Synology', + body: 'تغيّر حسابك أو رابط Synology. تم تسجيل خروجك من Synology Photos.', + }), + }, + passwordReset: { + subject: 'إعادة تعيين كلمة المرور', + greeting: 'مرحبا', + body: 'تلقينا طلبًا لإعادة تعيين كلمة المرور لحسابك في TREK. انقر على الزر أدناه لتعيين كلمة مرور جديدة.', + ctaIntro: 'إعادة تعيين كلمة المرور', + expiry: 'تنتهي صلاحية هذا الرابط خلال 60 دقيقة.', + ignore: + 'إذا لم تطلب هذا، يمكنك تجاهل هذه الرسالة — لن تتغير كلمة المرور الخاصة بك.', + }, +}; + +export default ar; diff --git a/shared/src/i18n/ar/files.ts b/shared/src/i18n/ar/files.ts new file mode 100644 index 00000000..a1e4efb3 --- /dev/null +++ b/shared/src/i18n/ar/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'الملفات', + 'files.pageTitle': 'الملفات والمستندات', + 'files.subtitle': '{count} ملف لـ {trip}', + 'files.download': 'تنزيل', + 'files.openError': 'تعذر فتح الملف', + 'files.downloadPdf': 'تنزيل PDF', + 'files.count': '{count} ملفات', + 'files.countSingular': 'ملف واحد', + 'files.uploaded': 'تم رفع {count}', + 'files.uploadError': 'فشل الرفع', + 'files.dropzone': 'أسقط الملفات هنا', + 'files.dropzoneHint': 'أو انقر للتصفح', + 'files.allowedTypes': + 'صور، PDF، DOC، DOCX، XLS، XLSX، TXT، CSV · حد أقصى 50 ميغابايت', + 'files.uploading': 'جارٍ الرفع...', + 'files.filterAll': 'الكل', + 'files.filterPdf': 'ملفات PDF', + 'files.filterImages': 'الصور', + 'files.filterDocs': 'المستندات', + 'files.filterCollab': 'ملاحظات Collab', + 'files.sourceCollab': 'من ملاحظات Collab', + 'files.empty': 'لا توجد ملفات بعد', + 'files.emptyHint': 'ارفع ملفات لإرفاقها برحلتك', + 'files.openTab': 'فتح في تبويب جديد', + 'files.confirm.delete': 'هل تريد حذف هذا الملف؟', + 'files.toast.deleted': 'تم حذف الملف', + 'files.toast.deleteError': 'فشل حذف الملف', + 'files.sourcePlan': 'خطة اليوم', + 'files.sourceBooking': 'الحجز', + 'files.sourceTransport': 'النقل', + 'files.attach': 'إرفاق', + 'files.pasteHint': 'يمكنك أيضًا لصق الصور من الحافظة (Ctrl+V)', + 'files.trash': 'سلة المهملات', + 'files.trashEmpty': 'سلة المهملات فارغة', + 'files.emptyTrash': 'إفراغ السلة', + 'files.restore': 'استعادة', + 'files.star': 'تمييز', + 'files.unstar': 'إلغاء التمييز', + 'files.assign': 'إسناد', + 'files.assignTitle': 'إسناد ملف', + 'files.assignPlace': 'المكان', + 'files.assignBooking': 'الحجز', + 'files.assignTransport': 'النقل', + 'files.unassigned': 'غير مسند', + 'files.unlink': 'إزالة الرابط', + 'files.toast.trashed': 'تم النقل إلى سلة المهملات', + 'files.toast.restored': 'تمت استعادة الملف', + 'files.toast.trashEmptied': 'تم إفراغ سلة المهملات', + 'files.toast.assigned': 'تم إسناد الملف', + 'files.toast.assignError': 'فشل الإسناد', + 'files.toast.restoreError': 'فشلت الاستعادة', + 'files.confirm.permanentDelete': + 'حذف هذا الملف نهائيًا؟ لا يمكن التراجع عن ذلك.', + 'files.confirm.emptyTrash': + 'حذف جميع ملفات سلة المهملات نهائيًا؟ لا يمكن التراجع عن ذلك.', + 'files.noteLabel': 'ملاحظة', + 'files.notePlaceholder': 'أضف ملاحظة...', +}; +export default files; diff --git a/shared/src/i18n/ar/index.ts b/shared/src/i18n/ar/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/ar/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/ar/inspector.ts b/shared/src/i18n/ar/inspector.ts new file mode 100644 index 00000000..fcecf8b0 --- /dev/null +++ b/shared/src/i18n/ar/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'مفتوح', + 'inspector.closed': 'مغلق', + 'inspector.openingHours': 'ساعات العمل', + 'inspector.showHours': 'عرض ساعات العمل', + 'inspector.files': 'الملفات', + 'inspector.filesCount': '{count} ملفات', + 'inspector.remove': 'إزالة', + 'inspector.removeFromDay': 'إزالة من اليوم', + 'inspector.addToDay': 'إضافة إلى اليوم', + 'inspector.confirmedRes': 'حجز مؤكد', + 'inspector.pendingRes': 'حجز قيد الانتظار', + 'inspector.google': 'فتح في Google Maps', + 'inspector.website': 'فتح الموقع الإلكتروني', + 'inspector.addRes': 'حجز', + 'inspector.editRes': 'تعديل الحجز', + 'inspector.participants': 'المشاركون', + 'inspector.trackStats': 'بيانات المسار', +}; +export default inspector; diff --git a/shared/src/i18n/ar/journey.ts b/shared/src/i18n/ar/journey.ts new file mode 100644 index 00000000..1ed12a24 --- /dev/null +++ b/shared/src/i18n/ar/journey.ts @@ -0,0 +1,56 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'البحث في الرحلات…', + 'journey.search.noResults': 'لا توجد رحلات تطابق "{query}"', + 'journey.status.archived': 'مؤرشف', + 'journey.detail.backToJourney': 'العودة للمجلة', + 'journey.detail.photos': 'صور', + 'journey.detail.day': 'اليوم {number}', + 'journey.detail.places': 'أماكن', + 'journey.skeletons.show': 'إظهار الاقتراحات', + 'journey.skeletons.hide': 'إخفاء الاقتراحات', + 'journey.editor.discardChangesConfirm': + 'لديك تغييرات غير محفوظة. هل تريد تجاهلها؟', + 'journey.editor.uploadFailed': 'فشل رفع الصور', + 'journey.editor.uploadPhotos': 'رفع صور', + 'journey.editor.uploading': '...جارٍ الرفع', + 'journey.editor.uploadingProgress': 'جارٍ الرفع {done}/{total}…', + 'journey.editor.uploadPartialFailed': + 'فشل رفع {failed} من {total} — احفظ مجدداً للمحاولة', + 'journey.editor.fromGallery': 'من المعرض', + 'journey.editor.addAnother': 'إضافة آخر', + 'journey.editor.makeFirst': 'جعله الأول', + 'journey.editor.searching': 'جارٍ البحث...', + 'journey.share.copy': 'نسخ', + 'journey.share.copied': 'تم النسخ!', + 'journey.invite.role': 'الدور', + 'journey.invite.viewer': 'مشاهد', + 'journey.invite.editor': 'محرر', + 'journey.invite.invite': 'دعوة', + 'journey.invite.inviting': 'جارٍ الدعوة...', + 'journey.settings.endJourney': 'أرشفة الرحلة', + 'journey.settings.reopenJourney': 'استعادة الرحلة', + 'journey.settings.archived': 'تم أرشفة الرحلة', + 'journey.settings.reopened': 'تمت إعادة فتح الرحلة', + 'journey.settings.endDescription': + 'يخفي شارة البث المباشر. يمكنك إعادة الفتح في أي وقت.', + 'journey.settings.failedToDelete': 'فشل في الحذف', + 'journey.entries.deleteTitle': 'حذف الإدخال', + 'journey.photosUploaded': 'تم رفع {count} صورة', + 'journey.photosUploadFailed': 'فشل رفع بعض الصور', + 'journey.photosAdded': 'تمت إضافة {count} صورة', + 'journey.picker.tripPeriod': 'فترة الرحلة', + 'journey.picker.dateRange': 'نطاق التاريخ', + 'journey.picker.allPhotos': 'كل الصور', + 'journey.picker.albums': 'ألبومات', + 'journey.picker.selected': 'محدد', + 'journey.picker.addTo': 'إضافة إلى', + 'journey.picker.newGallery': 'معرض جديد', + 'journey.picker.selectAll': 'تحديد الكل', + 'journey.picker.deselectAll': 'إلغاء تحديد الكل', + 'journey.picker.noAlbums': 'لم يتم العثور على ألبومات', + 'journey.picker.selectDate': 'اختر تاريخ', + 'journey.picker.search': 'بحث', +}; +export default journey; diff --git a/shared/src/i18n/ar/login.ts b/shared/src/i18n/ar/login.ts new file mode 100644 index 00000000..3463d293 --- /dev/null +++ b/shared/src/i18n/ar/login.ts @@ -0,0 +1,91 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'فشل تسجيل الدخول. يرجى التحقق من بياناتك.', + 'login.tagline': 'رحلاتك.\nخطتك.', + 'login.description': + 'خطط لرحلاتك بشكل تعاوني مع خرائط تفاعلية وميزانيات ومزامنة لحظية.', + 'login.features.maps': 'خرائط تفاعلية', + 'login.features.mapsDesc': 'Google Places ومسارات وتجميع', + 'login.features.realtime': 'مزامنة فورية', + 'login.features.realtimeDesc': 'خططوا معًا عبر WebSocket', + 'login.features.budget': 'تتبع الميزانية', + 'login.features.budgetDesc': 'فئات ورسوم وتقسيم لكل شخص', + 'login.features.collab': 'تعاون', + 'login.features.collabDesc': 'عدة مستخدمين مع رحلات مشتركة', + 'login.features.packing': 'قوائم تجهيز', + 'login.features.packingDesc': 'فئات وتقدم واقتراحات', + 'login.features.bookings': 'الحجوزات', + 'login.features.bookingsDesc': 'رحلات وفنادق ومطاعم وغير ذلك', + 'login.features.files': 'المستندات', + 'login.features.filesDesc': 'رفع الملفات وإدارتها', + 'login.features.routes': 'مسارات ذكية', + 'login.features.routesDesc': 'تحسين تلقائي وتصدير إلى Google Maps', + 'login.selfHosted': 'استضافة ذاتية · مفتوح المصدر · بياناتك تبقى ملكك', + 'login.title': 'تسجيل الدخول', + 'login.subtitle': 'مرحبًا بعودتك', + 'login.signingIn': 'جارٍ تسجيل الدخول…', + 'login.signIn': 'دخول', + 'login.createAdmin': 'إنشاء حساب مسؤول', + 'login.createAdminHint': 'أعد إعداد أول حساب مسؤول لـ TREK.', + 'login.setNewPassword': 'تعيين كلمة مرور جديدة', + 'login.setNewPasswordHint': 'يجب عليك تغيير كلمة المرور قبل المتابعة.', + 'login.createAccount': 'إنشاء حساب', + 'login.createAccountHint': 'سجّل حسابًا جديدًا.', + 'login.creating': 'جارٍ الإنشاء…', + 'login.noAccount': 'ليس لديك حساب؟', + 'login.hasAccount': 'لديك حساب بالفعل؟', + 'login.register': 'تسجيل', + 'login.username': 'اسم المستخدم', + 'login.oidc.registrationDisabled': 'التسجيل معطّل. تواصل مع المسؤول.', + 'login.oidc.noEmail': 'لم يتم استلام بريد إلكتروني من المزوّد.', + 'login.oidc.tokenFailed': 'فشلت المصادقة.', + 'login.oidc.invalidState': 'جلسة غير صالحة. حاول مرة أخرى.', + 'login.demoFailed': 'فشل الدخول إلى العرض التجريبي', + 'login.oidcSignIn': 'تسجيل الدخول عبر {name}', + 'login.oidcOnly': + 'تم تعطيل المصادقة بكلمة المرور. يرجى تسجيل الدخول عبر مزود SSO.', + 'login.oidcLoggedOut': 'تم تسجيل خروجك. سجّل الدخول مجدداً عبر مزود SSO.', + 'login.demoHint': 'جرّب العرض التجريبي دون الحاجة للتسجيل', + 'login.mfaTitle': 'المصادقة الثنائية', + 'login.mfaSubtitle': 'أدخل الرمز المكون من 6 أرقام من تطبيق المصادقة.', + 'login.mfaCodeLabel': 'رمز التحقق', + 'login.mfaCodeRequired': 'أدخل الرمز من تطبيق المصادقة.', + 'login.mfaHint': 'افتح Google Authenticator أو Authy أو أي تطبيق TOTP آخر.', + 'login.mfaBack': '← العودة لتسجيل الدخول', + 'login.mfaVerify': 'تحقق', + 'login.invalidInviteLink': 'رابط الدعوة غير صالح أو منتهي الصلاحية', + 'login.oidcFailed': 'فشل تسجيل الدخول عبر OIDC', + 'login.usernameRequired': 'اسم المستخدم مطلوب', + 'login.passwordMinLength': 'يجب أن تكون كلمة المرور 8 أحرف على الأقل', + 'login.forgotPassword': 'نسيت كلمة المرور؟', + 'login.forgotPasswordTitle': 'إعادة تعيين كلمة المرور', + 'login.forgotPasswordBody': + 'أدخل عنوان البريد الإلكتروني المسجَّل. إذا كان الحساب موجودًا، سنرسل رابط إعادة التعيين.', + 'login.forgotPasswordSubmit': 'إرسال الرابط', + 'login.forgotPasswordSentTitle': 'تحقق من بريدك', + 'login.forgotPasswordSentBody': + 'إذا كان هناك حساب مرتبط بهذا البريد، فإن الرابط في الطريق. تنتهي صلاحيته خلال 60 دقيقة.', + 'login.forgotPasswordSmtpHintOff': + 'ملاحظة: لم يقم المسؤول بتكوين SMTP، لذا سيتم كتابة رابط إعادة التعيين في وحدة تحكم الخادم بدلاً من إرساله عبر البريد الإلكتروني.', + 'login.backToLogin': 'العودة إلى تسجيل الدخول', + 'login.newPassword': 'كلمة المرور الجديدة', + 'login.confirmPassword': 'تأكيد كلمة المرور الجديدة', + 'login.passwordsDontMatch': 'كلمتا المرور غير متطابقتين', + 'login.mfaCode': 'رمز 2FA', + 'login.resetPasswordTitle': 'ضبط كلمة مرور جديدة', + 'login.resetPasswordBody': + 'اختر كلمة مرور قوية لم تستخدمها هنا من قبل. 8 أحرف على الأقل.', + 'login.resetPasswordMfaBody': + 'أدخل رمز 2FA أو رمز النسخ الاحتياطي لإتمام إعادة التعيين.', + 'login.resetPasswordSubmit': 'إعادة تعيين كلمة المرور', + 'login.resetPasswordVerify': 'تحقق وأعد التعيين', + 'login.resetPasswordSuccessTitle': 'تم تحديث كلمة المرور', + 'login.resetPasswordSuccessBody': + 'يمكنك الآن تسجيل الدخول بكلمة المرور الجديدة.', + 'login.resetPasswordInvalidLink': 'رابط إعادة تعيين غير صالح', + 'login.resetPasswordInvalidLinkBody': + 'هذا الرابط مفقود أو تالف. اطلب رابطًا جديدًا للمتابعة.', + 'login.resetPasswordFailed': 'فشلت إعادة التعيين. ربما انتهت صلاحية الرابط.', +}; +export default login; diff --git a/shared/src/i18n/ar/map.ts b/shared/src/i18n/ar/map.ts new file mode 100644 index 00000000..04eb21c9 --- /dev/null +++ b/shared/src/i18n/ar/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'الاتصالات', + 'map.showConnections': 'عرض مسارات الحجوزات', + 'map.hideConnections': 'إخفاء مسارات الحجوزات', +}; +export default map; diff --git a/shared/src/i18n/ar/members.ts b/shared/src/i18n/ar/members.ts new file mode 100644 index 00000000..4048da13 --- /dev/null +++ b/shared/src/i18n/ar/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'مشاركة الرحلة', + 'members.inviteUser': 'دعوة مستخدم', + 'members.selectUser': 'اختر مستخدمًا…', + 'members.invite': 'دعوة', + 'members.allHaveAccess': 'جميع المستخدمين لديهم صلاحية الوصول بالفعل.', + 'members.access': 'الصلاحية', + 'members.person': 'شخص', + 'members.persons': 'أشخاص', + 'members.you': 'أنت', + 'members.owner': 'المالك', + 'members.leaveTrip': 'مغادرة الرحلة', + 'members.removeAccess': 'إزالة الصلاحية', + 'members.confirmLeave': 'مغادرة الرحلة؟ ستفقد صلاحية الوصول.', + 'members.confirmRemove': 'إزالة صلاحية هذا المستخدم؟', + 'members.loadError': 'فشل تحميل الأعضاء', + 'members.added': 'تمت الإضافة', + 'members.addError': 'فشلت الإضافة', + 'members.removed': 'تمت إزالة العضو', + 'members.removeError': 'فشلت الإزالة', +}; +export default members; diff --git a/shared/src/i18n/ar/memories.ts b/shared/src/i18n/ar/memories.ts new file mode 100644 index 00000000..34530ab1 --- /dev/null +++ b/shared/src/i18n/ar/memories.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'صور', + 'memories.notConnected': 'Immich غير متصل', + 'memories.notConnectedHint': + 'قم بتوصيل Immich في الإعدادات لعرض صور رحلتك هنا.', + 'memories.notConnectedMultipleHint': + 'قم بتوصيل أحد موفري الصور هؤلاء: {provider_names} في الإعدادات لتتمكن من إضافة صور إلى هذه الرحلة.', + 'memories.noDates': 'أضف تواريخ لرحلتك لتحميل الصور.', + 'memories.noPhotos': 'لم يتم العثور على صور', + 'memories.noPhotosHint': 'لم يتم العثور على صور في Immich لفترة هذه الرحلة.', + 'memories.photosFound': 'صور', + 'memories.fromOthers': 'من آخرين', + 'memories.sharePhotos': 'مشاركة الصور', + 'memories.sharing': 'مشترك', + 'memories.reviewTitle': 'مراجعة صورك', + 'memories.reviewHint': 'انقر على الصور لاستبعادها من المشاركة.', + 'memories.shareCount': 'مشاركة {count} صور', + 'memories.providerUrl': 'عنوان URL للخادم', + 'memories.providerApiKey': 'مفتاح API', + 'memories.providerUsername': 'اسم المستخدم', + 'memories.providerPassword': 'كلمة المرور', + 'memories.providerOTP': 'رمز MFA (إذا كان مفعلاً)', + 'memories.skipSSLVerification': 'تخطي التحقق من شهادة SSL', + 'memories.immichAutoUpload': 'نسخ صور الرحلة إلى Immich عند الرفع', + 'memories.providerUrlHintSynology': + 'أدرج مسار تطبيق Photos في URL، مثل https://nas:5001/photo', + 'memories.testConnection': 'اختبار الاتصال', + 'memories.testShort': 'اختبار', + 'memories.testFirst': 'اختبر الاتصال أولاً', + 'memories.connected': 'متصل', + 'memories.disconnected': 'غير متصل', + 'memories.connectionSuccess': 'تم الاتصال بـ Immich', + 'memories.connectionError': 'تعذر الاتصال بـ Immich', + 'memories.saved': 'تم حفظ إعدادات {provider_name}', + 'memories.providerDisconnectedBanner': + 'اتصالك بـ {provider_name} مفقود. أعد الاتصال في الإعدادات لعرض الصور.', + 'memories.saveError': 'تعذّر حفظ إعدادات {provider_name}', + 'memories.addPhotos': 'إضافة صور', + 'memories.linkAlbum': 'ربط ألبوم', + 'memories.selectAlbum': 'اختيار ألبوم Immich', + 'memories.selectAlbumMultiple': 'اختيار ألبوم', + 'memories.noAlbums': 'لم يتم العثور على ألبومات', + 'memories.syncAlbum': 'مزامنة الألبوم', + 'memories.unlinkAlbum': 'إلغاء الربط', + 'memories.photos': 'صور', + 'memories.selectPhotos': 'اختيار صور من Immich', + 'memories.selectPhotosMultiple': 'اختيار الصور', + 'memories.selectHint': 'انقر على الصور لتحديدها.', + 'memories.selected': 'محدد', + 'memories.addSelected': 'إضافة {count} صور', + 'memories.alreadyAdded': 'تمت الإضافة', + 'memories.private': 'خاص', + 'memories.stopSharing': 'إيقاف المشاركة', + 'memories.oldest': 'الأقدم أولاً', + 'memories.newest': 'الأحدث أولاً', + 'memories.allLocations': 'جميع المواقع', + 'memories.tripDates': 'تواريخ الرحلة', + 'memories.allPhotos': 'جميع الصور', + 'memories.confirmShareTitle': 'مشاركة مع أعضاء الرحلة؟', + 'memories.confirmShareHint': + '{count} صور ستكون مرئية لجميع أعضاء هذه الرحلة. يمكنك جعل الصور الفردية خاصة لاحقًا.', + 'memories.confirmShareButton': 'مشاركة الصور', + 'memories.error.loadAlbums': 'فشل تحميل الألبومات', + 'memories.error.linkAlbum': 'فشل ربط الألبوم', + 'memories.error.unlinkAlbum': 'فشل إلغاء ربط الألبوم', + 'memories.error.syncAlbum': 'فشل مزامنة الألبوم', + 'memories.error.loadPhotos': 'فشل تحميل الصور', + 'memories.error.addPhotos': 'فشل إضافة الصور', + 'memories.error.removePhoto': 'فشل حذف الصورة', + 'memories.error.toggleSharing': 'فشل تحديث إعدادات المشاركة', + 'memories.saveRouteNotConfigured': 'مسار الحفظ غير مهيأ لهذا المزود', + 'memories.testRouteNotConfigured': 'مسار الاختبار غير مهيأ لهذا المزود', + 'memories.fillRequiredFields': 'يرجى ملء جميع الحقول المطلوبة', +}; +export default memories; diff --git a/shared/src/i18n/ar/nav.ts b/shared/src/i18n/ar/nav.ts new file mode 100644 index 00000000..8eff09f5 --- /dev/null +++ b/shared/src/i18n/ar/nav.ts @@ -0,0 +1,15 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'الرحلة', + 'nav.share': 'مشاركة', + 'nav.settings': 'الإعدادات', + 'nav.admin': 'الإدارة', + 'nav.logout': 'تسجيل الخروج', + 'nav.lightMode': 'الوضع الفاتح', + 'nav.darkMode': 'الوضع الداكن', + 'nav.autoMode': 'الوضع التلقائي', + 'nav.administrator': 'المسؤول', + 'nav.myTrips': 'رحلاتي', +}; +export default nav; diff --git a/shared/src/i18n/ar/notif.ts b/shared/src/i18n/ar/notif.ts new file mode 100644 index 00000000..4451db30 --- /dev/null +++ b/shared/src/i18n/ar/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[اختبار] إشعار', + 'notif.test.simple.text': 'هذا إشعار اختبار بسيط.', + 'notif.test.boolean.text': 'هل تقبل هذا الإشعار الاختباري؟', + 'notif.test.navigate.text': 'انقر أدناه للانتقال إلى لوحة التحكم.', + 'notif.trip_invite.title': 'دعوة للرحلة', + 'notif.trip_invite.text': '{actor} دعاك إلى {trip}', + 'notif.booking_change.title': 'تم تحديث الحجز', + 'notif.booking_change.text': '{actor} حدّث حجزاً في {trip}', + 'notif.trip_reminder.title': 'تذكير بالرحلة', + 'notif.trip_reminder.text': 'رحلتك {trip} تقترب!', + 'notif.todo_due.title': 'مهمة مستحقة', + 'notif.todo_due.text': '{todo} في {trip} مستحقة في {due}', + 'notif.vacay_invite.title': 'دعوة دمج الإجازة', + 'notif.vacay_invite.text': '{actor} يدعوك لدمج خطط الإجازة', + 'notif.photos_shared.title': 'تمت مشاركة الصور', + 'notif.photos_shared.text': '{actor} شارك {count} صورة في {trip}', + 'notif.collab_message.title': 'رسالة جديدة', + 'notif.collab_message.text': '{actor} أرسل رسالة في {trip}', + 'notif.packing_tagged.title': 'مهمة التعبئة', + 'notif.packing_tagged.text': '{actor} عيّنك في {category} في {trip}', + 'notif.version_available.title': 'إصدار جديد متاح', + 'notif.version_available.text': 'TREK {version} متاح الآن', + 'notif.action.view_trip': 'عرض الرحلة', + 'notif.action.view_collab': 'عرض الرسائل', + 'notif.action.view_packing': 'عرض التعبئة', + 'notif.action.view_photos': 'عرض الصور', + 'notif.action.view_vacay': 'عرض Vacay', + 'notif.action.view_admin': 'الذهاب للإدارة', + 'notif.action.view': 'عرض', + 'notif.action.accept': 'قبول', + 'notif.action.decline': 'رفض', + 'notif.generic.title': 'إشعار', + 'notif.generic.text': 'لديك إشعار جديد', + 'notif.dev.unknown_event.title': '[DEV] حدث غير معروف', + 'notif.dev.unknown_event.text': + 'نوع الحدث "{event}" غير مسجل في EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/ar/notifications.ts b/shared/src/i18n/ar/notifications.ts new file mode 100644 index 00000000..8e333277 --- /dev/null +++ b/shared/src/i18n/ar/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'الإشعارات', + 'notifications.markAllRead': 'تحديد الكل كمقروء', + 'notifications.deleteAll': 'حذف الكل', + 'notifications.showAll': 'عرض جميع الإشعارات', + 'notifications.empty': 'لا توجد إشعارات', + 'notifications.emptyDescription': 'لقد اطلعت على كل شيء!', + 'notifications.all': 'الكل', + 'notifications.unreadOnly': 'غير مقروء', + 'notifications.markRead': 'تحديد كمقروء', + 'notifications.markUnread': 'تحديد كغير مقروء', + 'notifications.delete': 'حذف', + 'notifications.system': 'النظام', + 'notifications.synologySessionCleared.title': 'تم قطع اتصال Synology Photos', + 'notifications.synologySessionCleared.text': + 'تغير خادمك أو حسابك — انتقل إلى الإعدادات لاختبار اتصالك مرة أخرى.', + 'notifications.versionAvailable.title': 'تحديث متاح', + 'notifications.versionAvailable.text': 'TREK {version} متاح الآن.', + 'notifications.versionAvailable.button': 'عرض التفاصيل', + 'notifications.test.title': 'إشعار تجريبي من {actor}', + 'notifications.test.text': 'هذا إشعار تجريبي بسيط.', + 'notifications.test.booleanTitle': 'يطلب منك {actor} الموافقة', + 'notifications.test.booleanText': 'إشعار تجريبي يتطلب إجابة.', + 'notifications.test.accept': 'موافقة', + 'notifications.test.decline': 'رفض', + 'notifications.test.navigateTitle': 'تحقق من شيء ما', + 'notifications.test.navigateText': 'إشعار تجريبي للتنقل.', + 'notifications.test.goThere': 'اذهب إلى هناك', + 'notifications.test.adminTitle': 'إذاعة المسؤول', + 'notifications.test.adminText': + 'أرسل {actor} إشعاراً تجريبياً لجميع المسؤولين.', + 'notifications.test.tripTitle': 'نشر {actor} في رحلتك', + 'notifications.test.tripText': 'إشعار تجريبي للرحلة "{trip}".', +}; +export default notifications; diff --git a/shared/src/i18n/ar/oauth.ts b/shared/src/i18n/ar/oauth.ts new file mode 100644 index 00000000..ea117dae --- /dev/null +++ b/shared/src/i18n/ar/oauth.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'الرحلات', + 'oauth.scope.group.places': 'الأماكن', + 'oauth.scope.group.packing': 'الأمتعة', + 'oauth.scope.group.todos': 'المهام', + 'oauth.scope.group.budget': 'الميزانية', + 'oauth.scope.group.reservations': 'الحجوزات', + 'oauth.scope.group.collab': 'التعاون', + 'oauth.scope.group.notifications': 'الإشعارات', + 'oauth.scope.group.vacay': 'الإجازة', + 'oauth.scope.group.weather': 'الطقس', + 'oauth.scope.group.journey': 'مذكرة السفر', + 'oauth.scope.trips:read.label': 'عرض الرحلات وخطط السفر', + 'oauth.scope.trips:read.description': + 'قراءة الرحلات والأيام والملاحظات والأعضاء', + 'oauth.scope.trips:write.label': 'تحرير الرحلات وخطط السفر', + 'oauth.scope.trips:write.description': + 'إنشاء وتحديث الرحلات والأيام والملاحظات وإدارة الأعضاء', + 'oauth.scope.trips:delete.label': 'حذف الرحلات', + 'oauth.scope.trips:delete.description': + 'حذف الرحلات بأكملها نهائياً — هذا الإجراء لا يمكن التراجع عنه', + 'oauth.scope.trips:share.label': 'إدارة روابط المشاركة', + 'oauth.scope.trips:share.description': + 'إنشاء روابط مشاركة عامة وتحديثها وإلغاؤها', + 'oauth.scope.places:read.label': 'عرض الأماكن وبيانات الخريطة', + 'oauth.scope.places:read.description': + 'قراءة الأماكن وتعيينات الأيام والعلامات والفئات', + 'oauth.scope.places:write.label': 'إدارة الأماكن', + 'oauth.scope.places:write.description': + 'إنشاء وتحديث وحذف الأماكن والتعيينات والعلامات', + 'oauth.scope.atlas:read.label': 'عرض Atlas', + 'oauth.scope.atlas:read.description': + 'قراءة الدول والمناطق المزارة وقائمة الأمنيات', + 'oauth.scope.atlas:write.label': 'إدارة Atlas', + 'oauth.scope.atlas:write.description': + 'تعليم الدول والمناطق كمزارة، وإدارة قائمة الأمنيات', + 'oauth.scope.packing:read.label': 'عرض قوائم الأمتعة', + 'oauth.scope.packing:read.description': + 'قراءة عناصر الأمتعة والحقائب ومُسنَدي الفئات', + 'oauth.scope.packing:write.label': 'إدارة قوائم الأمتعة', + 'oauth.scope.packing:write.description': + 'إضافة وتحديث وحذف وتبديل وإعادة ترتيب عناصر الأمتعة والحقائب', + 'oauth.scope.todos:read.label': 'عرض قوائم المهام', + 'oauth.scope.todos:read.description': 'قراءة مهام الرحلة ومُسنَدي الفئات', + 'oauth.scope.todos:write.label': 'إدارة قوائم المهام', + 'oauth.scope.todos:write.description': + 'إنشاء وتحديث وتبديل وحذف وإعادة ترتيب المهام', + 'oauth.scope.budget:read.label': 'عرض الميزانية', + 'oauth.scope.budget:read.description': 'قراءة بنود الميزانية وتفاصيل النفقات', + 'oauth.scope.budget:write.label': 'إدارة الميزانية', + 'oauth.scope.budget:write.description': 'إنشاء وتحديث وحذف بنود الميزانية', + 'oauth.scope.reservations:read.label': 'عرض الحجوزات', + 'oauth.scope.reservations:read.description': 'قراءة الحجوزات وتفاصيل الإقامة', + 'oauth.scope.reservations:write.label': 'إدارة الحجوزات', + 'oauth.scope.reservations:write.description': + 'إنشاء وتحديث وحذف وإعادة ترتيب الحجوزات', + 'oauth.scope.collab:read.label': 'عرض التعاون', + 'oauth.scope.collab:read.description': + 'قراءة ملاحظات التعاون والاستطلاعات والرسائل', + 'oauth.scope.collab:write.label': 'إدارة التعاون', + 'oauth.scope.collab:write.description': + 'إنشاء وتحديث وحذف الملاحظات والاستطلاعات والرسائل التعاونية', + 'oauth.scope.notifications:read.label': 'عرض الإشعارات', + 'oauth.scope.notifications:read.description': + 'قراءة إشعارات التطبيق وأعداد غير المقروءة', + 'oauth.scope.notifications:write.label': 'إدارة الإشعارات', + 'oauth.scope.notifications:write.description': + 'تعليم الإشعارات كمقروءة والرد عليها', + 'oauth.scope.vacay:read.label': 'عرض خطط الإجازة', + 'oauth.scope.vacay:read.description': + 'قراءة بيانات تخطيط الإجازة والإدخالات والإحصاءات', + 'oauth.scope.vacay:write.label': 'إدارة خطط الإجازة', + 'oauth.scope.vacay:write.description': + 'إنشاء وإدارة إدخالات الإجازة والعطلات وخطط الفريق', + 'oauth.scope.geo:read.label': 'الخرائط والترميز الجغرافي', + 'oauth.scope.geo:read.description': + 'البحث عن مواقع وحل عناوين الخرائط والترميز الجغرافي العكسي للإحداثيات', + 'oauth.scope.weather:read.label': 'توقعات الطقس', + 'oauth.scope.weather:read.description': + 'جلب توقعات الطقس لمواقع الرحلة وتواريخها', + 'oauth.scope.journey:read.label': 'عرض مذكرات السفر', + 'oauth.scope.journey:read.description': + 'قراءة مذكرات السفر والمدخلات وقائمة المساهمين', + 'oauth.scope.journey:write.label': 'إدارة مذكرات السفر', + 'oauth.scope.journey:write.description': + 'إنشاء مذكرات السفر وتحديثها وحذفها وإدخالاتها', + 'oauth.scope.journey:share.label': 'إدارة روابط مذكرات السفر', + 'oauth.scope.journey:share.description': + 'إنشاء روابط مشاركة عامة لمذكرات السفر وتحديثها وإلغاؤها', +}; +export default oauth; diff --git a/shared/src/i18n/ar/packing.ts b/shared/src/i18n/ar/packing.ts new file mode 100644 index 00000000..49c50350 --- /dev/null +++ b/shared/src/i18n/ar/packing.ts @@ -0,0 +1,184 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'قائمة التجهيز', + 'packing.empty': 'قائمة التجهيز فارغة', + 'packing.import': 'استيراد', + 'packing.importTitle': 'استيراد قائمة التعبئة', + 'packing.importHint': + 'عنصر واحد لكل سطر. يمكن إضافة الفئة والكمية مفصولة بفاصلة أو فاصلة منقوطة أو علامة تبويب: الاسم، الفئة، الكمية', + 'packing.importPlaceholder': + 'فرشاة أسنان\nواقي شمس، نظافة\nقمصان، ملابس، 5\nجواز سفر، مستندات', + 'packing.importCsv': 'تحميل CSV/TXT', + 'packing.importAction': 'استيراد {count}', + 'packing.importSuccess': 'تم استيراد {count} عنصر', + 'packing.importError': 'فشل الاستيراد', + 'packing.importEmpty': 'لا توجد عناصر للاستيراد', + 'packing.progress': '{packed} من {total} جُهّز ({percent}%)', + 'packing.clearChecked': 'إزالة {count} محدد', + 'packing.clearCheckedShort': 'إزالة {count}', + 'packing.suggestions': 'اقتراحات', + 'packing.suggestionsTitle': 'إضافة اقتراحات', + 'packing.allSuggested': 'تمت إضافة جميع الاقتراحات', + 'packing.allPacked': 'تم تجهيز الكل!', + 'packing.addPlaceholder': 'إضافة عنصر جديد...', + 'packing.categoryPlaceholder': 'الفئة...', + 'packing.filterAll': 'الكل', + 'packing.filterOpen': 'مفتوح', + 'packing.filterDone': 'تم', + 'packing.emptyTitle': 'قائمة التجهيز فارغة', + 'packing.emptyHint': 'أضف عناصر أو استخدم الاقتراحات', + 'packing.emptyFiltered': 'لا توجد عناصر مطابقة لهذا الفلتر', + 'packing.menuRename': 'إعادة تسمية', + 'packing.menuCheckAll': 'تحديد الكل', + 'packing.menuUncheckAll': 'إلغاء تحديد الكل', + 'packing.menuDeleteCat': 'حذف الفئة', + 'packing.noMembers': 'لا أعضاء', + 'packing.addItem': 'إضافة عنصر', + 'packing.addItemPlaceholder': 'اسم العنصر...', + 'packing.addCategory': 'إضافة فئة', + 'packing.newCategoryPlaceholder': 'اسم الفئة (مثال: ملابس)', + 'packing.applyTemplate': 'تطبيق قالب', + 'packing.template': 'قالب', + 'packing.templateApplied': 'تمت إضافة {count} عنصر من القالب', + 'packing.templateError': 'فشل تطبيق القالب', + 'packing.saveAsTemplate': 'حفظ كقالب', + 'packing.templateName': 'اسم القالب', + 'packing.templateSaved': 'تم حفظ قائمة الحقائب كقالب', + 'packing.bags': 'أمتعة', + 'packing.noBag': 'غير معيّن', + 'packing.totalWeight': 'الوزن الإجمالي', + 'packing.bagName': 'الاسم...', + 'packing.addBag': 'إضافة أمتعة', + 'packing.changeCategory': 'تغيير الفئة', + 'packing.confirm.clearChecked': 'هل تريد إزالة {count} عنصر محدد؟', + 'packing.confirm.deleteCat': 'هل تريد حذف الفئة "{name}" مع {count} عنصر؟', + 'packing.defaultCategory': 'أخرى', + 'packing.toast.saveError': 'فشل الحفظ', + 'packing.toast.deleteError': 'فشل الحذف', + 'packing.toast.renameError': 'فشلت إعادة التسمية', + 'packing.toast.addError': 'فشلت الإضافة', + 'packing.suggestions.items': [ + { + name: 'جواز السفر', + category: 'المستندات', + }, + { + name: 'بطاقة الهوية', + category: 'المستندات', + }, + { + name: 'تأمين السفر', + category: 'المستندات', + }, + { + name: 'تذاكر الطيران', + category: 'المستندات', + }, + { + name: 'بطاقة ائتمان', + category: 'المالية', + }, + { + name: 'نقد', + category: 'المالية', + }, + { + name: 'تأشيرة', + category: 'المستندات', + }, + { + name: 'قمصان', + category: 'الملابس', + }, + { + name: 'بنطلونات', + category: 'الملابس', + }, + { + name: 'ملابس داخلية', + category: 'الملابس', + }, + { + name: 'جوارب', + category: 'الملابس', + }, + { + name: 'جاكيت', + category: 'الملابس', + }, + { + name: 'ملابس نوم', + category: 'الملابس', + }, + { + name: 'ملابس سباحة', + category: 'الملابس', + }, + { + name: 'معطف مطر', + category: 'الملابس', + }, + { + name: 'أحذية مريحة', + category: 'الملابس', + }, + { + name: 'فرشاة أسنان', + category: 'أدوات العناية', + }, + { + name: 'معجون أسنان', + category: 'أدوات العناية', + }, + { + name: 'شامبو', + category: 'أدوات العناية', + }, + { + name: 'مزيل عرق', + category: 'أدوات العناية', + }, + { + name: 'واقي شمس', + category: 'أدوات العناية', + }, + { + name: 'شفرة حلاقة', + category: 'أدوات العناية', + }, + { + name: 'شاحن', + category: 'الإلكترونيات', + }, + { + name: 'بطارية محمولة', + category: 'الإلكترونيات', + }, + { + name: 'سماعات', + category: 'الإلكترونيات', + }, + { + name: 'محول سفر', + category: 'الإلكترونيات', + }, + { + name: 'كاميرا', + category: 'الإلكترونيات', + }, + { + name: 'مسكنات ألم', + category: 'الصحة', + }, + { + name: 'لاصقات جروح', + category: 'الصحة', + }, + { + name: 'مطهر', + category: 'الصحة', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/ar/pdf.ts b/shared/src/i18n/ar/pdf.ts new file mode 100644 index 00000000..3e2936ec --- /dev/null +++ b/shared/src/i18n/ar/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'خطة السفر', + 'pdf.planned': 'مخطط', + 'pdf.costLabel': 'التكلفة EUR', + 'pdf.preview': 'معاينة PDF', + 'pdf.saveAsPdf': 'حفظ كـ PDF', +}; +export default pdf; diff --git a/shared/src/i18n/ar/perm.ts b/shared/src/i18n/ar/perm.ts new file mode 100644 index 00000000..3f658a55 --- /dev/null +++ b/shared/src/i18n/ar/perm.ts @@ -0,0 +1,56 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'إعدادات الصلاحيات', + 'perm.subtitle': 'التحكم في من يمكنه تنفيذ الإجراءات عبر التطبيق', + 'perm.saved': 'تم حفظ إعدادات الصلاحيات', + 'perm.resetDefaults': 'إعادة التعيين إلى الافتراضي', + 'perm.customized': 'مخصص', + 'perm.level.admin': 'المسؤول فقط', + 'perm.level.tripOwner': 'مالك الرحلة', + 'perm.level.tripMember': 'أعضاء الرحلة', + 'perm.level.everybody': 'الجميع', + 'perm.cat.trip': 'إدارة الرحلات', + 'perm.cat.members': 'إدارة الأعضاء', + 'perm.cat.files': 'الملفات', + 'perm.cat.content': 'المحتوى والجدول الزمني', + 'perm.cat.extras': 'الميزانية والتعبئة والتعاون', + 'perm.action.trip_create': 'إنشاء رحلات', + 'perm.action.trip_edit': 'تعديل تفاصيل الرحلة', + 'perm.action.trip_delete': 'حذف الرحلات', + 'perm.action.trip_archive': 'أرشفة / إلغاء أرشفة الرحلات', + 'perm.action.trip_cover_upload': 'رفع صورة الغلاف', + 'perm.action.member_manage': 'إضافة / إزالة الأعضاء', + 'perm.action.file_upload': 'رفع الملفات', + 'perm.action.file_edit': 'تعديل بيانات الملف', + 'perm.action.file_delete': 'حذف الملفات', + 'perm.action.place_edit': 'إضافة / تعديل / حذف الأماكن', + 'perm.action.day_edit': 'تعديل الأيام والملاحظات والتعيينات', + 'perm.action.reservation_edit': 'إدارة الحجوزات', + 'perm.action.budget_edit': 'إدارة الميزانية', + 'perm.action.packing_edit': 'إدارة قوائم التعبئة', + 'perm.action.collab_edit': 'التعاون (ملاحظات، استطلاعات، دردشة)', + 'perm.action.share_manage': 'إدارة روابط المشاركة', + 'perm.actionHint.trip_create': 'من يمكنه إنشاء رحلات جديدة', + 'perm.actionHint.trip_edit': + 'من يمكنه تغيير اسم الرحلة والتواريخ والوصف والعملة', + 'perm.actionHint.trip_delete': 'من يمكنه حذف رحلة نهائياً', + 'perm.actionHint.trip_archive': 'من يمكنه أرشفة أو إلغاء أرشفة رحلة', + 'perm.actionHint.trip_cover_upload': 'من يمكنه رفع أو تغيير صورة الغلاف', + 'perm.actionHint.member_manage': 'من يمكنه دعوة أو إزالة أعضاء الرحلة', + 'perm.actionHint.file_upload': 'من يمكنه رفع ملفات إلى رحلة', + 'perm.actionHint.file_edit': 'من يمكنه تعديل أوصاف الملفات والروابط', + 'perm.actionHint.file_delete': + 'من يمكنه نقل الملفات إلى سلة المهملات أو حذفها نهائياً', + 'perm.actionHint.place_edit': 'من يمكنه إضافة أو تعديل أو حذف الأماكن', + 'perm.actionHint.day_edit': + 'من يمكنه تعديل الأيام وملاحظات الأيام وتعيينات الأماكن', + 'perm.actionHint.reservation_edit': 'من يمكنه إنشاء أو تعديل أو حذف الحجوزات', + 'perm.actionHint.budget_edit': + 'من يمكنه إنشاء أو تعديل أو حذف عناصر الميزانية', + 'perm.actionHint.packing_edit': 'من يمكنه إدارة عناصر التعبئة والحقائب', + 'perm.actionHint.collab_edit': + 'من يمكنه إنشاء ملاحظات واستطلاعات وإرسال رسائل', + 'perm.actionHint.share_manage': 'من يمكنه إنشاء أو حذف روابط المشاركة العامة', +}; +export default perm; diff --git a/shared/src/i18n/ar/photos.ts b/shared/src/i18n/ar/photos.ts new file mode 100644 index 00000000..f969fcfa --- /dev/null +++ b/shared/src/i18n/ar/photos.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'صور', + 'photos.subtitle': '{count} صورة لـ {trip}', + 'photos.dropHere': 'أسقط الصور هنا...', + 'photos.dropHereActive': 'أسقط الصور هنا', + 'photos.captionForAll': 'تعليق (للجميع)', + 'photos.captionPlaceholder': 'تعليق اختياري...', + 'photos.addCaption': 'إضافة تعليق...', + 'photos.allDays': 'كل الأيام', + 'photos.noPhotos': 'لا توجد صور بعد', + 'photos.uploadHint': 'ارفع صور رحلتك', + 'photos.clickToSelect': 'أو انقر للاختيار', + 'photos.linkPlace': 'ربط بمكان', + 'photos.noPlace': 'بلا مكان', + 'photos.uploadN': 'رفع {n} صورة', + 'photos.linkDay': 'ربط اليوم', + 'photos.noDay': 'لا يوم', + 'photos.dayLabel': 'اليوم {number}', + 'photos.photoSelected': 'صورة محددة', + 'photos.photosSelected': 'صور محددة', + 'photos.fileTypeHint': + 'JPG, PNG, WebP · الحد الأقصى 10 ميغابايت · حتى 30 صورة', +}; +export default photos; diff --git a/shared/src/i18n/ar/places.ts b/shared/src/i18n/ar/places.ts new file mode 100644 index 00000000..2f7a6820 --- /dev/null +++ b/shared/src/i18n/ar/places.ts @@ -0,0 +1,90 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'إضافة مكان/نشاط', + 'places.importFile': 'استيراد ملف', + 'places.sidebarDrop': 'أفلت للاستيراد', + 'places.importFileHint': + 'استورد ملفات .gpx أو .kml أو .kmz من أدوات مثل Google My Maps وGoogle Earth أو جهاز تتبع GPS.', + 'places.importFileDropHere': 'انقر لاختيار ملف أو اسحبه وأفلته هنا', + 'places.importFileDropActive': 'أفلت الملف للاختيار', + 'places.importFileUnsupported': + 'نوع الملف غير مدعوم. استخدم .gpx أو .kml أو .kmz.', + 'places.importFileTooLarge': + 'الملف كبير جدًا. الحد الأقصى لحجم الرفع هو {maxMb} MB.', + 'places.importFileError': 'فشل الاستيراد', + 'places.importAllSkipped': 'جميع الأماكن موجودة بالفعل في الرحلة.', + 'places.gpxImported': 'تم استيراد {count} مكان من GPX', + 'places.gpxImportTypes': 'ما الذي تريد استيراده؟', + 'places.gpxImportWaypoints': 'نقاط الطريق', + 'places.gpxImportRoutes': 'المسارات', + 'places.gpxImportTracks': 'المسارات (مع هندسة الطريق)', + 'places.gpxImportNoneSelected': 'اختر نوعاً واحداً على الأقل للاستيراد.', + 'places.kmlImportTypes': 'ما الذي تريد استيراده؟', + 'places.kmlImportPoints': 'نقاط (Placemarks)', + 'places.kmlImportPaths': 'مسارات (LineStrings)', + 'places.kmlImportNoneSelected': 'اختر نوعًا واحدًا على الأقل.', + 'places.selectionCount': '{count} محدد', + 'places.deleteSelected': 'حذف المحدد', + 'places.kmlKmzImported': 'تم استيراد {count} مكان من KMZ/KML', + 'places.urlResolved': 'تم استيراد المكان من الرابط', + 'places.importList': 'استيراد قائمة', + 'places.kmlKmzSummaryValues': + 'علامات المواضع: {total} • تم الاستيراد: {created} • تم التجاوز: {skipped}', + 'places.importGoogleList': 'قائمة Google', + 'places.importNaverList': 'قائمة Naver', + 'places.googleListHint': + 'الصق رابط قائمة Google Maps المشتركة لاستيراد جميع الأماكن.', + 'places.googleListImported': 'تم استيراد {count} أماكن من "{list}"', + 'places.googleListError': 'فشل استيراد قائمة Google Maps', + 'places.naverListHint': + 'الصق رابط قائمة Naver Maps مشتركة لاستيراد جميع الأماكن.', + 'places.naverListImported': 'تم استيراد {count} مكان من "{list}"', + 'places.naverListError': 'فشل استيراد قائمة Naver Maps', + 'places.viewDetails': 'عرض التفاصيل', + 'places.assignToDay': 'إلى أي يوم تريد الإضافة؟', + 'places.all': 'الكل', + 'places.unplanned': 'غير مخطط', + 'places.filterTracks': 'المسارات', + 'places.search': 'ابحث عن أماكن...', + 'places.allCategories': 'كل الفئات', + 'places.categoriesSelected': 'فئات', + 'places.clearFilter': 'مسح الفلتر', + 'places.count': '{count} أماكن', + 'places.countSingular': 'مكان واحد', + 'places.allPlanned': 'تم تخطيط جميع الأماكن', + 'places.noneFound': 'لم يتم العثور على أماكن', + 'places.editPlace': 'تعديل المكان', + 'places.formName': 'الاسم', + 'places.formNamePlaceholder': 'مثال: برج إيفل', + 'places.formDescription': 'الوصف', + 'places.formDescriptionPlaceholder': 'وصف مختصر...', + 'places.formAddress': 'العنوان', + 'places.formAddressPlaceholder': 'الشارع، المدينة، البلد', + 'places.formLat': 'خط العرض (مثال: 48.8566)', + 'places.formLng': 'خط الطول (مثال: 2.3522)', + 'places.formCategory': 'الفئة', + 'places.noCategory': 'بلا فئة', + 'places.categoryNamePlaceholder': 'اسم الفئة', + 'places.formTime': 'الوقت', + 'places.startTime': 'البداية', + 'places.endTime': 'النهاية', + 'places.endTimeBeforeStart': 'وقت النهاية قبل وقت البداية', + 'places.timeCollision': 'تداخل في الوقت مع:', + 'places.formWebsite': 'الموقع الإلكتروني', + 'places.formNotes': 'ملاحظات', + 'places.formNotesPlaceholder': 'ملاحظات شخصية...', + 'places.formReservation': 'حجز', + 'places.reservationNotesPlaceholder': 'ملاحظات الحجز، رقم التأكيد...', + 'places.mapsSearchPlaceholder': 'ابحث عن أماكن...', + 'places.mapsSearchError': 'فشل البحث عن المكان.', + 'places.loadingDetails': 'جارٍ تحميل تفاصيل المكان…', + 'places.osmHint': + 'يتم البحث عبر OpenStreetMap (بدون صور أو ساعات عمل أو تقييمات). أضف مفتاح Google API في الإعدادات للحصول على جميع التفاصيل.', + 'places.osmActive': + 'البحث عبر OpenStreetMap (بدون صور أو تقييمات أو ساعات عمل). أضف مفتاح Google API في الإعدادات لبيانات موسعة.', + 'places.categoryCreateError': 'فشل إنشاء الفئة', + 'places.nameRequired': 'يرجى إدخال اسم', + 'places.saveError': 'فشل الحفظ', +}; +export default places; diff --git a/shared/src/i18n/ar/planner.ts b/shared/src/i18n/ar/planner.ts new file mode 100644 index 00000000..40d7e3af --- /dev/null +++ b/shared/src/i18n/ar/planner.ts @@ -0,0 +1,67 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'الأماكن', + 'planner.bookings': 'الحجوزات', + 'planner.packingList': 'قائمة التجهيز', + 'planner.documents': 'المستندات', + 'planner.dayPlan': 'خطة اليوم', + 'planner.reservations': 'الحجوزات', + 'planner.minTwoPlaces': 'يلزم مكانان على الأقل مع إحداثيات', + 'planner.noGeoPlaces': 'لا توجد أماكن بإحداثيات', + 'planner.routeCalculated': 'تم حساب المسار', + 'planner.routeCalcFailed': 'تعذر حساب المسار', + 'planner.routeError': 'خطأ أثناء حساب المسار', + 'planner.icsExportFailed': 'فشل تصدير ICS', + 'planner.routeOptimized': 'تم تحسين المسار', + 'planner.reservationUpdated': 'تم تحديث الحجز', + 'planner.reservationAdded': 'تمت إضافة الحجز', + 'planner.confirmDeleteReservation': 'حذف الحجز؟', + 'planner.reservationDeleted': 'تم حذف الحجز', + 'planner.days': 'الأيام', + 'planner.allPlaces': 'كل الأماكن', + 'planner.totalPlaces': 'إجمالي {n} أماكن', + 'planner.noDaysPlanned': 'لا توجد أيام مخططة بعد', + 'planner.editTrip': 'تعديل الرحلة ←', + 'planner.placeOne': 'مكان واحد', + 'planner.placeN': '{n} أماكن', + 'planner.addNote': 'إضافة ملاحظة', + 'planner.noEntries': 'لا توجد عناصر لهذا اليوم', + 'planner.addPlace': 'إضافة مكان/نشاط', + 'planner.addPlaceShort': '+ إضافة مكان/نشاط', + 'planner.resPending': 'حجز قيد الانتظار · ', + 'planner.resConfirmed': 'حجز مؤكد · ', + 'planner.notePlaceholder': 'ملاحظة…', + 'planner.noteTimePlaceholder': 'الوقت (اختياري)', + 'planner.noteExamplePlaceholder': + 'مثال: S3 الساعة 14:30 من المحطة المركزية، عبّارة من الرصيف 7، استراحة غداء…', + 'planner.totalCost': 'إجمالي التكلفة', + 'planner.searchPlaces': 'ابحث عن أماكن…', + 'planner.allCategories': 'كل الفئات', + 'planner.noPlacesFound': 'لم يتم العثور على أماكن', + 'planner.addFirstPlace': 'أضف أول مكان', + 'planner.noReservations': 'لا توجد حجوزات', + 'planner.addFirstReservation': 'أضف أول حجز', + 'planner.new': 'جديد', + 'planner.addToDay': '+ يوم', + 'planner.calculating': 'جارٍ الحساب…', + 'planner.route': 'المسار', + 'planner.optimize': 'تحسين', + 'planner.openGoogleMaps': 'فتح في Google Maps', + 'planner.selectDayHint': 'اختر يومًا من القائمة اليسرى لعرض خطة اليوم', + 'planner.noPlacesForDay': 'لا توجد أماكن لهذا اليوم بعد', + 'planner.addPlacesLink': 'إضافة أماكن ←', + 'planner.minTotal': 'دقيقة إجمالًا', + 'planner.noReservation': 'لا يوجد حجز', + 'planner.removeFromDay': 'إزالة من اليوم', + 'planner.addToThisDay': 'إضافة إلى اليوم', + 'planner.overview': 'نظرة عامة', + 'planner.noDays': 'لا توجد أيام بعد', + 'planner.editTripToAddDays': 'عدّل الرحلة لإضافة أيام', + 'planner.dayCount': '{n} أيام', + 'planner.clickToUnlock': 'انقر لفتح القفل', + 'planner.keepPosition': 'الحفاظ على الموضع أثناء تحسين المسار', + 'planner.dayDetails': 'تفاصيل اليوم', + 'planner.dayN': 'اليوم {n}', +}; +export default planner; diff --git a/shared/src/i18n/ar/register.ts b/shared/src/i18n/ar/register.ts new file mode 100644 index 00000000..76082cb4 --- /dev/null +++ b/shared/src/i18n/ar/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'كلمتا المرور غير متطابقتين', + 'register.passwordTooShort': 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', + 'register.failed': 'فشل التسجيل', + 'register.getStarted': 'ابدأ الآن', + 'register.subtitle': 'أنشئ حسابًا وابدأ التخطيط لرحلات أحلامك.', + 'register.feature1': 'خطط رحلات غير محدودة', + 'register.feature2': 'عرض خريطة تفاعلي', + 'register.feature3': 'إدارة الأماكن والفئات', + 'register.feature4': 'تتبع الحجوزات', + 'register.feature5': 'إنشاء قوائم تجهيز', + 'register.feature6': 'حفظ الصور والملفات', + 'register.createAccount': 'إنشاء حساب', + 'register.startPlanning': 'ابدأ تخطيط رحلتك', + 'register.minChars': '6 أحرف على الأقل', + 'register.confirmPassword': 'تأكيد كلمة المرور', + 'register.repeatPassword': 'إعادة كلمة المرور', + 'register.registering': 'جارٍ التسجيل...', + 'register.register': 'تسجيل', + 'register.hasAccount': 'لديك حساب بالفعل؟', + 'register.signIn': 'تسجيل الدخول', +}; +export default register; diff --git a/shared/src/i18n/ar/reservations.ts b/shared/src/i18n/ar/reservations.ts new file mode 100644 index 00000000..cecb9f5f --- /dev/null +++ b/shared/src/i18n/ar/reservations.ts @@ -0,0 +1,117 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'الحجوزات', + 'reservations.empty': 'لا توجد حجوزات بعد', + 'reservations.emptyHint': 'أضف حجوزات للرحلات الجوية والفنادق وغير ذلك', + 'reservations.add': 'إضافة حجز', + 'reservations.addManual': 'حجز يدوي', + 'reservations.placeHint': + 'نصيحة: يُفضل إنشاء الحجوزات مباشرة من مكان لربطها بخطة اليوم.', + 'reservations.confirmed': 'مؤكد', + 'reservations.pending': 'قيد الانتظار', + 'reservations.summary': '{confirmed} مؤكدة، {pending} قيد الانتظار', + 'reservations.fromPlan': 'من الخطة', + 'reservations.showFiles': 'عرض الملفات', + 'reservations.editTitle': 'تعديل الحجز', + 'reservations.status': 'الحالة', + 'reservations.datetime': 'التاريخ والوقت', + 'reservations.startTime': 'وقت البداية', + 'reservations.endTime': 'وقت النهاية', + 'reservations.date': 'التاريخ', + 'reservations.time': 'الوقت', + 'reservations.timeAlt': 'الوقت (بديل، مثل 19:30)', + 'reservations.notes': 'ملاحظات', + 'reservations.notesPlaceholder': 'ملاحظات إضافية...', + 'reservations.meta.airline': 'شركة الطيران', + 'reservations.meta.flightNumber': 'رقم الرحلة', + 'reservations.meta.from': 'من', + 'reservations.meta.to': 'إلى', + 'reservations.needsReview': 'مراجعة', + 'reservations.needsReviewHint': + 'تعذّر مطابقة المطار تلقائياً — يرجى تأكيد الموقع.', + 'reservations.searchLocation': 'ابحث عن محطة، ميناء، عنوان...', + 'reservations.meta.trainNumber': 'رقم القطار', + 'reservations.meta.platform': 'المنصة', + 'reservations.meta.seat': 'المقعد', + 'reservations.meta.checkIn': 'تسجيل الوصول', + 'reservations.meta.checkInUntil': 'تسجيل الدخول حتى', + 'reservations.meta.checkOut': 'تسجيل المغادرة', + 'reservations.meta.linkAccommodation': 'الإقامة', + 'reservations.meta.pickAccommodation': 'ربط بالإقامة', + 'reservations.meta.noAccommodation': 'لا يوجد', + 'reservations.meta.hotelPlace': 'الإقامة', + 'reservations.meta.pickHotel': 'اختر الإقامة', + 'reservations.meta.fromDay': 'من', + 'reservations.meta.toDay': 'إلى', + 'reservations.meta.selectDay': 'اختر يومًا', + 'reservations.type.flight': 'رحلة جوية', + 'reservations.type.hotel': 'إقامة', + 'reservations.type.restaurant': 'مطعم', + 'reservations.type.train': 'قطار', + 'reservations.type.car': 'سيارة', + 'reservations.type.cruise': 'رحلة بحرية', + 'reservations.type.event': 'فعالية', + 'reservations.type.tour': 'جولة', + 'reservations.type.other': 'أخرى', + 'reservations.confirm.delete': 'هل تريد حذف الحجز "{name}"؟', + 'reservations.confirm.deleteTitle': 'حذف الحجز؟', + 'reservations.confirm.deleteBody': 'سيتم حذف "{name}" نهائيًا.', + 'reservations.toast.updated': 'تم تحديث الحجز', + 'reservations.toast.removed': 'تم حذف الحجز', + 'reservations.toast.fileUploaded': 'تم رفع الملف', + 'reservations.toast.uploadError': 'فشل الرفع', + 'reservations.newTitle': 'حجز جديد', + 'reservations.bookingType': 'نوع الحجز', + 'reservations.titleLabel': 'العنوان', + 'reservations.titlePlaceholder': 'مثال: Lufthansa LH123، فندق أدلون، ...', + 'reservations.locationAddress': 'الموقع / العنوان', + 'reservations.locationPlaceholder': 'العنوان، المطار، الفندق...', + 'reservations.confirmationCode': 'رمز الحجز', + 'reservations.confirmationPlaceholder': 'مثال: ABC12345', + 'reservations.day': 'اليوم', + 'reservations.noDay': 'بلا يوم', + 'reservations.place': 'المكان', + 'reservations.noPlace': 'بلا مكان', + 'reservations.pendingSave': 'سيتم الحفظ…', + 'reservations.uploading': 'جارٍ الرفع...', + 'reservations.attachFile': 'إرفاق ملف', + 'reservations.linkExisting': 'ربط ملف موجود', + 'reservations.toast.saveError': 'فشل الحفظ', + 'reservations.toast.updateError': 'فشل التحديث', + 'reservations.toast.deleteError': 'فشل الحذف', + 'reservations.confirm.remove': 'إزالة الحجز "{name}"؟', + 'reservations.linkAssignment': 'ربط بخطة اليوم', + 'reservations.pickAssignment': 'اختر عنصرًا من خطتك...', + 'reservations.noAssignment': 'بلا ربط', + 'reservations.price': 'السعر', + 'reservations.budgetCategory': 'فئة الميزانية', + 'reservations.budgetCategoryPlaceholder': 'مثال: المواصلات، الإقامة', + 'reservations.budgetCategoryAuto': 'تلقائي (حسب نوع الحجز)', + 'reservations.budgetHint': + 'سيتم إنشاء إدخال في الميزانية تلقائيًا عند الحفظ.', + 'reservations.departureDate': 'المغادرة', + 'reservations.arrivalDate': 'الوصول', + 'reservations.departureTime': 'وقت المغادرة', + 'reservations.arrivalTime': 'وقت الوصول', + 'reservations.pickupDate': 'الاستلام', + 'reservations.returnDate': 'الإرجاع', + 'reservations.pickupTime': 'وقت الاستلام', + 'reservations.returnTime': 'وقت الإرجاع', + 'reservations.endDate': 'تاريخ الانتهاء', + 'reservations.meta.departureTimezone': 'TZ المغادرة', + 'reservations.meta.arrivalTimezone': 'TZ الوصول', + 'reservations.span.departure': 'المغادرة', + 'reservations.span.arrival': 'الوصول', + 'reservations.span.inTransit': 'في الطريق', + 'reservations.span.pickup': 'الاستلام', + 'reservations.span.return': 'الإرجاع', + 'reservations.span.active': 'نشط', + 'reservations.span.start': 'البداية', + 'reservations.span.end': 'النهاية', + 'reservations.span.ongoing': 'جارٍ', + 'reservations.validation.endBeforeStart': + 'يجب أن يكون تاريخ/وقت الانتهاء بعد تاريخ/وقت البدء', + 'reservations.addBooking': 'إضافة حجز', +}; +export default reservations; diff --git a/shared/src/i18n/ar/settings.ts b/shared/src/i18n/ar/settings.ts new file mode 100644 index 00000000..fafaa676 --- /dev/null +++ b/shared/src/i18n/ar/settings.ts @@ -0,0 +1,274 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'الإعدادات', + 'settings.subtitle': 'ضبط إعداداتك الشخصية', + 'settings.tabs.display': 'العرض', + 'settings.tabs.map': 'الخريطة', + 'settings.tabs.notifications': 'الإشعارات', + 'settings.tabs.integrations': 'التكاملات', + 'settings.tabs.account': 'الحساب', + 'settings.tabs.about': 'حول', + 'settings.map': 'الخريطة', + 'settings.mapTemplate': 'قالب الخريطة', + 'settings.mapTemplatePlaceholder.select': 'اختر قالبًا...', + 'settings.mapDefaultHint': 'اتركه فارغًا لاستخدام OpenStreetMap افتراضيًا', + 'settings.mapHint': 'قالب URL لبلاطات الخريطة', + 'settings.mapProvider': 'مزود الخريطة', + 'settings.mapProviderHint': + 'يؤثر على خرائط Trip Planner و Journey. يستخدم Atlas دائمًا Leaflet.', + 'settings.mapLeafletSubtitle': '2D كلاسيكي، أي بلاطات نقطية', + 'settings.mapMapboxSubtitle': 'بلاطات متجهية ومبانٍ ثلاثية الأبعاد وتضاريس', + 'settings.mapExperimental': 'تجريبي', + 'settings.mapMapboxToken': 'رمز وصول Mapbox', + 'settings.mapMapboxTokenHint': 'الرمز العام (pk.*) من', + 'settings.mapMapboxTokenLink': 'mapbox.com ← رموز الوصول', + 'settings.mapStyle': 'نمط الخريطة', + 'settings.mapStylePlaceholder': 'اختر نمط Mapbox', + 'settings.mapStyleHint': + 'إعداد مسبق أو عنوان URL mapbox://styles/USER/ID خاص بك', + 'settings.map3dBuildings': 'مبانٍ ثلاثية الأبعاد وتضاريس', + 'settings.map3dHint': + 'إمالة + مبانٍ ثلاثية الأبعاد حقيقية — يعمل مع كل نمط بما في ذلك الأقمار الصناعية.', + 'settings.mapHighQuality': 'وضع الجودة العالية', + 'settings.mapHighQualityHint': + 'تحسين الحواف + إسقاط كروي لحواف أكثر حدة وعرض واقعي للعالم.', + 'settings.mapHighQualityWarning': 'قد يؤثر على الأداء في الأجهزة الأقل قدرة.', + 'settings.mapTipLabel': 'نصيحة:', + 'settings.mapTip': + 'انقر بزر الماوس الأيمن واسحب لتدوير/إمالة الخريطة. النقر الأوسط لإضافة مكان (النقر الأيمن مخصص للتدوير).', + 'settings.latitude': 'خط العرض', + 'settings.longitude': 'خط الطول', + 'settings.saveMap': 'حفظ الخريطة', + 'settings.apiKeys': 'مفاتيح API', + 'settings.mapsKey': 'مفتاح Google Maps API', + 'settings.mapsKeyHint': 'للبحث عن الأماكن. يتطلب Places API (New).', + 'settings.weatherKey': 'مفتاح OpenWeatherMap API', + 'settings.weatherKeyHint': 'لبيانات الطقس.', + 'settings.keyPlaceholder': 'أدخل المفتاح...', + 'settings.configured': 'مُعدّ', + 'settings.saveKeys': 'حفظ المفاتيح', + 'settings.display': 'العرض', + 'settings.colorMode': 'نمط الألوان', + 'settings.light': 'فاتح', + 'settings.dark': 'داكن', + 'settings.auto': 'تلقائي', + 'settings.language': 'اللغة', + 'settings.temperature': 'وحدة الحرارة', + 'settings.timeFormat': 'تنسيق الوقت', + 'settings.bookingLabels': 'تسميات مسارات الحجوزات', + 'settings.bookingLabelsHint': + 'عرض أسماء المحطات/المطارات على الخريطة. عند الإيقاف، يتم عرض الرمز فقط.', + 'settings.blurBookingCodes': 'إخفاء رموز الحجز', + 'settings.notifications': 'الإشعارات', + 'settings.notifyTripInvite': 'دعوات الرحلات', + 'settings.notifyBookingChange': 'تغييرات الحجز', + 'settings.notifyTripReminder': 'تذكيرات الرحلات', + 'settings.notifyTodoDue': 'مهمة مستحقة', + 'settings.notifyVacayInvite': 'دعوات دمج الإجازات', + 'settings.notifyPhotosShared': 'صور مشتركة (Immich)', + 'settings.notifyCollabMessage': 'رسائل الدردشة (Collab)', + 'settings.notifyPackingTagged': 'قائمة الأمتعة: التعيينات', + 'settings.notifyWebhook': 'إشعارات Webhook', + 'settings.notifyVersionAvailable': 'إصدار جديد متاح', + 'settings.notificationPreferences.noChannels': + 'لم يتم تكوين قنوات إشعارات. اطلب من المسؤول إعداد إشعارات البريد الإلكتروني أو webhook.', + 'settings.webhookUrl.label': 'رابط Webhook', + 'settings.webhookUrl.hint': + 'أدخل رابط Webhook الخاص بـ Discord أو Slack أو المخصص لتلقي الإشعارات.', + 'settings.webhookUrl.saved': 'تم حفظ رابط Webhook', + 'settings.webhookUrl.test': 'اختبار', + 'settings.webhookUrl.testSuccess': 'تم إرسال Webhook الاختباري بنجاح', + 'settings.webhookUrl.testFailed': 'فشل إرسال Webhook الاختباري', + 'settings.ntfyUrl.topicLabel': 'موضوع Ntfy', + 'settings.ntfyUrl.serverLabel': 'عنوان URL خادم Ntfy (اختياري)', + 'settings.ntfyUrl.hint': + 'أدخل موضوع Ntfy الخاص بك لتلقي الإشعارات الفورية. اترك حقل الخادم فارغاً لاستخدام الإعداد الافتراضي الذي حدده المسؤول.', + 'settings.ntfyUrl.tokenLabel': 'رمز الوصول (اختياري)', + 'settings.ntfyUrl.tokenHint': 'مطلوب للمواضيع المحمية بكلمة مرور.', + 'settings.ntfyUrl.saved': 'تم حفظ إعدادات Ntfy', + 'settings.ntfyUrl.test': 'اختبار', + 'settings.ntfyUrl.testSuccess': 'تم إرسال إشعار Ntfy التجريبي بنجاح', + 'settings.ntfyUrl.testFailed': 'فشل إشعار Ntfy التجريبي', + 'settings.ntfyUrl.tokenCleared': 'تم مسح رمز الوصول', + 'settings.notificationsDisabled': + 'الإشعارات غير مكوّنة. اطلب من المسؤول تفعيل إشعارات البريد الإلكتروني أو Webhook.', + 'settings.notificationsActive': 'القناة النشطة', + 'settings.notificationsManagedByAdmin': + 'يتم تكوين أحداث الإشعارات بواسطة المسؤول.', + 'settings.on': 'تشغيل', + 'settings.off': 'إيقاف', + 'settings.mcp.title': 'إعداد MCP', + 'settings.mcp.endpoint': 'نقطة نهاية MCP', + 'settings.mcp.clientConfig': 'إعداد العميل', + 'settings.mcp.clientConfigHint': + 'استبدل برمز API من القائمة أدناه. قد يحتاج مسار npx إلى ضبط وفق نظامك (مثلاً C:\\PROGRA~1\\nodejs\\npx.cmd على Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'استبدل و ببيانات الاعتماد المعروضة في عميل OAuth 2.1 الذي أنشأته أعلاه. سيفتح mcp-remote متصفحك لإتمام التفويض في أول اتصال. قد يحتاج مسار npx إلى تعديل حسب نظامك (مثال: C:PROGRA~1\nodejs\npx.cmd على Windows).', + 'settings.mcp.copy': 'نسخ', + 'settings.mcp.copied': 'تم النسخ!', + 'settings.mcp.apiTokens': 'رموز API', + 'settings.mcp.createToken': 'إنشاء رمز جديد', + 'settings.mcp.noTokens': 'لا توجد رموز بعد. أنشئ رمزاً للاتصال بعملاء MCP.', + 'settings.mcp.tokenCreatedAt': 'أُنشئ', + 'settings.mcp.tokenUsedAt': 'استُخدم', + 'settings.mcp.deleteTokenTitle': 'حذف الرمز', + 'settings.mcp.deleteTokenMessage': + 'سيتوقف هذا الرمز عن العمل فوراً. أي عميل MCP يستخدمه سيفقد الوصول.', + 'settings.mcp.modal.createTitle': 'إنشاء رمز API', + 'settings.mcp.modal.tokenName': 'اسم الرمز', + 'settings.mcp.modal.tokenNamePlaceholder': + 'مثال: Claude Desktop، حاسوب العمل', + 'settings.mcp.modal.creating': 'جارٍ الإنشاء…', + 'settings.mcp.modal.create': 'إنشاء الرمز', + 'settings.mcp.modal.createdTitle': 'تم إنشاء الرمز', + 'settings.mcp.modal.createdWarning': + 'سيُعرض هذا الرمز مرة واحدة فقط. انسخه واحفظه الآن — لا يمكن استرداده.', + 'settings.mcp.modal.done': 'تم', + 'settings.mcp.toast.created': 'تم إنشاء الرمز', + 'settings.mcp.toast.createError': 'فشل إنشاء الرمز', + 'settings.mcp.toast.deleted': 'تم حذف الرمز', + 'settings.mcp.toast.deleteError': 'فشل حذف الرمز', + 'settings.mcp.apiTokensDeprecated': + 'رموز API قديمة وستُزال في إصدار مستقبلي. يُرجى استخدام عملاء OAuth 2.1 بدلاً منها.', + 'settings.oauth.clients': 'عملاء OAuth 2.1', + 'settings.oauth.clientsHint': + 'سجّل عملاء OAuth 2.1 للسماح لتطبيقات MCP الخارجية (Claude Web وCursor وغيرها) بالاتصال دون رموز ثابتة.', + 'settings.oauth.createClient': 'عميل جديد', + 'settings.oauth.noClients': 'لا يوجد عملاء OAuth مسجلون.', + 'settings.oauth.clientId': 'معرّف العميل', + 'settings.oauth.clientSecret': 'سر العميل', + 'settings.oauth.deleteClient': 'حذف العميل', + 'settings.oauth.deleteClientMessage': + 'سيتم حذف هذا العميل وجميع الجلسات النشطة بشكل دائم. ستفقد أي تطبيق يستخدمه وصوله فوراً.', + 'settings.oauth.rotateSecret': 'تجديد السر', + 'settings.oauth.rotateSecretMessage': + 'سيتم إنشاء سر عميل جديد وإبطال جميع الجلسات الحالية فوراً. حدّث تطبيقك قبل إغلاق هذا الحوار.', + 'settings.oauth.rotateSecretConfirm': 'تجديد', + 'settings.oauth.rotateSecretConfirming': 'جارٍ التجديد…', + 'settings.oauth.rotateSecretDoneTitle': 'تم إنشاء سر جديد', + 'settings.oauth.rotateSecretDoneWarning': + 'يُعرض هذا السر مرة واحدة فقط. انسخه الآن وحدّث تطبيقك — تم إبطال جميع الجلسات السابقة.', + 'settings.oauth.activeSessions': 'جلسات OAuth النشطة', + 'settings.oauth.sessionScopes': 'النطاقات', + 'settings.oauth.sessionExpires': 'تنتهي', + 'settings.oauth.revoke': 'إلغاء', + 'settings.oauth.revokeSession': 'إلغاء الجلسة', + 'settings.oauth.revokeSessionMessage': + 'سيؤدي هذا إلى إلغاء الوصول لهذه الجلسة OAuth فوراً.', + 'settings.oauth.modal.createTitle': 'تسجيل عميل OAuth', + 'settings.oauth.modal.presets': 'إعدادات سريعة', + 'settings.oauth.modal.clientName': 'اسم التطبيق', + 'settings.oauth.modal.clientNamePlaceholder': + 'مثال: Claude Web، تطبيق MCP الخاص بي', + 'settings.oauth.modal.redirectUris': 'عناوين URI لإعادة التوجيه', + 'settings.oauth.modal.redirectUrisHint': + 'عنوان URI واحد لكل سطر. يُطلب HTTPS (localhost مستثنى). يُطبق تطابق دقيق.', + 'settings.oauth.modal.scopes': 'النطاقات المسموح بها', + 'settings.oauth.modal.scopesHint': + 'list_trips وget_trip_summary متاحان دائماً — لا يُطلب نطاق. يساعدان الذكاء الاصطناعي في اكتشاف معرّفات الرحلات.', + 'settings.oauth.modal.selectAll': 'تحديد الكل', + 'settings.oauth.modal.deselectAll': 'إلغاء تحديد الكل', + 'settings.oauth.modal.creating': 'جارٍ التسجيل…', + 'settings.oauth.modal.create': 'تسجيل العميل', + 'settings.oauth.modal.createdTitle': 'تم تسجيل العميل', + 'settings.oauth.modal.createdWarning': + 'يُعرض سر العميل مرة واحدة فقط. انسخه الآن — لا يمكن استرداده.', + 'settings.oauth.toast.createError': 'فشل تسجيل عميل OAuth', + 'settings.oauth.toast.deleted': 'تم حذف عميل OAuth', + 'settings.oauth.toast.deleteError': 'فشل حذف عميل OAuth', + 'settings.oauth.toast.revoked': 'تم إلغاء الجلسة', + 'settings.oauth.toast.revokeError': 'فشل إلغاء الجلسة', + 'settings.oauth.toast.rotateError': 'فشل تجديد سر العميل', + 'settings.oauth.modal.machineClient': + 'عميل آلي (بدون تسجيل دخول عبر المتصفح)', + 'settings.oauth.modal.machineClientHint': + 'استخدام منحة client_credentials — لا تحتاج إلى عناوين إعادة التوجيه. يُصدر الرمز المميز مباشرةً عبر client_id + client_secret ويعمل بصلاحياتك ضمن النطاقات المحددة.', + 'settings.oauth.modal.machineClientUsage': + 'للحصول على رمز مميز: POST /oauth/token مع grant_type=client_credentials وclient_id وclient_secret. بدون متصفح، بدون رمز تحديث.', + 'settings.oauth.badge.machine': 'آلي', + 'settings.account': 'الحساب', + 'settings.about': 'حول', + 'settings.about.reportBug': 'الإبلاغ عن خطأ', + 'settings.about.reportBugHint': 'وجدت مشكلة؟ أخبرنا', + 'settings.about.featureRequest': 'اقتراح ميزة', + 'settings.about.featureRequestHint': 'اقترح ميزة جديدة', + 'settings.about.wikiHint': 'التوثيق والأدلة', + 'settings.about.supporters.badge': 'الداعمون الشهريون', + 'settings.about.supporters.title': 'رفاق رحلة TREK', + 'settings.about.supporters.subtitle': + 'بينما تخطّط لمسارك التالي، يساعد هؤلاء الأشخاص في التخطيط لمستقبل TREK. تذهب مساهمتهم الشهرية مباشرةً إلى التطوير والساعات الفعلية المبذولة — حتى يظلّ TREK مفتوح المصدر.', + 'settings.about.supporters.since': 'داعم منذ {date}', + 'settings.about.supporters.tierEmpty': 'كن الأول', + 'settings.about.description': + 'TREK هو مخطط سفر مستضاف ذاتيًا يساعدك على تنظيم رحلاتك من أول فكرة حتى آخر ذكرى. تخطيط يومي، ميزانية، قوائم تعبئة، صور والمزيد — كل شيء في مكان واحد، على خادمك الخاص.', + 'settings.about.madeWith': 'صُنع بـ', + 'settings.about.madeBy': 'بواسطة موريس ومجتمع مفتوح المصدر متنامٍ.', + 'settings.username': 'اسم المستخدم', + 'settings.email': 'البريد الإلكتروني', + 'settings.role': 'الدور', + 'settings.roleAdmin': 'مسؤول', + 'settings.oidcLinked': 'مرتبط مع', + 'settings.changePassword': 'تغيير كلمة المرور', + 'settings.currentPassword': 'كلمة المرور الحالية', + 'settings.currentPasswordRequired': 'كلمة المرور الحالية مطلوبة', + 'settings.newPassword': 'كلمة المرور الجديدة', + 'settings.confirmPassword': 'تأكيد كلمة المرور الجديدة', + 'settings.updatePassword': 'تحديث كلمة المرور', + 'settings.passwordRequired': 'أدخل كلمة المرور الحالية والجديدة', + 'settings.passwordTooShort': 'يجب أن تتكون كلمة المرور من 8 أحرف على الأقل', + 'settings.passwordMismatch': 'كلمتا المرور غير متطابقتين', + 'settings.passwordWeak': + 'يجب أن تحتوي كلمة المرور على حرف كبير وحرف صغير ورقم ورمز خاص', + 'settings.passwordChanged': 'تم تغيير كلمة المرور بنجاح', + 'settings.mustChangePassword': + 'يجب عليك تغيير كلمة المرور قبل المتابعة. يرجى تعيين كلمة مرور جديدة أدناه.', + 'settings.deleteAccount': 'حذف الحساب', + 'settings.deleteAccountTitle': 'هل تريد حذف حسابك؟', + 'settings.deleteAccountWarning': + 'سيتم حذف حسابك وجميع رحلاتك وأماكنك وملفاتك نهائيًا. لا يمكن التراجع عن ذلك.', + 'settings.deleteAccountConfirm': 'حذف نهائي', + 'settings.deleteBlockedTitle': 'الحذف غير ممكن', + 'settings.deleteBlockedMessage': + 'أنت المسؤول الوحيد. قم بترقية مستخدم آخر إلى مسؤول قبل حذف حسابك.', + 'settings.roleUser': 'مستخدم', + 'settings.saveProfile': 'حفظ الملف الشخصي', + 'settings.toast.mapSaved': 'تم حفظ إعدادات الخريطة', + 'settings.toast.keysSaved': 'تم حفظ مفاتيح API', + 'settings.toast.displaySaved': 'تم حفظ إعدادات العرض', + 'settings.toast.profileSaved': 'تم حفظ الملف الشخصي', + 'settings.uploadAvatar': 'رفع صورة الملف الشخصي', + 'settings.removeAvatar': 'إزالة صورة الملف الشخصي', + 'settings.avatarUploaded': 'تم تحديث صورة الملف الشخصي', + 'settings.avatarRemoved': 'تمت إزالة صورة الملف الشخصي', + 'settings.avatarError': 'فشل الرفع', + 'settings.mfa.title': 'المصادقة الثنائية (2FA)', + 'settings.mfa.description': + 'تضيف خطوة ثانية عند تسجيل الدخول. استخدم تطبيق مصادقة (Google Authenticator، Authy، إلخ).', + 'settings.mfa.requiredByPolicy': + 'المسؤول يتطلب المصادقة الثنائية. اضبط تطبيق المصادقة أدناه قبل المتابعة.', + 'settings.mfa.backupTitle': 'رموز النسخ الاحتياطي', + 'settings.mfa.backupDescription': + 'استخدم هذه الرموز لمرة واحدة إذا فقدت الوصول إلى تطبيق المصادقة.', + 'settings.mfa.backupWarning': + 'احفظ هذه الرموز الآن. كل رمز يمكن استخدامه مرة واحدة فقط.', + 'settings.mfa.backupCopy': 'نسخ الرموز', + 'settings.mfa.backupDownload': 'تنزيل TXT', + 'settings.mfa.backupPrint': 'طباعة / PDF', + 'settings.mfa.backupCopied': 'تم نسخ رموز النسخ الاحتياطي', + 'settings.mfa.enabled': 'المصادقة الثنائية مفعّلة على حسابك.', + 'settings.mfa.disabled': 'المصادقة الثنائية غير مفعّلة.', + 'settings.mfa.setup': 'إعداد المصادقة', + 'settings.mfa.scanQr': 'امسح رمز QR بتطبيقك أو أدخل المفتاح يدويًا.', + 'settings.mfa.secretLabel': 'المفتاح السري (إدخال يدوي)', + 'settings.mfa.codePlaceholder': 'رمز من 6 أرقام', + 'settings.mfa.enable': 'تفعيل 2FA', + 'settings.mfa.cancelSetup': 'إلغاء', + 'settings.mfa.disableTitle': 'تعطيل 2FA', + 'settings.mfa.disableHint': 'أدخل كلمة مرور حسابك ورمزًا حاليًا من المصادقة.', + 'settings.mfa.disable': 'تعطيل 2FA', + 'settings.mfa.toastEnabled': 'تم تفعيل المصادقة الثنائية', + 'settings.mfa.toastDisabled': 'تم تعطيل المصادقة الثنائية', + 'settings.mfa.demoBlocked': 'غير متاح في الوضع التجريبي', +}; +export default settings; diff --git a/shared/src/i18n/ar/share.ts b/shared/src/i18n/ar/share.ts new file mode 100644 index 00000000..3797b3fa --- /dev/null +++ b/shared/src/i18n/ar/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'رابط عام', + 'share.linkHint': + 'أنشئ رابطًا يمكن لأي شخص استخدامه لعرض هذه الرحلة بدون تسجيل الدخول. للقراءة فقط — لا يمكن التعديل.', + 'share.createLink': 'إنشاء رابط', + 'share.deleteLink': 'حذف الرابط', + 'share.createError': 'تعذر إنشاء الرابط', + 'share.permMap': 'الخريطة والخطة', + 'share.permBookings': 'الحجوزات', + 'share.permPacking': 'الأمتعة', + 'share.permBudget': 'الميزانية', + 'share.permCollab': 'الدردشة', +}; +export default share; diff --git a/shared/src/i18n/ar/shared.ts b/shared/src/i18n/ar/shared.ts new file mode 100644 index 00000000..530fd91b --- /dev/null +++ b/shared/src/i18n/ar/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'الرابط منتهي أو غير صالح', + 'shared.expiredHint': 'رابط الرحلة المشترك لم يعد نشطًا.', + 'shared.readOnly': 'عرض للقراءة فقط', + 'shared.tabPlan': 'الخطة', + 'shared.tabBookings': 'الحجوزات', + 'shared.tabPacking': 'قائمة التعبئة', + 'shared.tabBudget': 'الميزانية', + 'shared.tabChat': 'الدردشة', + 'shared.days': 'أيام', + 'shared.places': 'أماكن', + 'shared.other': 'أخرى', + 'shared.totalBudget': 'إجمالي الميزانية', + 'shared.messages': 'رسائل', + 'shared.sharedVia': 'تمت المشاركة عبر', + 'shared.confirmed': 'مؤكد', + 'shared.pending': 'قيد الانتظار', +}; +export default shared; diff --git a/shared/src/i18n/ar/stats.ts b/shared/src/i18n/ar/stats.ts new file mode 100644 index 00000000..756a6021 --- /dev/null +++ b/shared/src/i18n/ar/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'الدول', + 'stats.cities': 'المدن', + 'stats.trips': 'الرحلات', + 'stats.places': 'الأماكن', + 'stats.worldProgress': 'التقدم حول العالم', + 'stats.visited': 'تمت زيارتها', + 'stats.remaining': 'المتبقية', + 'stats.visitedCountries': 'الدول التي تمت زيارتها', +}; +export default stats; diff --git a/shared/src/i18n/ar/system_notice.ts b/shared/src/i18n/ar/system_notice.ts new file mode 100644 index 00000000..65867b08 --- /dev/null +++ b/shared/src/i18n/ar/system_notice.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.v3_photos.title': 'تم نقل الصور في الإصدار 3.0', + 'system_notice.v3_photos.body': + 'تمت إزالة تبويب ​**الصور**​ من مخطط الرحلة. صورك آمنة — لم يعدّل TREK مكتبتك على Immich أو Synology قطّ.\n\nتعيش الصور الآن في إضافة **Journey**. Journey اختيارية — إن لم تكن متاحة بعد، اطلب من المسؤول تفعيلها عبر Admin ← الإضافات.', + 'system_notice.v3_journey.title': 'تعرّف على Journey — مذكرة سفر', + 'system_notice.v3_journey.body': + 'وثّق رحلاتك كقصص غنية بخطوط زمنية ومعارض صور وخرائط تفاعلية.', + 'system_notice.v3_journey.cta_label': 'فتح Journey', + 'system_notice.v3_journey.highlight_timeline': 'جدول زمني يومي ومعرض', + 'system_notice.v3_journey.highlight_photos': 'استيراد من Immich أو Synology', + 'system_notice.v3_journey.highlight_share': 'مشاركة علنية — دون تسجيل دخول', + 'system_notice.v3_journey.highlight_export': 'تصدير كألبوم صور PDF', + 'system_notice.v3_features.title': 'مزيد من مميزات 3.0', + 'system_notice.v3_features.body': + 'بعض الجديد الآخر الجدير بالمعرفة في هذا الإصدار.', + 'system_notice.v3_features.highlight_dashboard': + 'إعادة تصميم لوحة التحكم mobile-first', + 'system_notice.v3_features.highlight_offline': 'وضع لا اتصال كامل كتطبيق PWA', + 'system_notice.v3_features.highlight_search': 'إكمال تلقائي في الوقت الفعلي', + 'system_notice.v3_features.highlight_import': + 'استيراد أماكن من ملفات KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: ترقية OAuth 2.1', + 'system_notice.v3_mcp.body': + 'تمت إعادة تصميم تكامل MCP بالكامل. OAuth 2.1 هو الآن طريقة المصادقة الموصى بها. الرموز الثابتة (trek_…) مهملة وستُزال في إصدار مستقبلي.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 موصى به (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 نطاق أذونات دقيق', + 'system_notice.v3_mcp.highlight_deprecated': 'الرموز الثابتة trek_ مهملة', + 'system_notice.v3_mcp.highlight_tools': 'مجموعة أدوات وإرشادات موسعة', + 'system_notice.v3_thankyou.title': 'كلمة شخصية مني', + 'system_notice.v3_thankyou.body': + 'قبل أن تمضي — أريد أن أتوقف لحظة.\n\nبدأ TREK كمشروع جانبي بنيته لرحلاتي الخاصة. لم أتخيل يومًا أنه سيكبر ليصبح شيئًا يعتمد عليه 4,000 منكم لتخطيط مغامراتهم. كل نجمة، كل مشكلة، كل طلب ميزة — أقرأها جميعًا، وهي ما يبقيني مستمرًا في الليالي المتأخرة بين عمل بدوام كامل والجامعة.\n\nأريدكم أن تعرفوا: TREK سيبقى دائمًا مفتوح المصدر، دائمًا مستضافًا ذاتيًا، دائمًا ملككم. لا تتبع، لا اشتراكات، لا شروط خفية. مجرد أداة بناها شخص يحب السفر بقدر ما تحبونه.\n\nشكر خاص لـ [jubnl](https://github.com/jubnl) — لقد أصبحت متعاونًا رائعًا. الكثير مما يجعل الإصدار 3.0 عظيمًا يحمل بصماتك. شكرًا لإيمانك بهذا المشروع عندما كان لا يزال في بداياته.\n\nولكل واحد منكم ممن أبلغ عن خطأ، أو ترجم نصًا، أو شارك TREK مع صديق، أو ببساطة استخدمه لتخطيط رحلة — **شكرًا لكم**. أنتم السبب في وجود هذا.\n\nإلى المزيد من المغامرات معًا.\n\n— Maurice\n\n---\n\n[انضم إلى المجتمع على Discord](https://discord.gg/7Q6M6jDwzf)\n\nإذا جعل TREK رحلاتك أفضل، [فنجان قهوة صغير](https://ko-fi.com/mauriceboe) يبقي الأضواء مشتعلة.', + 'system_notice.v3014_whitespace_collision.title': + 'إجراء مطلوب: تعارض في حسابات المستخدمين', + 'system_notice.v3014_whitespace_collision.body': + 'اكتشف ترقية 3.0.14 تعارضًا في أسماء مستخدمين أو بريد إلكتروني ناتجًا عن مسافات بيضاء في بداية أو نهاية القيم المخزنة. تمت إعادة تسمية الحسابات المتأثرة تلقائيًا. تحقق من سجلات الخادم بحثًا عن أسطر تبدأ بـ **[migration] WHITESPACE COLLISION** لتحديد الحسابات التي تحتاج إلى مراجعة.', + 'system_notice.welcome_v1.title': 'مرحبًا بك في TREK', + 'system_notice.welcome_v1.body': + 'مخطط رحلاتك الشامل. أنشئ جداول السفر، وشارك رحلاتك مع الأصدقاء، وابقَ منظمًا — سواء كنت متصلاً بالإنترنت أم لا.', + 'system_notice.welcome_v1.cta_label': 'خطط لرحلة', + 'system_notice.welcome_v1.hero_alt': 'وجهة سفر خلابة مع واجهة تطبيق TREK', + 'system_notice.welcome_v1.highlight_plan': 'جداول رحلات يومية لكل سفرة', + 'system_notice.welcome_v1.highlight_share': 'تعاون مع شركاء السفر', + 'system_notice.welcome_v1.highlight_offline': 'يعمل بلا إنترنت على الهاتف', + 'system_notice.pager.prev': 'الإشعار السابق', + 'system_notice.pager.next': 'الإشعار التالي', + 'system_notice.pager.goto': 'الانتقال إلى الإشعار {n}', + 'system_notice.pager.position': 'الإشعار {current} من {total}', +}; +export default system_notice; diff --git a/shared/src/i18n/ar/todo.ts b/shared/src/i18n/ar/todo.ts new file mode 100644 index 00000000..2d79c741 --- /dev/null +++ b/shared/src/i18n/ar/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'قائمة الأمتعة', + 'todo.subtab.todo': 'المهام', + 'todo.completed': 'مكتمل', + 'todo.filter.all': 'الكل', + 'todo.filter.open': 'مفتوح', + 'todo.filter.done': 'منجز', + 'todo.uncategorized': 'بدون تصنيف', + 'todo.namePlaceholder': 'اسم المهمة', + 'todo.descriptionPlaceholder': 'وصف (اختياري)', + 'todo.unassigned': 'غير مُسنَد', + 'todo.noCategory': 'بدون فئة', + 'todo.hasDescription': 'له وصف', + 'todo.addItem': 'إضافة مهمة جديدة', + 'todo.sidebar.sortBy': 'ترتيب حسب', + 'todo.priority': 'الأولوية', + 'todo.newCategoryLabel': 'جديد', + 'todo.newCategory': 'اسم الفئة', + 'todo.addCategory': 'إضافة فئة', + 'todo.newItem': 'مهمة جديدة', + 'todo.empty': 'لا توجد مهام بعد. أضف مهمة للبدء!', + 'todo.filter.my': 'مهامي', + 'todo.filter.overdue': 'متأخرة', + 'todo.sidebar.tasks': 'المهام', + 'todo.sidebar.categories': 'الفئات', + 'todo.detail.title': 'مهمة', + 'todo.detail.description': 'وصف', + 'todo.detail.category': 'فئة', + 'todo.detail.dueDate': 'تاريخ الاستحقاق', + 'todo.detail.assignedTo': 'مسند إلى', + 'todo.detail.delete': 'حذف', + 'todo.detail.save': 'حفظ التغييرات', + 'todo.sortByPrio': 'الأولوية', + 'todo.detail.priority': 'الأولوية', + 'todo.detail.noPriority': 'لا شيء', + 'todo.detail.create': 'إنشاء مهمة', +}; +export default todo; diff --git a/shared/src/i18n/ar/transport.ts b/shared/src/i18n/ar/transport.ts new file mode 100644 index 00000000..5a81819b --- /dev/null +++ b/shared/src/i18n/ar/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'إضافة وسيلة نقل', + 'transport.modalTitle.create': 'إضافة وسيلة نقل', + 'transport.modalTitle.edit': 'تعديل وسيلة النقل', + 'transport.title': 'المواصلات', + 'transport.addManual': 'نقل يدوي', +}; +export default transport; diff --git a/shared/src/i18n/ar/trip.ts b/shared/src/i18n/ar/trip.ts new file mode 100644 index 00000000..f4227d26 --- /dev/null +++ b/shared/src/i18n/ar/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'الخطة', + 'trip.tabs.transports': 'المواصلات', + 'trip.tabs.reservations': 'الحجوزات', + 'trip.tabs.reservationsShort': 'حجز', + 'trip.tabs.packing': 'قائمة التجهيز', + 'trip.tabs.packingShort': 'تجهيز', + 'trip.tabs.lists': 'القوائم', + 'trip.tabs.listsShort': 'القوائم', + 'trip.tabs.budget': 'الميزانية', + 'trip.tabs.files': 'الملفات', + 'trip.loading': 'جارٍ تحميل الرحلة...', + 'trip.loadingPhotos': 'جارٍ تحميل صور الأماكن...', + 'trip.mobilePlan': 'الخطة', + 'trip.mobilePlaces': 'الأماكن', + 'trip.toast.placeUpdated': 'تم تحديث المكان', + 'trip.toast.placeAdded': 'تمت إضافة المكان', + 'trip.toast.placeDeleted': 'تم حذف المكان', + 'trip.toast.selectDay': 'يرجى اختيار يوم أولًا', + 'trip.toast.assignedToDay': 'تم إسناد المكان إلى اليوم', + 'trip.toast.reorderError': 'فشل إعادة الترتيب', + 'trip.toast.reservationUpdated': 'تم تحديث الحجز', + 'trip.toast.reservationAdded': 'تمت إضافة الحجز', + 'trip.toast.deleted': 'تم الحذف', + 'trip.confirm.deletePlace': 'هل تريد حذف هذا المكان؟', + 'trip.confirm.deletePlaces': 'حذف {count} أماكن؟', + 'trip.toast.placesDeleted': 'تم حذف {count} أماكن', +}; +export default trip; diff --git a/shared/src/i18n/ar/trips.ts b/shared/src/i18n/ar/trips.ts new file mode 100644 index 00000000..5392a0f6 --- /dev/null +++ b/shared/src/i18n/ar/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} تمت إزالته', + 'trips.memberRemoveError': 'فشل في الإزالة', + 'trips.memberAdded': '{username} تمت إضافته', + 'trips.memberAddError': 'فشل في الإضافة', + 'trips.reminder': 'تذكير', + 'trips.reminderNone': 'بدون', + 'trips.reminderDay': 'يوم', + 'trips.reminderDays': 'أيام', + 'trips.reminderCustom': 'مخصص', + 'trips.reminderDaysBefore': 'أيام قبل المغادرة', + 'trips.reminderDisabledHint': + 'تذكيرات الرحلة معطلة. قم بتفعيلها من الإدارة > الإعدادات > الإشعارات.', +}; +export default trips; diff --git a/shared/src/i18n/ar/undo.ts b/shared/src/i18n/ar/undo.ts new file mode 100644 index 00000000..e3992b07 --- /dev/null +++ b/shared/src/i18n/ar/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'تراجع', + 'undo.tooltip': 'تراجع: {action}', + 'undo.assignPlace': 'تم تعيين المكان لليوم', + 'undo.removeAssignment': 'تم إزالة المكان من اليوم', + 'undo.reorder': 'تمت إعادة ترتيب الأماكن', + 'undo.optimize': 'تم تحسين المسار', + 'undo.deletePlace': 'تم حذف المكان', + 'undo.deletePlaces': 'تم حذف الأماكن', + 'undo.moveDay': 'تم نقل المكان إلى يوم آخر', + 'undo.lock': 'تم تبديل قفل المكان', + 'undo.importGpx': 'استيراد GPX', + 'undo.importKeyholeMarkup': 'استيراد KMZ/KML', + 'undo.importGoogleList': 'استيراد خرائط Google', + 'undo.importNaverList': 'استيراد خرائط Naver', + 'undo.addPlace': 'تمت إضافة المكان', + 'undo.done': 'تم التراجع: {action}', +}; +export default undo; diff --git a/shared/src/i18n/ar/vacay.ts b/shared/src/i18n/ar/vacay.ts new file mode 100644 index 00000000..784373b6 --- /dev/null +++ b/shared/src/i18n/ar/vacay.ts @@ -0,0 +1,96 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'خطط وأدر أيام الإجازة', + 'vacay.settings': 'الإعدادات', + 'vacay.year': 'السنة', + 'vacay.addYear': 'إضافة السنة التالية', + 'vacay.addPrevYear': 'إضافة السنة السابقة', + 'vacay.removeYear': 'إزالة السنة', + 'vacay.removeYearConfirm': 'إزالة {year}؟', + 'vacay.removeYearHint': + 'سيتم حذف كل إدخالات الإجازات والعطل الخاصة بهذه السنة نهائيًا.', + 'vacay.remove': 'إزالة', + 'vacay.persons': 'الأشخاص', + 'vacay.noPersons': 'لم تتم إضافة أشخاص بعد', + 'vacay.addPerson': 'إضافة شخص', + 'vacay.editPerson': 'تعديل الشخص', + 'vacay.removePerson': 'إزالة الشخص', + 'vacay.removePersonConfirm': 'إزالة {name}؟', + 'vacay.removePersonHint': 'سيتم حذف جميع إدخالات الإجازة لهذا الشخص نهائيًا.', + 'vacay.personName': 'الاسم', + 'vacay.personNamePlaceholder': 'أدخل الاسم', + 'vacay.color': 'اللون', + 'vacay.add': 'إضافة', + 'vacay.legend': 'المفتاح', + 'vacay.publicHoliday': 'عطلة رسمية', + 'vacay.companyHoliday': 'عطلة شركة', + 'vacay.weekend': 'نهاية الأسبوع', + 'vacay.modeVacation': 'إجازة', + 'vacay.modeCompany': 'عطلة شركة', + 'vacay.entitlement': 'الاستحقاق', + 'vacay.entitlementDays': 'الأيام', + 'vacay.used': 'المستخدم', + 'vacay.remaining': 'المتبقي', + 'vacay.carriedOver': 'من {year}', + 'vacay.blockWeekends': 'حظر عطلة نهاية الأسبوع', + 'vacay.blockWeekendsHint': 'منع إدخالات الإجازة يومي السبت والأحد', + 'vacay.weekendDays': 'أيام عطلة نهاية الأسبوع', + 'vacay.mon': 'الاثنين', + 'vacay.tue': 'الثلاثاء', + 'vacay.wed': 'الأربعاء', + 'vacay.thu': 'الخميس', + 'vacay.fri': 'الجمعة', + 'vacay.sat': 'السبت', + 'vacay.sun': 'الأحد', + 'vacay.publicHolidays': 'العطل الرسمية', + 'vacay.publicHolidaysHint': 'وضع علامة على العطل الرسمية في التقويم', + 'vacay.selectCountry': 'اختر الدولة', + 'vacay.selectRegion': 'اختر المنطقة (اختياري)', + 'vacay.addCalendar': 'إضافة تقويم', + 'vacay.calendarLabel': 'التسمية', + 'vacay.calendarColor': 'اللون', + 'vacay.noCalendars': 'لا توجد تقويمات', + 'vacay.companyHolidays': 'عطل الشركة', + 'vacay.companyHolidaysHint': 'السماح بوضع علامة على أيام عطلات الشركة', + 'vacay.companyHolidaysNoDeduct': 'لا تُخصم عطل الشركة من أيام الإجازة.', + 'vacay.weekStart': 'يبدأ الأسبوع في', + 'vacay.weekStartHint': 'اختر ما إذا كان الأسبوع يبدأ يوم الاثنين أو الأحد', + 'vacay.carryOver': 'الترحيل', + 'vacay.carryOverHint': + 'ترحيل أيام الإجازة المتبقية تلقائيًا إلى السنة التالية', + 'vacay.sharing': 'المشاركة', + 'vacay.sharingHint': 'شارك خطة إجازاتك مع مستخدمي TREK الآخرين', + 'vacay.owner': 'المالك', + 'vacay.shareEmailPlaceholder': 'البريد الإلكتروني لمستخدم TREK', + 'vacay.shareSuccess': 'تمت مشاركة الخطة بنجاح', + 'vacay.shareError': 'تعذرت مشاركة الخطة', + 'vacay.dissolve': 'فك الدمج', + 'vacay.dissolveHint': 'افصل التقويمات مرة أخرى. سيتم الاحتفاظ بإدخالاتك.', + 'vacay.dissolveAction': 'فك', + 'vacay.dissolved': 'تم فصل التقويم', + 'vacay.fusedWith': 'مُدمج مع', + 'vacay.you': 'أنت', + 'vacay.noData': 'لا توجد بيانات', + 'vacay.changeColor': 'تغيير اللون', + 'vacay.inviteUser': 'دعوة مستخدم', + 'vacay.inviteHint': 'ادعُ مستخدم TREK آخرًا لمشاركة تقويم إجازة مشترك.', + 'vacay.selectUser': 'اختر مستخدمًا', + 'vacay.sendInvite': 'إرسال الدعوة', + 'vacay.inviteSent': 'تم إرسال الدعوة', + 'vacay.inviteError': 'تعذر إرسال الدعوة', + 'vacay.pending': 'قيد الانتظار', + 'vacay.noUsersAvailable': 'لا يوجد مستخدمون متاحون', + 'vacay.accept': 'قبول', + 'vacay.decline': 'رفض', + 'vacay.acceptFusion': 'قبول ودمج', + 'vacay.inviteTitle': 'طلب دمج', + 'vacay.inviteWantsToFuse': 'يريد مشاركة تقويم إجازة معك.', + 'vacay.fuseInfo1': 'سيرى كلاكما جميع إدخالات الإجازة في تقويم مشترك واحد.', + 'vacay.fuseInfo2': 'يمكن لكلا الطرفين إنشاء وتعديل الإدخالات لبعضهما البعض.', + 'vacay.fuseInfo3': 'يمكن لكلا الطرفين حذف الإدخالات وتغيير مستحقات الإجازة.', + 'vacay.fuseInfo4': 'تتم مشاركة الإعدادات مثل العطل الرسمية وعطل الشركة.', + 'vacay.fuseInfo5': + 'يمكن فك الدمج في أي وقت من قبل أي طرف. ستبقى إدخالاتك محفوظة.', +}; +export default vacay; diff --git a/shared/src/i18n/br/admin.ts b/shared/src/i18n/br/admin.ts new file mode 100644 index 00000000..efff6cc2 --- /dev/null +++ b/shared/src/i18n/br/admin.ts @@ -0,0 +1,367 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notificações', + 'admin.notifications.hint': + 'Escolha um canal de notificação. Apenas um pode estar ativo por vez.', + 'admin.notifications.none': 'Desativado', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Salvar configurações de notificação', + 'admin.notifications.saved': 'Configurações de notificação salvas', + 'admin.notifications.testWebhook': 'Enviar webhook de teste', + 'admin.notifications.testWebhookSuccess': + 'Webhook de teste enviado com sucesso', + 'admin.notifications.testWebhookFailed': 'Falha ao enviar webhook de teste', + 'admin.smtp.title': 'E-mail e notificações', + 'admin.smtp.hint': 'Configuração SMTP para envio de notificações por e-mail.', + 'admin.smtp.testButton': 'Enviar e-mail de teste', + 'admin.webhook.hint': + 'Enviar notificações para um webhook externo (Discord, Slack, etc.).', + 'admin.smtp.testSuccess': 'E-mail de teste enviado com sucesso', + 'admin.smtp.testFailed': 'Falha ao enviar e-mail de teste', + 'admin.title': 'Administração', + 'admin.subtitle': 'Gestão de usuários e configurações do sistema', + 'admin.tabs.users': 'Usuários', + 'admin.tabs.categories': 'Categorias', + 'admin.tabs.backup': 'Backup', + 'admin.stats.users': 'Usuários', + 'admin.stats.trips': 'Viagens', + 'admin.stats.places': 'Lugares', + 'admin.stats.photos': 'Fotos', + 'admin.stats.files': 'Arquivos', + 'admin.table.user': 'Usuário', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Função', + 'admin.table.created': 'Criado', + 'admin.table.lastLogin': 'Último acesso', + 'admin.table.actions': 'Ações', + 'admin.you': '(Você)', + 'admin.editUser': 'Editar usuário', + 'admin.newPassword': 'Nova senha', + 'admin.newPasswordHint': 'Deixe em branco para manter a senha atual', + 'admin.deleteUser': + 'Excluir o usuário "{name}"? Todas as viagens serão excluídas permanentemente.', + 'admin.deleteUserTitle': 'Excluir usuário', + 'admin.newPasswordPlaceholder': 'Digite a nova senha…', + 'admin.toast.loadError': 'Falha ao carregar dados do admin', + 'admin.toast.userUpdated': 'Usuário atualizado', + 'admin.toast.updateError': 'Falha ao atualizar', + 'admin.toast.userDeleted': 'Usuário excluído', + 'admin.toast.deleteError': 'Falha ao excluir', + 'admin.toast.cannotDeleteSelf': 'Não é possível excluir a própria conta', + 'admin.toast.userCreated': 'Usuário criado', + 'admin.toast.createError': 'Falha ao criar usuário', + 'admin.toast.fieldsRequired': + 'Nome de usuário, e-mail e senha são obrigatórios', + 'admin.createUser': 'Criar usuário', + 'admin.invite.title': 'Links de convite', + 'admin.invite.subtitle': 'Crie links de cadastro de uso único', + 'admin.invite.create': 'Criar link', + 'admin.invite.createAndCopy': 'Criar e copiar', + 'admin.invite.empty': 'Nenhum link de convite criado ainda', + 'admin.invite.maxUses': 'Máx. usos', + 'admin.invite.expiry': 'Expira após', + 'admin.invite.uses': 'usado(s)', + 'admin.invite.expiresAt': 'expira', + 'admin.invite.createdBy': 'por', + 'admin.invite.active': 'Ativo', + 'admin.invite.expired': 'Expirado', + 'admin.invite.usedUp': 'Esgotado', + 'admin.invite.copied': 'Link de convite copiado para a área de transferência', + 'admin.invite.copyLink': 'Copiar link', + 'admin.invite.deleted': 'Link de convite excluído', + 'admin.invite.createError': 'Falha ao criar link de convite', + 'admin.invite.deleteError': 'Falha ao excluir link de convite', + 'admin.tabs.settings': 'Configurações', + 'admin.allowRegistration': 'Permitir cadastro', + 'admin.allowRegistrationHint': 'Novos usuários podem se cadastrar sozinhos', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Exigir autenticação em dois fatores (2FA)', + 'admin.requireMfaHint': + 'Usuários sem 2FA precisam concluir a configuração em Configurações antes de usar o app.', + 'admin.apiKeys': 'Chaves de API', + 'admin.apiKeysHint': + 'Opcional. Habilita dados estendidos de lugares, como fotos e clima.', + 'admin.mapsKey': 'Chave da API Google Maps', + 'admin.mapsKeyHint': + 'Necessária para busca de lugares. Obtenha em console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Sem chave de API, o OpenStreetMap é usado na busca. Com uma chave Google, também podem ser carregadas fotos, avaliações e horários. Obtenha em console.cloud.google.com.', + 'admin.recommended': 'Recomendado', + 'admin.weatherKey': 'Chave OpenWeatherMap', + 'admin.weatherKeyHint': + 'Para dados meteorológicos. Grátis em openweathermap.org', + 'admin.validateKey': 'Testar', + 'admin.keyValid': 'Conectado', + 'admin.keyInvalid': 'Inválida', + 'admin.keySaved': 'Chaves de API salvas', + 'admin.oidcTitle': 'Login Único (OIDC)', + 'admin.oidcSubtitle': + 'Permitir login via provedores externos como Google, Apple, Authentik ou Keycloak.', + 'admin.oidcDisplayName': 'Nome exibido', + 'admin.oidcIssuer': 'URL do emissor', + 'admin.oidcIssuerHint': + 'URL do emissor OpenID Connect do provedor, ex.: https://accounts.google.com', + 'admin.oidcSaved': 'Configuração OIDC salva', + 'admin.oidcOnlyMode': 'Desativar login por senha', + 'admin.oidcOnlyModeHint': + 'Quando ativado, só é permitido login SSO. Login e cadastro por senha ficam bloqueados.', + 'admin.fileTypes': 'Tipos de arquivo permitidos', + 'admin.fileTypesHint': + 'Configure quais tipos de arquivo os usuários podem enviar.', + 'admin.fileTypesFormat': + 'Extensões separadas por vírgula (ex.: jpg,png,pdf,doc). Use * para permitir todos.', + 'admin.fileTypesSaved': 'Configurações de tipos de arquivo salvas', + 'admin.placesPhotos.title': 'Fotos de Locais', + 'admin.placesPhotos.subtitle': + 'Busca fotos da Google Places API. Desative para economizar cota da API. Fotos do Wikimedia não são afetadas.', + 'admin.placesAutocomplete.title': 'Autocompletar de Locais', + 'admin.placesAutocomplete.subtitle': + 'Usa a Google Places API para sugestões de pesquisa. Desative para economizar cota da API.', + 'admin.placesDetails.title': 'Detalhes do Local', + 'admin.placesDetails.subtitle': + 'Busca informações detalhadas do local (horários, avaliação, site) da Google Places API. Desative para economizar cota da API.', + 'admin.bagTracking.title': 'Rastreamento de malas', + 'admin.bagTracking.subtitle': + 'Ativar peso e atribuição de mala para itens da lista', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Mensagens em tempo real para colaboração', + 'admin.collab.notes.title': 'Notas', + 'admin.collab.notes.subtitle': 'Notas e documentos compartilhados', + 'admin.collab.polls.title': 'Enquetes', + 'admin.collab.polls.subtitle': 'Enquetes e votações em grupo', + 'admin.collab.whatsnext.title': 'Próximos passos', + 'admin.collab.whatsnext.subtitle': + 'Sugestões de atividades e próximos passos', + 'admin.tabs.config': 'Personalização', + 'admin.tabs.defaults': 'Padrões do usuário', + 'admin.defaultSettings.title': 'Configurações padrão do usuário', + 'admin.defaultSettings.description': + 'Defina padrões para toda a instância. Usuários que não alteraram uma configuração verão esses valores. As próprias alterações deles sempre têm prioridade.', + 'admin.defaultSettings.saved': 'Padrão salvo', + 'admin.defaultSettings.reset': 'Redefinir para o padrão integrado', + 'admin.defaultSettings.resetToBuiltIn': 'redefinir', + 'admin.tabs.templates': 'Modelos de mala', + 'admin.packingTemplates.title': 'Modelos de mala', + 'admin.packingTemplates.subtitle': + 'Crie listas de mala reutilizáveis para suas viagens', + 'admin.packingTemplates.create': 'Novo modelo', + 'admin.packingTemplates.namePlaceholder': 'Nome do modelo (ex.: Praia)', + 'admin.packingTemplates.empty': 'Nenhum modelo criado ainda', + 'admin.packingTemplates.items': 'itens', + 'admin.packingTemplates.categories': 'categorias', + 'admin.packingTemplates.itemName': 'Nome do item', + 'admin.packingTemplates.itemCategory': 'Categoria', + 'admin.packingTemplates.categoryName': 'Nome da categoria (ex.: Roupas)', + 'admin.packingTemplates.addCategory': 'Adicionar categoria', + 'admin.packingTemplates.created': 'Modelo criado', + 'admin.packingTemplates.deleted': 'Modelo excluído', + 'admin.packingTemplates.loadError': 'Falha ao carregar modelos', + 'admin.packingTemplates.createError': 'Falha ao criar modelo', + 'admin.packingTemplates.deleteError': 'Falha ao excluir modelo', + 'admin.packingTemplates.saveError': 'Falha ao salvar', + 'admin.tabs.addons': 'Complementos', + 'admin.addons.title': 'Complementos', + 'admin.addons.subtitle': + 'Ative ou desative recursos para personalizar sua experiência no TREK.', + 'admin.addons.catalog.memories.name': 'Memórias', + 'admin.addons.catalog.memories.description': + 'Álbuns de fotos compartilhados em cada viagem', + 'admin.addons.catalog.packing.name': 'Listas', + 'admin.addons.catalog.packing.description': + 'Listas de bagagem e tarefas a fazer para suas viagens', + 'admin.addons.catalog.budget.name': 'Orçamento', + 'admin.addons.catalog.budget.description': + 'Acompanhe despesas e planeje o orçamento da viagem', + 'admin.addons.catalog.documents.name': 'Documentos', + 'admin.addons.catalog.documents.description': + 'Armazene e gerencie documentos de viagem', + 'admin.addons.catalog.vacay.name': 'Férias', + 'admin.addons.catalog.vacay.description': + 'Planejador de férias pessoal com visão em calendário', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Mapa mundial com países visitados e estatísticas', + 'admin.addons.catalog.collab.name': 'Colab', + 'admin.addons.catalog.collab.description': + 'Notas, enquetes e chat em tempo real para planejar a viagem', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol para integração com assistentes de IA', + 'admin.addons.subtitleBefore': + 'Ative ou desative recursos para personalizar sua ', + 'admin.addons.subtitleAfter': ' experiência.', + 'admin.addons.enabled': 'Ativado', + 'admin.addons.disabled': 'Desativado', + 'admin.addons.type.trip': 'Viagem', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integração', + 'admin.addons.tripHint': 'Disponível como aba em cada viagem', + 'admin.addons.globalHint': + 'Disponível como seção própria na navegação principal', + 'admin.addons.toast.updated': 'Complemento atualizado', + 'admin.addons.toast.error': 'Falha ao atualizar complemento', + 'admin.addons.noAddons': 'Nenhum complemento disponível', + 'admin.addons.integrationHint': + 'Serviços de backend e integrações de API sem página dedicada', + 'admin.weather.title': 'Dados meteorológicos', + 'admin.weather.badge': 'Desde 24 de março de 2026', + 'admin.weather.description': + 'O TREK usa Open-Meteo como fonte de clima. Open-Meteo é um serviço gratuito e de código aberto — sem chave de API.', + 'admin.weather.forecast': 'Previsão de 16 dias', + 'admin.weather.forecastDesc': 'Antes eram 5 dias (OpenWeatherMap)', + 'admin.weather.climate': 'Dados climáticos históricos', + 'admin.weather.climateDesc': + 'Médias dos últimos 85 anos para dias além da previsão de 16 dias', + 'admin.weather.requests': '10.000 requisições / dia', + 'admin.weather.requestsDesc': 'Grátis, sem chave de API', + 'admin.weather.locationHint': + 'O clima usa o primeiro lugar com coordenadas de cada dia. Se nenhum lugar estiver atribuído ao dia, qualquer lugar da lista serve como referência.', + 'admin.tabs.audit': 'Auditoria', + 'admin.audit.subtitle': + 'Eventos sensíveis de segurança e administração (backups, usuários, 2FA, configurações).', + 'admin.audit.empty': 'Nenhum registro de auditoria.', + 'admin.audit.refresh': 'Atualizar', + 'admin.audit.loadMore': 'Carregar mais', + 'admin.audit.showing': '{count} carregados · {total} no total', + 'admin.audit.col.time': 'Hora', + 'admin.audit.col.user': 'Usuário', + 'admin.audit.col.action': 'Ação', + 'admin.audit.col.resource': 'Recurso', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Detalhes', + 'admin.tabs.github': 'GitHub', + 'admin.github.title': 'Histórico de versões', + 'admin.github.subtitle': 'Últimas atualizações de {repo}', + 'admin.github.latest': 'Mais recente', + 'admin.github.prerelease': 'Pré-lançamento', + 'admin.github.showDetails': 'Mostrar detalhes', + 'admin.github.hideDetails': 'Ocultar detalhes', + 'admin.github.loadMore': 'Carregar mais', + 'admin.github.loading': 'Carregando...', + 'admin.github.error': 'Falha ao carregar versões', + 'admin.github.by': 'por', + 'admin.github.support': 'Ajuda a continuar desenvolvendo o TREK', + 'admin.update.available': 'Atualização disponível', + 'admin.update.text': + 'O TREK {version} está disponível. Você está na {current}.', + 'admin.update.button': 'Ver no GitHub', + 'admin.update.install': 'Instalar atualização', + 'admin.update.confirmTitle': 'Instalar atualização?', + 'admin.update.confirmText': + 'O TREK será atualizado de {current} para {version}. O servidor reiniciará automaticamente em seguida.', + 'admin.update.dataInfo': + 'Todos os seus dados (viagens, usuários, chaves de API, envios, Vacay, Atlas, orçamentos) serão preservados.', + 'admin.update.warning': + 'O app ficará brevemente indisponível durante o reinício.', + 'admin.update.confirm': 'Atualizar agora', + 'admin.update.installing': 'Atualizando…', + 'admin.update.success': 'Atualização instalada! O servidor está reiniciando…', + 'admin.update.failed': 'Falha na atualização', + 'admin.update.backupHint': 'Recomendamos criar um backup antes de atualizar.', + 'admin.update.backupLink': 'Ir para Backup', + 'admin.update.howTo': 'Como atualizar', + 'admin.update.dockerText': + 'Sua instância TREK roda no Docker. Para atualizar para {version}, execute no servidor:', + 'admin.update.reloadHint': 'Recarregue a página em alguns segundos.', + 'admin.tabs.permissions': 'Permissões', + 'admin.tabs.mcpTokens': 'Acesso MCP', + 'admin.mcpTokens.title': 'Acesso MCP', + 'admin.mcpTokens.subtitle': + 'Gerenciar sessões OAuth e tokens de API de todos os usuários', + 'admin.mcpTokens.sectionTitle': 'Tokens de API', + 'admin.mcpTokens.owner': 'Proprietário', + 'admin.mcpTokens.tokenName': 'Nome do Token', + 'admin.mcpTokens.created': 'Criado', + 'admin.mcpTokens.lastUsed': 'Último uso', + 'admin.mcpTokens.never': 'Nunca', + 'admin.mcpTokens.empty': 'Nenhum token MCP foi criado ainda', + 'admin.mcpTokens.deleteTitle': 'Excluir Token', + 'admin.mcpTokens.deleteMessage': + 'Isso revogará o token imediatamente. O usuário perderá o acesso MCP por este token.', + 'admin.mcpTokens.deleteSuccess': 'Token excluído', + 'admin.mcpTokens.deleteError': 'Falha ao excluir token', + 'admin.mcpTokens.loadError': 'Falha ao carregar tokens', + 'admin.oauthSessions.sectionTitle': 'Sessões OAuth', + 'admin.oauthSessions.clientName': 'Cliente', + 'admin.oauthSessions.owner': 'Proprietário', + 'admin.oauthSessions.scopes': 'Permissões', + 'admin.oauthSessions.created': 'Criado', + 'admin.oauthSessions.empty': 'Nenhuma sessão OAuth ativa', + 'admin.oauthSessions.revokeTitle': 'Revogar sessão', + 'admin.oauthSessions.revokeMessage': + 'Esta sessão OAuth será revogada imediatamente. O cliente perderá o acesso MCP.', + 'admin.oauthSessions.revokeSuccess': 'Sessão revogada', + 'admin.oauthSessions.revokeError': 'Falha ao revogar sessão', + 'admin.oauthSessions.loadError': 'Falha ao carregar sessões OAuth', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'As notificações no aplicativo estão sempre ativas e não podem ser desativadas globalmente.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook de admin', + 'admin.notifications.adminWebhookPanel.hint': + 'Este webhook é usado exclusivamente para notificações de admin (ex. alertas de versão). É independente dos webhooks de usuários e dispara automaticamente quando uma URL está configurada.', + 'admin.notifications.adminWebhookPanel.saved': + 'URL do webhook de admin salva', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Webhook de teste enviado com sucesso', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Falha no webhook de teste', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'O webhook de admin dispara automaticamente quando uma URL está configurada', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Permite que os usuários configurem seus próprios tópicos ntfy para notificações push. Configure o servidor padrão abaixo para preencher as configurações do usuário.', + 'admin.notifications.testNtfy': 'Enviar Ntfy de teste', + 'admin.notifications.testNtfySuccess': 'Ntfy de teste enviado com sucesso', + 'admin.notifications.testNtfyFailed': 'Falha ao enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', + 'admin.notifications.adminNtfyPanel.hint': + 'Este tópico Ntfy é usado exclusivamente para notificações de admin (ex. alertas de versão). É independente dos tópicos por usuário e sempre dispara quando configurado.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL do servidor Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Também usado como servidor padrão para notificações ntfy dos usuários. Deixe em branco para usar ntfy.sh. Os usuários podem substituir isso em suas próprias configurações.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Tópico de admin', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acesso (opcional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Token de acesso admin removido', + 'admin.notifications.adminNtfyPanel.saved': + 'Configurações de Ntfy de admin salvas', + 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Ntfy de teste enviado com sucesso', + 'admin.notifications.adminNtfyPanel.testFailed': + 'Falha ao enviar Ntfy de teste', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'O Ntfy de admin sempre dispara quando um tópico está configurado', + 'admin.notifications.adminNotificationsHint': + 'Configure quais canais entregam notificações de admin (ex. alertas de versão). O webhook dispara automaticamente se uma URL de webhook de admin estiver definida.', + 'admin.notifications.tripReminders.title': 'Lembretes de viagem', + 'admin.notifications.tripReminders.hint': + 'Envia uma notificação de lembrete antes do início de uma viagem (requer dias de lembrete definidos na viagem).', + 'admin.notifications.tripReminders.enabled': 'Lembretes de viagem ativados', + 'admin.notifications.tripReminders.disabled': + 'Lembretes de viagem desativados', + 'admin.tabs.notifications': 'Notificações', + 'admin.addons.catalog.journey.name': 'Jornada', + 'admin.addons.catalog.journey.description': + 'Rastreamento de viagens e diário de viajante com check-ins, fotos e histórias diárias', +}; +export default admin; diff --git a/shared/src/i18n/br/airport.ts b/shared/src/i18n/br/airport.ts new file mode 100644 index 00000000..2fc896d6 --- /dev/null +++ b/shared/src/i18n/br/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Código ou cidade do aeroporto (ex. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/br/atlas.ts b/shared/src/i18n/br/atlas.ts new file mode 100644 index 00000000..09f6faea --- /dev/null +++ b/shared/src/i18n/br/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Sua pegada de viagens pelo mundo', + 'atlas.countries': 'Países', + 'atlas.trips': 'Viagens', + 'atlas.places': 'Lugares', + 'atlas.unmark': 'Remover', + 'atlas.confirmMark': 'Marcar este país como visitado?', + 'atlas.confirmUnmark': 'Remover este país da lista de visitados?', + 'atlas.confirmUnmarkRegion': 'Remover esta região da lista de visitados?', + 'atlas.markVisited': 'Marcar como visitado', + 'atlas.markVisitedHint': 'Adicionar este país à lista de visitados', + 'atlas.markRegionVisitedHint': 'Adicionar esta região à lista de visitados', + 'atlas.addToBucket': 'Adicionar à lista de desejos', + 'atlas.addPoi': 'Adicionar lugar', + 'atlas.searchCountry': 'Buscar um país...', + 'atlas.bucketNamePlaceholder': 'Nome (país, cidade, lugar…)', + 'atlas.month': 'Mês', + 'atlas.year': 'Ano', + 'atlas.addToBucketHint': 'Salvar como lugar que você quer visitar', + 'atlas.bucketWhen': 'Quando pretende visitar?', + 'atlas.statsTab': 'Estatísticas', + 'atlas.bucketTab': 'Lista de desejos', + 'atlas.addBucket': 'Adicionar à lista de desejos', + 'atlas.bucketNotesPlaceholder': 'Notas (opcional)', + 'atlas.bucketEmpty': 'Sua lista de desejos está vazia', + 'atlas.bucketEmptyHint': 'Adicione lugares que sonha em visitar', + 'atlas.days': 'Dias', + 'atlas.visitedCountries': 'Países visitados', + 'atlas.cities': 'Cidades', + 'atlas.noData': 'Ainda sem dados de viagem', + 'atlas.noDataHint': + 'Crie uma viagem e adicione lugares para ver o mapa mundial', + 'atlas.lastTrip': 'Última viagem', + 'atlas.nextTrip': 'Próxima viagem', + 'atlas.daysLeft': 'dias restantes', + 'atlas.streak': 'Sequência', + 'atlas.years': 'anos', + 'atlas.yearInRow': 'ano seguido', + 'atlas.yearsInRow': 'anos seguidos', + 'atlas.tripIn': 'viagem em', + 'atlas.tripsIn': 'viagens em', + 'atlas.since': 'desde', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Ásia', + 'atlas.northAmerica': 'América do Norte', + 'atlas.southAmerica': 'América do Sul', + 'atlas.africa': 'África', + 'atlas.oceania': 'Oceania', + 'atlas.other': 'Outro', + 'atlas.firstVisit': 'Primeira viagem', + 'atlas.lastVisitLabel': 'Última viagem', + 'atlas.tripSingular': 'Viagem', + 'atlas.tripPlural': 'Viagens', + 'atlas.placeVisited': 'Lugar visitado', + 'atlas.placesVisited': 'Lugares visitados', +}; +export default atlas; diff --git a/shared/src/i18n/br/backup.ts b/shared/src/i18n/br/backup.ts new file mode 100644 index 00000000..1f42a85a --- /dev/null +++ b/shared/src/i18n/br/backup.ts @@ -0,0 +1,78 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Backup de dados', + 'backup.subtitle': 'Banco de dados e todos os arquivos enviados', + 'backup.refresh': 'Atualizar', + 'backup.upload': 'Enviar backup', + 'backup.uploading': 'Enviando…', + 'backup.create': 'Criar backup', + 'backup.creating': 'Criando…', + 'backup.empty': 'Nenhum backup ainda', + 'backup.createFirst': 'Criar primeiro backup', + 'backup.download': 'Baixar', + 'backup.restore': 'Restaurar', + 'backup.confirm.restore': + 'Restaurar o backup "{name}"?\n\nTodos os dados atuais serão substituídos pelo backup.', + 'backup.confirm.uploadRestore': + 'Enviar e restaurar o arquivo "{name}"?\n\nTodos os dados atuais serão sobrescritos.', + 'backup.confirm.delete': 'Excluir o backup "{name}"?', + 'backup.toast.loadError': 'Falha ao carregar backups', + 'backup.toast.created': 'Backup criado com sucesso', + 'backup.toast.createError': 'Falha ao criar backup', + 'backup.toast.restored': 'Backup restaurado. A página será recarregada…', + 'backup.toast.restoreError': 'Falha ao restaurar', + 'backup.toast.uploadError': 'Falha no envio', + 'backup.toast.deleted': 'Backup excluído', + 'backup.toast.deleteError': 'Falha ao excluir', + 'backup.toast.downloadError': 'Falha no download', + 'backup.toast.settingsSaved': 'Configurações de backup automático salvas', + 'backup.toast.settingsError': 'Falha ao salvar configurações', + 'backup.auto.title': 'Backup automático', + 'backup.auto.subtitle': 'Backup automático em agenda', + 'backup.auto.enable': 'Ativar backup automático', + 'backup.auto.enableHint': + 'Backups serão criados automaticamente conforme a agenda escolhida', + 'backup.auto.interval': 'Intervalo', + 'backup.auto.hour': 'Executar no horário', + 'backup.auto.hourHint': 'Horário local do servidor (formato {format})', + 'backup.auto.dayOfWeek': 'Dia da semana', + 'backup.auto.dayOfMonth': 'Dia do mês', + 'backup.auto.dayOfMonthHint': + 'Limitado a 1–28 para compatibilidade com todos os meses', + 'backup.auto.scheduleSummary': 'Agenda', + 'backup.auto.summaryDaily': 'Todos os dias às {hour}:00', + 'backup.auto.summaryWeekly': 'Toda {day} às {hour}:00', + 'backup.auto.summaryMonthly': 'Dia {day} de cada mês às {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'O backup automático é configurado via variáveis de ambiente Docker. Para alterar essas configurações, atualize o docker-compose.yml e reinicie o contêiner.', + 'backup.auto.copyEnv': 'Copiar variáveis de ambiente Docker', + 'backup.auto.envCopied': + 'Variáveis de ambiente Docker copiadas para a área de transferência', + 'backup.auto.keepLabel': 'Excluir backups antigos após', + 'backup.dow.sunday': 'Dom', + 'backup.dow.monday': 'Seg', + 'backup.dow.tuesday': 'Ter', + 'backup.dow.wednesday': 'Qua', + 'backup.dow.thursday': 'Qui', + 'backup.dow.friday': 'Sex', + 'backup.dow.saturday': 'Sáb', + 'backup.interval.hourly': 'A cada hora', + 'backup.interval.daily': 'Diário', + 'backup.interval.weekly': 'Semanal', + 'backup.interval.monthly': 'Mensal', + 'backup.keep.1day': '1 dia', + 'backup.keep.3days': '3 dias', + 'backup.keep.7days': '7 dias', + 'backup.keep.14days': '14 dias', + 'backup.keep.30days': '30 dias', + 'backup.keep.forever': 'Manter para sempre', + 'backup.restoreConfirmTitle': 'Restaurar backup?', + 'backup.restoreWarning': + 'Todos os dados atuais (viagens, lugares, usuários, envios) serão permanentemente substituídos pelo backup. Esta ação não pode ser desfeita.', + 'backup.restoreTip': + 'Dica: crie um backup do estado atual antes de restaurar.', + 'backup.restoreConfirm': 'Sim, restaurar', +}; +export default backup; diff --git a/shared/src/i18n/br/budget.ts b/shared/src/i18n/br/budget.ts new file mode 100644 index 00000000..bd3d66fc --- /dev/null +++ b/shared/src/i18n/br/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Orçamento', + 'budget.exportCsv': 'Exportar CSV', + 'budget.emptyTitle': 'Nenhum orçamento criado ainda', + 'budget.emptyText': + 'Crie categorias e lançamentos para planejar o orçamento da viagem', + 'budget.emptyPlaceholder': 'Nome da categoria...', + 'budget.createCategory': 'Criar categoria', + 'budget.category': 'Categoria', + 'budget.categoryName': 'Nome da categoria', + 'budget.table.name': 'Nome', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Pessoas', + 'budget.table.days': 'Dias', + 'budget.table.perPerson': 'Por pessoa', + 'budget.table.perDay': 'Por dia', + 'budget.table.perPersonDay': 'P. p. / dia', + 'budget.table.note': 'Obs.', + 'budget.table.date': 'Data', + 'budget.newEntry': 'Novo lançamento', + 'budget.defaultEntry': 'Novo lançamento', + 'budget.defaultCategory': 'Nova categoria', + 'budget.total': 'Total', + 'budget.totalBudget': 'Orçamento total', + 'budget.byCategory': 'Por categoria', + 'budget.editTooltip': 'Clique para editar', + 'budget.linkedToReservation': 'Vinculado a uma reserva — edite o nome por lá', + 'budget.confirm.deleteCategory': + 'Excluir a categoria "{name}" com {count} lançamento(s)?', + 'budget.deleteCategory': 'Excluir categoria', + 'budget.perPerson': 'Por pessoa', + 'budget.paid': 'Pago', + 'budget.open': 'Em aberto', + 'budget.noMembers': 'Nenhum membro atribuído', + 'budget.settlement': 'Acerto', + 'budget.settlementInfo': + 'Clique no avatar de um membro em um item do orçamento para marcá-lo em verde — significa que ele pagou. O acerto mostra quem deve quanto a quem.', + 'budget.netBalances': 'Saldos líquidos', + 'budget.categoriesLabel': 'categorias', +}; +export default budget; diff --git a/shared/src/i18n/br/categories.ts b/shared/src/i18n/br/categories.ts new file mode 100644 index 00000000..dc1b05c7 --- /dev/null +++ b/shared/src/i18n/br/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categorias', + 'categories.subtitle': 'Gerenciar categorias de lugares', + 'categories.new': 'Nova categoria', + 'categories.empty': 'Nenhuma categoria ainda', + 'categories.namePlaceholder': 'Nome da categoria', + 'categories.icon': 'Ícone', + 'categories.color': 'Cor', + 'categories.customColor': 'Escolher cor personalizada', + 'categories.preview': 'Pré-visualização', + 'categories.defaultName': 'Categoria', + 'categories.update': 'Atualizar', + 'categories.create': 'Criar', + 'categories.confirm.delete': + 'Excluir categoria? Os lugares desta categoria não serão excluídos.', + 'categories.toast.loadError': 'Falha ao carregar categorias', + 'categories.toast.nameRequired': 'Digite um nome', + 'categories.toast.updated': 'Categoria atualizada', + 'categories.toast.created': 'Categoria criada', + 'categories.toast.saveError': 'Falha ao salvar', + 'categories.toast.deleted': 'Categoria excluída', + 'categories.toast.deleteError': 'Falha ao excluir', +}; +export default categories; diff --git a/shared/src/i18n/br/collab.ts b/shared/src/i18n/br/collab.ts new file mode 100644 index 00000000..b2cc54e7 --- /dev/null +++ b/shared/src/i18n/br/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Notas', + 'collab.tabs.polls': 'Enquetes', + 'collab.whatsNext.title': 'Próximos passos', + 'collab.whatsNext.today': 'Hoje', + 'collab.whatsNext.tomorrow': 'Amanhã', + 'collab.whatsNext.empty': 'Nenhuma atividade próxima', + 'collab.whatsNext.until': 'até', + 'collab.whatsNext.emptyHint': 'Atividades com horário aparecerão aqui', + 'collab.chat.send': 'Enviar', + 'collab.chat.placeholder': 'Digite uma mensagem...', + 'collab.chat.empty': 'Inicie a conversa', + 'collab.chat.emptyHint': + 'As mensagens são compartilhadas com todos os membros da viagem', + 'collab.chat.emptyDesc': + 'Compartilhe ideias, planos e atualizações com o grupo', + 'collab.chat.today': 'Hoje', + 'collab.chat.yesterday': 'Ontem', + 'collab.chat.deletedMessage': 'apagou uma mensagem', + 'collab.chat.reply': 'Responder', + 'collab.chat.loadMore': 'Carregar mensagens antigas', + 'collab.chat.justNow': 'agora mesmo', + 'collab.chat.minutesAgo': 'há {n} min', + 'collab.chat.hoursAgo': 'há {n} h', + 'collab.notes.title': 'Notas', + 'collab.notes.new': 'Nova nota', + 'collab.notes.empty': 'Nenhuma nota ainda', + 'collab.notes.emptyHint': 'Comece a registrar ideias e planos', + 'collab.notes.all': 'Todas', + 'collab.notes.titlePlaceholder': 'Título da nota', + 'collab.notes.contentPlaceholder': 'Escreva algo...', + 'collab.notes.categoryPlaceholder': 'Categoria', + 'collab.notes.newCategory': 'Nova categoria...', + 'collab.notes.category': 'Categoria', + 'collab.notes.noCategory': 'Sem categoria', + 'collab.notes.color': 'Cor', + 'collab.notes.save': 'Salvar', + 'collab.notes.cancel': 'Cancelar', + 'collab.notes.edit': 'Editar', + 'collab.notes.delete': 'Excluir', + 'collab.notes.pin': 'Fixar', + 'collab.notes.unpin': 'Desafixar', + 'collab.notes.daysAgo': 'há {n} d', + 'collab.notes.categorySettings': 'Gerenciar categorias', + 'collab.notes.create': 'Criar', + 'collab.notes.website': 'Site', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Anexar arquivos', + 'collab.notes.noCategoriesYet': 'Nenhuma categoria ainda', + 'collab.notes.emptyDesc': 'Crie uma nota para começar', + 'collab.polls.title': 'Enquetes', + 'collab.polls.new': 'Nova enquete', + 'collab.polls.empty': 'Nenhuma enquete ainda', + 'collab.polls.emptyHint': 'Pergunte ao grupo e votem juntos', + 'collab.polls.question': 'Pergunta', + 'collab.polls.questionPlaceholder': 'O que vamos fazer?', + 'collab.polls.addOption': '+ Adicionar opção', + 'collab.polls.optionPlaceholder': 'Opção {n}', + 'collab.polls.create': 'Criar enquete', + 'collab.polls.close': 'Encerrar', + 'collab.polls.closed': 'Encerrada', + 'collab.polls.votes': '{n} votos', + 'collab.polls.vote': '{n} voto', + 'collab.polls.multipleChoice': 'Múltipla escolha', + 'collab.polls.multiChoice': 'Múltipla escolha', + 'collab.polls.deadline': 'Prazo', + 'collab.polls.option': 'Opção', + 'collab.polls.options': 'Opções', + 'collab.polls.delete': 'Excluir', + 'collab.polls.closedSection': 'Encerradas', +}; +export default collab; diff --git a/shared/src/i18n/br/common.ts b/shared/src/i18n/br/common.ts new file mode 100644 index 00000000..06c789e5 --- /dev/null +++ b/shared/src/i18n/br/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Salvar', + 'common.showMore': 'Mostrar mais', + 'common.showLess': 'Mostrar menos', + 'common.cancel': 'Cancelar', + 'common.clear': 'Limpar', + 'common.delete': 'Excluir', + 'common.edit': 'Editar', + 'common.add': 'Adicionar', + 'common.loading': 'Carregando...', + 'common.import': 'Importar', + 'common.select': 'Selecionar', + 'common.selectAll': 'Selecionar tudo', + 'common.deselectAll': 'Desmarcar tudo', + 'common.error': 'Erro', + 'common.unknownError': 'Erro desconhecido', + 'common.tooManyAttempts': 'Muitas tentativas. Tente novamente mais tarde.', + 'common.back': 'Voltar', + 'common.all': 'Todos', + 'common.close': 'Fechar', + 'common.open': 'Abrir', + 'common.upload': 'Enviar', + 'common.search': 'Buscar', + 'common.confirm': 'Confirmar', + 'common.ok': 'OK', + 'common.yes': 'Sim', + 'common.no': 'Não', + 'common.or': 'ou', + 'common.none': 'Nenhum', + 'common.date': 'Data', + 'common.rename': 'Renomear', + 'common.discardChanges': 'Descartar alterações', + 'common.discard': 'Descartar', + 'common.name': 'Nome', + 'common.email': 'E-mail', + 'common.password': 'Senha', + 'common.saving': 'Salvando...', + 'common.saved': 'Salvo', + 'common.expand': 'Expandir', + 'common.collapse': 'Recolher', + 'common.update': 'Atualizar', + 'common.change': 'Alterar', + 'common.uploading': 'Enviando…', + 'common.backToPlanning': 'Voltar ao planejamento', + 'common.reset': 'Redefinir', + 'common.copy': 'Copiar', + 'common.copied': 'Copiado', + 'common.justNow': 'agora mesmo', + 'common.hoursAgo': 'há {count}h', + 'common.daysAgo': 'há {count}d', +}; +export default common; diff --git a/shared/src/i18n/br/dashboard.ts b/shared/src/i18n/br/dashboard.ts new file mode 100644 index 00000000..b811fd98 --- /dev/null +++ b/shared/src/i18n/br/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Minhas viagens', + 'dashboard.subtitle.loading': 'Carregando viagens...', + 'dashboard.subtitle.trips': '{count} viagens ({archived} arquivadas)', + 'dashboard.subtitle.empty': 'Comece sua primeira viagem', + 'dashboard.subtitle.activeOne': '{count} viagem ativa', + 'dashboard.subtitle.activeMany': '{count} viagens ativas', + 'dashboard.subtitle.archivedSuffix': ' · {count} arquivadas', + 'dashboard.newTrip': 'Nova viagem', + 'dashboard.gridView': 'Grade', + 'dashboard.listView': 'Lista', + 'dashboard.currency': 'Moeda', + 'dashboard.timezone': 'Fusos horários', + 'dashboard.localTime': 'Local', + 'dashboard.timezoneCustomTitle': 'Fuso personalizado', + 'dashboard.timezoneCustomLabelPlaceholder': 'Rótulo (opcional)', + 'dashboard.timezoneCustomTzPlaceholder': 'ex.: America/Sao_Paulo', + 'dashboard.timezoneCustomAdd': 'Adicionar', + 'dashboard.timezoneCustomErrorEmpty': 'Informe um identificador de fuso', + 'dashboard.timezoneCustomErrorInvalid': + 'Fuso inválido. Use o formato Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Já adicionado', + 'dashboard.emptyTitle': 'Nenhuma viagem ainda', + 'dashboard.emptyText': 'Crie sua primeira viagem e comece a planejar!', + 'dashboard.emptyButton': 'Criar primeira viagem', + 'dashboard.nextTrip': 'Próxima viagem', + 'dashboard.shared': 'Compartilhada', + 'dashboard.sharedBy': 'Compartilhada por {name}', + 'dashboard.days': 'Dias', + 'dashboard.places': 'Lugares', + 'dashboard.members': 'Parceiros de viagem', + 'dashboard.archive': 'Arquivar', + 'dashboard.copyTrip': 'Copiar', + 'dashboard.copySuffix': 'cópia', + 'dashboard.restore': 'Restaurar', + 'dashboard.archived': 'Arquivada', + 'dashboard.status.ongoing': 'Em andamento', + 'dashboard.status.today': 'Hoje', + 'dashboard.status.tomorrow': 'Amanhã', + 'dashboard.status.past': 'Passada', + 'dashboard.status.daysLeft': 'Faltam {count} dias', + 'dashboard.toast.loadError': 'Não foi possível carregar as viagens', + 'dashboard.toast.created': 'Viagem criada com sucesso!', + 'dashboard.toast.createError': 'Não foi possível criar a viagem', + 'dashboard.toast.updated': 'Viagem atualizada!', + 'dashboard.toast.updateError': 'Não foi possível atualizar a viagem', + 'dashboard.toast.deleted': 'Viagem excluída', + 'dashboard.toast.deleteError': 'Não foi possível excluir a viagem', + 'dashboard.toast.archived': 'Viagem arquivada', + 'dashboard.toast.archiveError': 'Não foi possível arquivar', + 'dashboard.toast.restored': 'Viagem restaurada', + 'dashboard.toast.restoreError': 'Não foi possível restaurar', + 'dashboard.toast.copied': 'Viagem copiada!', + 'dashboard.toast.copyError': 'Não foi possível copiar a viagem', + 'dashboard.confirm.delete': + 'Excluir a viagem "{title}"? Todos os lugares e planos serão excluídos permanentemente.', + 'dashboard.editTrip': 'Editar viagem', + 'dashboard.createTrip': 'Criar nova viagem', + 'dashboard.tripTitle': 'Título', + 'dashboard.tripTitlePlaceholder': 'ex.: Verão no Japão', + 'dashboard.tripDescription': 'Descrição', + 'dashboard.tripDescriptionPlaceholder': 'Sobre o que é esta viagem?', + 'dashboard.startDate': 'Data de início', + 'dashboard.endDate': 'Data de término', + 'dashboard.dayCount': 'Número de dias', + 'dashboard.dayCountHint': + 'Quantos dias planejar quando nenhuma data de viagem for definida.', + 'dashboard.noDateHint': + 'Sem datas — serão criados 7 dias padrão. Você pode alterar depois.', + 'dashboard.coverImage': 'Imagem de capa', + 'dashboard.addCoverImage': 'Adicionar capa (ou arrastar e soltar)', + 'dashboard.addMembers': 'Companheiros de viagem', + 'dashboard.addMember': 'Adicionar membro', + 'dashboard.coverSaved': 'Capa salva', + 'dashboard.coverUploadError': 'Falha no envio', + 'dashboard.coverRemoveError': 'Falha ao remover', + 'dashboard.titleRequired': 'O título é obrigatório', + 'dashboard.endDateError': 'A data final deve ser depois da inicial', + 'dashboard.greeting.morning': 'Bom dia,', + 'dashboard.greeting.afternoon': 'Boa tarde,', + 'dashboard.greeting.evening': 'Boa noite,', + 'dashboard.mobile.liveNow': 'Ao vivo agora', + 'dashboard.mobile.tripProgress': 'Progresso da viagem', + 'dashboard.mobile.daysLeft': '{count} dias restantes', + 'dashboard.mobile.places': 'Lugares', + 'dashboard.mobile.buddies': 'Companheiros', + 'dashboard.mobile.newTrip': 'Nova viagem', + 'dashboard.mobile.currency': 'Moeda', + 'dashboard.mobile.timezone': 'Fuso horário', + 'dashboard.mobile.upcomingTrips': 'Próximas viagens', + 'dashboard.mobile.yourTrips': 'Suas viagens', + 'dashboard.mobile.trips': 'viagens', + 'dashboard.mobile.starts': 'Começa', + 'dashboard.mobile.duration': 'Duração', + 'dashboard.mobile.day': 'dia', + 'dashboard.mobile.days': 'dias', + 'dashboard.mobile.ongoing': 'Em andamento', + 'dashboard.mobile.startsToday': 'Começa hoje', + 'dashboard.mobile.tomorrow': 'Amanhã', + 'dashboard.mobile.inDays': 'Em {count} dias', + 'dashboard.mobile.inMonths': 'Em {count} meses', + 'dashboard.mobile.completed': 'Concluído', + 'dashboard.mobile.currencyConverter': 'Conversor de moedas', +}; +export default dashboard; diff --git a/shared/src/i18n/br/day.ts b/shared/src/i18n/br/day.ts new file mode 100644 index 00000000..1541bf13 --- /dev/null +++ b/shared/src/i18n/br/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Probabilidade de chuva', + 'day.precipitation': 'Precipitação', + 'day.wind': 'Vento', + 'day.sunrise': 'Nascer do sol', + 'day.sunset': 'Pôr do sol', + 'day.hourlyForecast': 'Previsão por hora', + 'day.climateHint': + 'Médias históricas — previsão real disponível até 16 dias desta data.', + 'day.noWeather': + 'Sem dados meteorológicos. Adicione um lugar com coordenadas.', + 'day.overview': 'Resumo do dia', + 'day.accommodation': 'Hospedagem', + 'day.addAccommodation': 'Adicionar hospedagem', + 'day.hotelDayRange': 'Aplicar aos dias', + 'day.noPlacesForHotel': 'Adicione lugares à viagem primeiro', + 'day.allDays': 'Todos', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Até', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Confirmação', + 'day.editAccommodation': 'Editar hospedagem', + 'day.reservations': 'Reservas', +}; +export default day; diff --git a/shared/src/i18n/br/dayplan.ts b/shared/src/i18n/br/dayplan.ts new file mode 100644 index 00000000..5627e2ae --- /dev/null +++ b/shared/src/i18n/br/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Exportar calendário (ICS)', + 'dayplan.emptyDay': 'Nenhum lugar planejado para este dia', + 'dayplan.addNote': 'Adicionar nota', + 'dayplan.editNote': 'Editar nota', + 'dayplan.noteAdd': 'Adicionar nota', + 'dayplan.noteEdit': 'Editar nota', + 'dayplan.noteTitle': 'Nota', + 'dayplan.noteSubtitle': 'Nota do dia', + 'dayplan.totalCost': 'Custo total', + 'dayplan.days': 'Dias', + 'dayplan.dayN': 'Dia {n}', + 'dayplan.calculating': 'Calculando...', + 'dayplan.route': 'Rota', + 'dayplan.optimize': 'Otimizar', + 'dayplan.optimized': 'Rota otimizada', + 'dayplan.routeError': 'Falha ao calcular a rota', + 'dayplan.toast.needTwoPlaces': + 'São necessários pelo menos dois lugares para otimizar a rota', + 'dayplan.toast.routeOptimized': 'Rota otimizada', + 'dayplan.toast.noGeoPlaces': + 'Nenhum lugar com coordenadas para calcular a rota', + 'dayplan.confirmed': 'Confirmada', + 'dayplan.pendingRes': 'Pendente', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Exportar plano do dia em PDF', + 'dayplan.pdfError': 'Falha ao exportar PDF', + 'dayplan.cannotReorderTransport': + 'Reservas com horário fixo não podem ser reordenadas', + 'dayplan.confirmRemoveTimeTitle': 'Remover horário?', + 'dayplan.confirmRemoveTimeBody': + 'Este lugar tem um horário fixo ({time}). Movê-lo removerá o horário e permitirá ordenação livre.', + 'dayplan.confirmRemoveTimeAction': 'Remover horário e mover', + 'dayplan.cannotDropOnTimed': + 'Itens não podem ser colocados entre entradas com horário fixo', + 'dayplan.cannotBreakChronology': + 'Isso quebraria a ordem cronológica dos itens e reservas agendados', + 'dayplan.mobile.addPlace': 'Adicionar lugar', + 'dayplan.mobile.searchPlaces': 'Buscar lugares...', + 'dayplan.mobile.allAssigned': 'Todos os lugares atribuídos', + 'dayplan.mobile.noMatch': 'Sem correspondência', + 'dayplan.mobile.createNew': 'Criar novo lugar', +}; +export default dayplan; diff --git a/shared/src/i18n/br/externalNotifications.ts b/shared/src/i18n/br/externalNotifications.ts new file mode 100644 index 00000000..baf41c0b --- /dev/null +++ b/shared/src/i18n/br/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const br: NotificationLocale = { + email: { + footer: 'Você recebeu isso porque tem as notificações ativadas no TREK.', + manage: 'Gerenciar preferências nas configurações', + madeWith: 'Made with', + openTrek: 'Abrir TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Convite para "${p.trip}"`, + body: `${p.actor} convidou ${p.invitee || 'um membro'} para a viagem "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nova reserva: ${p.booking}`, + body: `${p.actor} adicionou uma reserva "${p.booking}" (${p.type}) em "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Lembrete: ${p.trip}`, + body: `Sua viagem "${p.trip}" está chegando!`, + }), + todo_due: (p) => ({ + title: `Tarefa com vencimento: ${p.todo}`, + body: `"${p.todo}" em "${p.trip}" vence em ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Convite Vacay Fusion', + body: `${p.actor} convidou você para fundir planos de férias. Abra o TREK para aceitar ou recusar.`, + }), + photos_shared: (p) => ({ + title: `${p.count} fotos compartilhadas`, + body: `${p.actor} compartilhou ${p.count} foto(s) em "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nova mensagem em "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Bagagem: ${p.category}`, + body: `${p.actor} atribuiu você à categoria "${p.category}" em "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nova versão do TREK disponível', + body: `O TREK ${p.version} está disponível. Acesse o painel de administração para atualizar.`, + }), + synology_session_cleared: () => ({ + title: 'Sessão Synology encerrada', + body: 'Sua conta ou URL do Synology foi alterada. Você foi desconectado do Synology Photos.', + }), + }, + passwordReset: { + subject: 'Redefinir sua senha', + greeting: 'Olá', + body: 'Recebemos um pedido para redefinir a senha da sua conta TREK. Clique no botão abaixo para definir uma nova senha.', + ctaIntro: 'Redefinir senha', + expiry: 'Este link expira em 60 minutos.', + ignore: + 'Se você não solicitou isto, pode ignorar este e-mail — sua senha não será alterada.', + }, +}; + +export default br; diff --git a/shared/src/i18n/br/files.ts b/shared/src/i18n/br/files.ts new file mode 100644 index 00000000..c06f5f13 --- /dev/null +++ b/shared/src/i18n/br/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Arquivos', + 'files.pageTitle': 'Arquivos e documentos', + 'files.subtitle': '{count} arquivos para {trip}', + 'files.download': 'Baixar', + 'files.openError': 'Não foi possível abrir o arquivo', + 'files.downloadPdf': 'Baixar PDF', + 'files.count': '{count} arquivos', + 'files.countSingular': '1 arquivo', + 'files.uploaded': '{count} enviado(s)', + 'files.uploadError': 'Falha no envio', + 'files.dropzone': 'Solte os arquivos aqui', + 'files.dropzoneHint': 'ou clique para escolher', + 'files.allowedTypes': + 'Imagens, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Máx. 50 MB', + 'files.uploading': 'Enviando...', + 'files.filterAll': 'Todos', + 'files.filterPdf': 'PDFs', + 'files.filterImages': 'Imagens', + 'files.filterDocs': 'Documentos', + 'files.filterCollab': 'Notas Colab', + 'files.sourceCollab': 'Das notas Colab', + 'files.empty': 'Nenhum arquivo ainda', + 'files.emptyHint': 'Envie arquivos para anexá-los à viagem', + 'files.openTab': 'Abrir em nova aba', + 'files.confirm.delete': 'Excluir este arquivo?', + 'files.toast.deleted': 'Arquivo excluído', + 'files.toast.deleteError': 'Falha ao excluir arquivo', + 'files.sourcePlan': 'Plano do dia', + 'files.sourceBooking': 'Reserva', + 'files.sourceTransport': 'Transporte', + 'files.attach': 'Anexar', + 'files.pasteHint': + 'Você também pode colar imagens da área de transferência (Ctrl+V)', + 'files.trash': 'Lixeira', + 'files.trashEmpty': 'A lixeira está vazia', + 'files.emptyTrash': 'Esvaziar lixeira', + 'files.restore': 'Restaurar', + 'files.star': 'Favoritar', + 'files.unstar': 'Remover favorito', + 'files.assign': 'Atribuir', + 'files.assignTitle': 'Atribuir arquivo', + 'files.assignPlace': 'Lugar', + 'files.assignBooking': 'Reserva', + 'files.assignTransport': 'Transporte', + 'files.unassigned': 'Não atribuído', + 'files.unlink': 'Remover vínculo', + 'files.toast.trashed': 'Movido para a lixeira', + 'files.toast.restored': 'Arquivo restaurado', + 'files.toast.trashEmptied': 'Lixeira esvaziada', + 'files.toast.assigned': 'Arquivo atribuído', + 'files.toast.assignError': 'Falha na atribuição', + 'files.toast.restoreError': 'Falha ao restaurar', + 'files.confirm.permanentDelete': + 'Excluir permanentemente este arquivo? Não é possível desfazer.', + 'files.confirm.emptyTrash': + 'Excluir permanentemente todos os arquivos na lixeira? Não é possível desfazer.', + 'files.noteLabel': 'Nota', + 'files.notePlaceholder': 'Adicione uma nota...', +}; +export default files; diff --git a/shared/src/i18n/br/index.ts b/shared/src/i18n/br/index.ts new file mode 100644 index 00000000..43c637d6 --- /dev/null +++ b/shared/src/i18n/br/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...collab, + ...memories, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/br/inspector.ts b/shared/src/i18n/br/inspector.ts new file mode 100644 index 00000000..9f833bdd --- /dev/null +++ b/shared/src/i18n/br/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Aberto', + 'inspector.closed': 'Fechado', + 'inspector.openingHours': 'Horário de funcionamento', + 'inspector.showHours': 'Mostrar horário de funcionamento', + 'inspector.files': 'Arquivos', + 'inspector.filesCount': '{count} arquivos', + 'inspector.removeFromDay': 'Remover do dia', + 'inspector.remove': 'Remover', + 'inspector.addToDay': 'Adicionar ao dia', + 'inspector.confirmedRes': 'Reserva confirmada', + 'inspector.pendingRes': 'Reserva pendente', + 'inspector.google': 'Abrir no Google Maps', + 'inspector.website': 'Abrir site', + 'inspector.addRes': 'Reserva', + 'inspector.editRes': 'Editar reserva', + 'inspector.participants': 'Participantes', + 'inspector.trackStats': 'Dados da trilha', +}; +export default inspector; diff --git a/shared/src/i18n/br/journey.ts b/shared/src/i18n/br/journey.ts new file mode 100644 index 00000000..be7f8994 --- /dev/null +++ b/shared/src/i18n/br/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Buscar jornadas…', + 'journey.search.noResults': 'Nenhuma jornada corresponde a "{query}"', + 'journey.title': 'Jornada', + 'journey.subtitle': 'Registre suas viagens em tempo real', + 'journey.new': 'Nova jornada', + 'journey.create': 'Criar', + 'journey.titlePlaceholder': 'Para onde você vai?', + 'journey.empty': 'Nenhuma jornada ainda', + 'journey.emptyHint': 'Comece a documentar sua próxima viagem', + 'journey.deleted': 'Jornada excluída', + 'journey.createError': 'Não foi possível criar a jornada', + 'journey.deleteError': 'Não foi possível excluir a jornada', + 'journey.deleteConfirmTitle': 'Excluir', + 'journey.deleteConfirmMessage': + 'Excluir "{title}"? Isso não pode ser desfeito.', + 'journey.deleteConfirmGeneric': 'Tem certeza de que deseja excluir isso?', + 'journey.notFound': 'Jornada não encontrada', + 'journey.photos': 'Fotos', + 'journey.timelineEmpty': 'Nenhuma parada ainda', + 'journey.timelineEmptyHint': + 'Adicione um check-in ou escreva uma entrada no diário para começar', + 'journey.status.draft': 'Rascunho', + 'journey.status.active': 'Ativa', + 'journey.status.completed': 'Concluída', + 'journey.status.upcoming': 'Próxima', + 'journey.status.archived': 'Arquivado', + 'journey.checkin.add': 'Fazer check-in', + 'journey.checkin.namePlaceholder': 'Nome do local', + 'journey.checkin.notesPlaceholder': 'Notas (opcional)', + 'journey.checkin.save': 'Salvar', + 'journey.checkin.error': 'Não foi possível salvar o check-in', + 'journey.entry.add': 'Diário', + 'journey.entry.edit': 'Editar entrada', + 'journey.entry.titlePlaceholder': 'Título (opcional)', + 'journey.entry.bodyPlaceholder': 'O que aconteceu hoje?', + 'journey.entry.save': 'Salvar', + 'journey.entry.error': 'Não foi possível salvar a entrada', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Falha no envio', + 'journey.share.share': 'Compartilhar', + 'journey.share.public': 'Público', + 'journey.share.linkCopied': 'Link público copiado', + 'journey.share.disabled': 'Compartilhamento público desativado', + 'journey.editor.titlePlaceholder': 'Dê um nome a este momento...', + 'journey.editor.bodyPlaceholder': 'Conte a história deste dia...', + 'journey.editor.placePlaceholder': 'Localização (opcional)', + 'journey.editor.tagsPlaceholder': + 'Tags: joia escondida, melhor refeição, preciso voltar...', + 'journey.visibility.private': 'Privado', + 'journey.visibility.shared': 'Compartilhado', + 'journey.visibility.public': 'Público', + 'journey.emptyState.title': 'Sua história começa aqui', + 'journey.emptyState.subtitle': + 'Faça check-in em um lugar ou escreva sua primeira entrada no diário', + 'journey.frontpage.subtitle': + 'Transforme suas viagens em histórias que você nunca vai esquecer', + 'journey.frontpage.createJourney': 'Criar jornada', + 'journey.frontpage.activeJourney': 'Jornada ativa', + 'journey.frontpage.allJourneys': 'Todas as jornadas', + 'journey.frontpage.journeys': 'jornadas', + 'journey.frontpage.createNew': 'Criar uma nova jornada', + 'journey.frontpage.createNewSub': + 'Escolha viagens, escreva histórias, compartilhe suas aventuras', + 'journey.frontpage.live': 'Ao vivo', + 'journey.frontpage.synced': 'Sincronizado', + 'journey.frontpage.continueWriting': 'Continuar escrevendo', + 'journey.frontpage.updated': 'Atualizado {time}', + 'journey.frontpage.suggestionLabel': 'A viagem acabou de terminar', + 'journey.frontpage.suggestionText': + 'Transforme {title} em uma jornada', + 'journey.frontpage.dismiss': 'Dispensar', + 'journey.frontpage.journeyName': 'Nome da jornada', + 'journey.frontpage.namePlaceholder': 'ex. Sudeste Asiático 2026', + 'journey.frontpage.selectTrips': 'Selecionar viagens', + 'journey.frontpage.tripsSelected': 'viagens selecionadas', + 'journey.frontpage.trips': 'viagens', + 'journey.frontpage.placesImported': 'lugares serão importados', + 'journey.frontpage.places': 'lugares', + 'journey.detail.backToJourney': 'Voltar à jornada', + 'journey.detail.syncedWithTrips': 'Sincronizado com viagens', + 'journey.detail.addEntry': 'Adicionar entrada', + 'journey.detail.newEntry': 'Nova entrada', + 'journey.detail.editEntry': 'Editar entrada', + 'journey.detail.noEntries': 'Nenhuma entrada ainda', + 'journey.detail.noEntriesHint': + 'Adicione uma viagem para começar com entradas preliminares', + 'journey.detail.noPhotos': 'Nenhuma foto ainda', + 'journey.detail.noPhotosHint': + 'Envie fotos para as entradas ou explore sua biblioteca do Immich/Synology', + 'journey.detail.journeyStats': 'Estatísticas da jornada', + 'journey.detail.syncedTrips': 'Viagens sincronizadas', + 'journey.detail.noTripsLinked': 'Nenhuma viagem vinculada ainda', + 'journey.detail.contributors': 'Colaboradores', + 'journey.detail.readMore': 'Ler mais', + 'journey.detail.prosCons': 'Prós e contras', + 'journey.detail.photos': 'fotos', + 'journey.detail.day': 'Dia {number}', + 'journey.detail.places': 'lugares', + 'journey.stats.days': 'Dias', + 'journey.stats.cities': 'Cidades', + 'journey.stats.entries': 'Entradas', + 'journey.stats.photos': 'Fotos', + 'journey.stats.places': 'Lugares', + 'journey.skeletons.show': 'Mostrar sugestões', + 'journey.skeletons.hide': 'Ocultar sugestões', + 'journey.verdict.lovedIt': 'Adorei', + 'journey.verdict.couldBeBetter': 'Poderia ser melhor', + 'journey.synced.places': 'lugares', + 'journey.synced.synced': 'sincronizado', + 'journey.editor.discardChangesConfirm': + 'Você tem alterações não salvas. Descartá-las?', + 'journey.editor.uploadFailed': 'Falha ao enviar fotos', + 'journey.editor.uploadPhotos': 'Enviar fotos', + 'journey.editor.uploading': 'Enviando...', + 'journey.editor.uploadingProgress': 'Enviando {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} de {total} fotos falharam — salve novamente para tentar', + 'journey.editor.fromGallery': 'Da galeria', + 'journey.editor.allPhotosAdded': 'Todas as fotos já foram adicionadas', + 'journey.editor.writeStory': 'Escreva sua história...', + 'journey.editor.prosCons': 'Prós e contras', + 'journey.editor.pros': 'Prós', + 'journey.editor.cons': 'Contras', + 'journey.editor.proPlaceholder': 'Algo ótimo...', + 'journey.editor.conPlaceholder': 'Não tão bom...', + 'journey.editor.addAnother': 'Adicionar outro', + 'journey.editor.date': 'Data', + 'journey.editor.location': 'Localização', + 'journey.editor.searchLocation': 'Buscar localização...', + 'journey.editor.mood': 'Humor', + 'journey.editor.weather': 'Clima', + 'journey.editor.photoFirst': '1º', + 'journey.editor.makeFirst': 'Tornar 1º', + 'journey.editor.searching': 'Pesquisando...', + 'journey.mood.amazing': 'Incrível', + 'journey.mood.good': 'Bom', + 'journey.mood.neutral': 'Neutro', + 'journey.mood.rough': 'Difícil', + 'journey.weather.sunny': 'Ensolarado', + 'journey.weather.partly': 'Parcialmente nublado', + 'journey.weather.cloudy': 'Nublado', + 'journey.weather.rainy': 'Chuvoso', + 'journey.weather.stormy': 'Tempestuoso', + 'journey.weather.cold': 'Nevando', + 'journey.trips.linkTrip': 'Vincular viagem', + 'journey.trips.searchTrip': 'Buscar viagem', + 'journey.trips.searchPlaceholder': 'Nome da viagem ou destino...', + 'journey.trips.noTripsAvailable': 'Nenhuma viagem disponível', + 'journey.trips.link': 'Vincular', + 'journey.trips.tripLinked': 'Viagem vinculada', + 'journey.trips.linkFailed': 'Não foi possível vincular a viagem', + 'journey.trips.addTrip': 'Adicionar viagem', + 'journey.trips.unlinkTrip': 'Desvincular viagem', + 'journey.trips.unlinkMessage': + 'Desvincular "{title}"? Todas as entradas e fotos sincronizadas desta viagem serão excluídas permanentemente. Isso não pode ser desfeito.', + 'journey.trips.unlink': 'Desvincular', + 'journey.trips.tripUnlinked': 'Viagem desvinculada', + 'journey.trips.unlinkFailed': 'Não foi possível desvincular a viagem', + 'journey.trips.noTripsLinkedSettings': 'Nenhuma viagem vinculada', + 'journey.contributors.invite': 'Convidar colaborador', + 'journey.contributors.searchUser': 'Buscar usuário', + 'journey.contributors.searchPlaceholder': 'Nome de usuário ou e-mail...', + 'journey.contributors.noUsers': 'Nenhum usuário encontrado', + 'journey.contributors.role': 'Função', + 'journey.contributors.added': 'Colaborador adicionado', + 'journey.contributors.addFailed': 'Não foi possível adicionar o colaborador', + 'journey.share.publicShare': 'Compartilhamento público', + 'journey.share.createLink': 'Criar link de compartilhamento', + 'journey.share.linkCreated': 'Link de compartilhamento criado', + 'journey.share.createFailed': 'Não foi possível criar o link', + 'journey.share.copy': 'Copiar', + 'journey.share.copied': 'Copiado!', + 'journey.share.timeline': 'Linha do tempo', + 'journey.share.gallery': 'Galeria', + 'journey.share.map': 'Mapa', + 'journey.share.removeLink': 'Remover link de compartilhamento', + 'journey.share.linkDeleted': 'Link de compartilhamento removido', + 'journey.share.deleteFailed': 'Não foi possível excluir', + 'journey.share.updateFailed': 'Não foi possível atualizar', + 'journey.invite.role': 'Função', + 'journey.invite.viewer': 'Visualizador', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Convidar', + 'journey.invite.inviting': 'Convidando...', + 'journey.settings.title': 'Configurações da jornada', + 'journey.settings.coverImage': 'Imagem de capa', + 'journey.settings.changeCover': 'Alterar capa', + 'journey.settings.addCover': 'Adicionar imagem de capa', + 'journey.settings.name': 'Nome', + 'journey.settings.subtitle': 'Subtítulo', + 'journey.settings.subtitlePlaceholder': 'ex. Tailândia, Vietnã e Camboja', + 'journey.settings.endJourney': 'Arquivar Jornada', + 'journey.settings.reopenJourney': 'Restaurar Jornada', + 'journey.settings.archived': 'Jornada arquivada', + 'journey.settings.reopened': 'Jornada reaberta', + 'journey.settings.endDescription': + 'Oculta o selo Ao Vivo. Você pode reabrir a qualquer momento.', + 'journey.settings.delete': 'Excluir', + 'journey.settings.deleteJourney': 'Excluir jornada', + 'journey.settings.deleteMessage': + 'Excluir "{title}"? Todas as entradas e fotos serão perdidas.', + 'journey.settings.saved': 'Configurações salvas', + 'journey.settings.saveFailed': 'Não foi possível salvar', + 'journey.settings.coverUpdated': 'Capa atualizada', + 'journey.settings.coverFailed': 'Falha no envio', + 'journey.settings.failedToDelete': 'Falha ao excluir', + 'journey.entries.deleteTitle': 'Excluir entrada', + 'journey.photosUploaded': '{count} fotos enviadas', + 'journey.photosUploadFailed': 'Algumas fotos não foram enviadas', + 'journey.photosAdded': '{count} fotos adicionadas', + 'journey.public.notFound': 'Não encontrado', + 'journey.public.notFoundMessage': + 'Esta jornada não existe ou o link expirou.', + 'journey.public.readOnly': 'Somente leitura · Jornada pública', + 'journey.public.tagline': 'Kit de recursos e exploração de viagens', + 'journey.public.sharedVia': 'Compartilhado via', + 'journey.public.madeWith': 'Feito com', + 'journey.pdf.journeyBook': 'Livro da jornada', + 'journey.pdf.madeWith': 'Feito com TREK', + 'journey.pdf.day': 'Dia', + 'journey.pdf.theEnd': 'Fim', + 'journey.pdf.saveAsPdf': 'Salvar como PDF', + 'journey.pdf.pages': 'páginas', + 'journey.picker.tripPeriod': 'Período da viagem', + 'journey.picker.dateRange': 'Período', + 'journey.picker.allPhotos': 'Todas as fotos', + 'journey.picker.albums': 'Álbuns', + 'journey.picker.selected': 'selecionados', + 'journey.picker.addTo': 'Adicionar a', + 'journey.picker.newGallery': 'Nova galeria', + 'journey.picker.selectAll': 'Selecionar tudo', + 'journey.picker.deselectAll': 'Desmarcar tudo', + 'journey.picker.noAlbums': 'Nenhum álbum encontrado', + 'journey.picker.selectDate': 'Selecionar data', + 'journey.picker.search': 'Pesquisar', +}; +export default journey; diff --git a/shared/src/i18n/br/login.ts b/shared/src/i18n/br/login.ts new file mode 100644 index 00000000..3e4dde2f --- /dev/null +++ b/shared/src/i18n/br/login.ts @@ -0,0 +1,95 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Falha no login. Verifique suas credenciais.', + 'login.tagline': 'Suas viagens.\nSeu plano.', + 'login.description': + 'Planeje viagens em equipe com mapas interativos, orçamento e sincronização em tempo real.', + 'login.features.maps': 'Mapas interativos', + 'login.features.mapsDesc': 'Google Places, rotas e agrupamento', + 'login.features.realtime': 'Sincronização em tempo real', + 'login.features.realtimeDesc': 'Planejem juntos via WebSocket', + 'login.features.budget': 'Controle de orçamento', + 'login.features.budgetDesc': 'Categorias, gráficos e custo por pessoa', + 'login.features.collab': 'Colaboração', + 'login.features.collabDesc': 'Vários usuários com viagens compartilhadas', + 'login.features.packing': 'Listas de malas', + 'login.features.packingDesc': 'Categorias, progresso e sugestões', + 'login.features.bookings': 'Reservas', + 'login.features.bookingsDesc': 'Voos, hotéis, restaurantes e mais', + 'login.features.files': 'Documentos', + 'login.features.filesDesc': 'Envie e gerencie documentos', + 'login.features.routes': 'Rotas inteligentes', + 'login.features.routesDesc': 'Otimize e exporte para o Google Maps', + 'login.selfHosted': 'Auto-hospedado · Código aberto · Seus dados são seus', + 'login.title': 'Entrar', + 'login.subtitle': 'Bem-vindo de volta', + 'login.signingIn': 'Entrando…', + 'login.signIn': 'Entrar', + 'login.createAdmin': 'Criar conta de administrador', + 'login.createAdminHint': + 'Configure a primeira conta de administrador do TREK.', + 'login.setNewPassword': 'Definir nova senha', + 'login.setNewPasswordHint': 'Você deve alterar sua senha antes de continuar.', + 'login.createAccount': 'Criar conta', + 'login.createAccountHint': 'Cadastre uma nova conta.', + 'login.creating': 'Criando…', + 'login.noAccount': 'Não tem conta?', + 'login.hasAccount': 'Já tem conta?', + 'login.register': 'Cadastrar', + 'login.emailPlaceholder': 'seu@email.com', + 'login.username': 'Nome de usuário', + 'login.oidc.registrationDisabled': + 'Cadastro desativado. Fale com o administrador.', + 'login.oidc.noEmail': 'Nenhum e-mail recebido do provedor.', + 'login.oidc.tokenFailed': 'Falha na autenticação.', + 'login.oidc.invalidState': 'Sessão inválida. Tente novamente.', + 'login.demoFailed': 'Falha no login de demonstração', + 'login.oidcSignIn': 'Entrar com {name}', + 'login.oidcOnly': 'Login por senha desativado. Use o provedor SSO.', + 'login.oidcLoggedOut': + 'Você foi desconectado. Entre novamente usando o provedor SSO.', + 'login.demoHint': 'Experimente a demonstração — sem cadastro', + 'login.mfaTitle': 'Autenticação em duas etapas', + 'login.mfaSubtitle': 'Digite o código de 6 dígitos do seu app autenticador.', + 'login.mfaCodeLabel': 'Código de verificação', + 'login.mfaCodeRequired': 'Digite o código do app autenticador.', + 'login.mfaHint': 'Abra o Google Authenticator, Authy ou outro app TOTP.', + 'login.mfaBack': '← Voltar ao login', + 'login.mfaVerify': 'Verificar', + 'login.invalidInviteLink': 'Link de convite inválido ou expirado', + 'login.oidcFailed': 'Falha no login OIDC', + 'login.usernameRequired': 'Nome de usuário é obrigatório', + 'login.passwordMinLength': 'A senha deve ter pelo menos 8 caracteres', + 'login.forgotPassword': 'Esqueceu a senha?', + 'login.forgotPasswordTitle': 'Redefinir sua senha', + 'login.forgotPasswordBody': + 'Digite o e-mail cadastrado. Se houver uma conta, enviaremos um link de redefinição.', + 'login.forgotPasswordSubmit': 'Enviar link', + 'login.forgotPasswordSentTitle': 'Verifique seu e-mail', + 'login.forgotPasswordSentBody': + 'Se houver uma conta para esse e-mail, o link está a caminho. Ele expira em 60 minutos.', + 'login.forgotPasswordSmtpHintOff': + 'Observação: seu administrador não configurou SMTP, então o link de redefinição será gravado no console do servidor em vez de ser enviado por e-mail.', + 'login.backToLogin': 'Voltar ao login', + 'login.newPassword': 'Nova senha', + 'login.confirmPassword': 'Confirmar nova senha', + 'login.passwordsDontMatch': 'As senhas não coincidem', + 'login.mfaCode': 'Código 2FA', + 'login.resetPasswordTitle': 'Definir uma nova senha', + 'login.resetPasswordBody': + 'Escolha uma senha forte que você ainda não tenha usado aqui. Mínimo de 8 caracteres.', + 'login.resetPasswordMfaBody': + 'Digite seu código 2FA ou um código de backup para concluir a redefinição.', + 'login.resetPasswordSubmit': 'Redefinir senha', + 'login.resetPasswordVerify': 'Verificar e redefinir', + 'login.resetPasswordSuccessTitle': 'Senha atualizada', + 'login.resetPasswordSuccessBody': + 'Agora você pode entrar com sua nova senha.', + 'login.resetPasswordInvalidLink': 'Link de redefinição inválido', + 'login.resetPasswordInvalidLinkBody': + 'Este link está ausente ou corrompido. Solicite um novo para continuar.', + 'login.resetPasswordFailed': + 'Falha na redefinição. O link pode ter expirado.', +}; +export default login; diff --git a/shared/src/i18n/br/map.ts b/shared/src/i18n/br/map.ts new file mode 100644 index 00000000..0bc47a25 --- /dev/null +++ b/shared/src/i18n/br/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Conexões', + 'map.showConnections': 'Mostrar rotas de reservas', + 'map.hideConnections': 'Ocultar rotas de reservas', +}; +export default map; diff --git a/shared/src/i18n/br/members.ts b/shared/src/i18n/br/members.ts new file mode 100644 index 00000000..a735d132 --- /dev/null +++ b/shared/src/i18n/br/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Compartilhar viagem', + 'members.inviteUser': 'Convidar usuário', + 'members.selectUser': 'Selecionar usuário…', + 'members.invite': 'Convidar', + 'members.allHaveAccess': 'Todos os usuários já têm acesso.', + 'members.access': 'Acesso', + 'members.person': 'pessoa', + 'members.persons': 'pessoas', + 'members.you': 'você', + 'members.owner': 'Proprietário', + 'members.leaveTrip': 'Sair da viagem', + 'members.removeAccess': 'Remover acesso', + 'members.confirmLeave': 'Sair da viagem? Você perderá o acesso.', + 'members.confirmRemove': 'Remover o acesso deste usuário?', + 'members.loadError': 'Falha ao carregar membros', + 'members.added': 'adicionado', + 'members.addError': 'Falha ao adicionar', + 'members.removed': 'Membro removido', + 'members.removeError': 'Falha ao remover', +}; +export default members; diff --git a/shared/src/i18n/br/memories.ts b/shared/src/i18n/br/memories.ts new file mode 100644 index 00000000..c83f78f8 --- /dev/null +++ b/shared/src/i18n/br/memories.ts @@ -0,0 +1,83 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Fotos', + 'memories.notConnected': 'Immich não conectado', + 'memories.notConnectedHint': + 'Conecte sua instância Immich nas Configurações para ver suas fotos de viagem aqui.', + 'memories.notConnectedMultipleHint': + 'Conecte um destes provedores de fotos: {provider_names} nas Configurações para poder adicionar fotos a esta viagem.', + 'memories.noDates': 'Adicione datas à sua viagem para carregar fotos.', + 'memories.noPhotos': 'Nenhuma foto encontrada', + 'memories.noPhotosHint': + 'Nenhuma foto encontrada no Immich para o período desta viagem.', + 'memories.photosFound': 'fotos', + 'memories.fromOthers': 'de outros', + 'memories.sharePhotos': 'Compartilhar fotos', + 'memories.sharing': 'Compartilhando', + 'memories.reviewTitle': 'Revise suas fotos', + 'memories.reviewHint': + 'Clique nas fotos para excluí-las do compartilhamento.', + 'memories.shareCount': 'Compartilhar {count} fotos', + 'memories.providerUrl': 'URL do servidor', + 'memories.providerApiKey': 'Chave de API', + 'memories.providerUsername': 'Nome de usuário', + 'memories.providerPassword': 'Senha', + 'memories.providerOTP': 'Código MFA (se habilitado)', + 'memories.skipSSLVerification': 'Pular verificação de certificado SSL', + 'memories.immichAutoUpload': 'Espelhar fotos da jornada no Immich ao enviar', + 'memories.providerUrlHintSynology': + 'Inclua o caminho do aplicativo Photos na URL, ex. https://nas:5001/photo', + 'memories.testConnection': 'Testar conexão', + 'memories.testShort': 'Testar', + 'memories.testFirst': 'Teste a conexão primeiro', + 'memories.connected': 'Conectado', + 'memories.disconnected': 'Não conectado', + 'memories.connectionSuccess': 'Conectado ao Immich', + 'memories.connectionError': 'Não foi possível conectar ao Immich', + 'memories.saved': 'Configurações do {provider_name} salvas', + 'memories.providerDisconnectedBanner': + 'Sua conexão com {provider_name} foi perdida. Reconecte nas Configurações para ver as fotos.', + 'memories.saveError': + 'Não foi possível salvar as configurações de {provider_name}', + 'memories.addPhotos': 'Adicionar fotos', + 'memories.linkAlbum': 'Vincular álbum', + 'memories.selectAlbum': 'Selecionar álbum do Immich', + 'memories.selectAlbumMultiple': 'Selecionar álbum', + 'memories.noAlbums': 'Nenhum álbum encontrado', + 'memories.syncAlbum': 'Sincronizar álbum', + 'memories.unlinkAlbum': 'Desvincular', + 'memories.photos': 'fotos', + 'memories.selectPhotos': 'Selecionar fotos do Immich', + 'memories.selectPhotosMultiple': 'Selecionar fotos', + 'memories.selectHint': 'Toque nas fotos para selecioná-las.', + 'memories.selected': 'selecionadas', + 'memories.addSelected': 'Adicionar {count} fotos', + 'memories.alreadyAdded': 'Já adicionada', + 'memories.private': 'Privado', + 'memories.stopSharing': 'Parar de compartilhar', + 'memories.oldest': 'Mais antigas', + 'memories.newest': 'Mais recentes', + 'memories.allLocations': 'Todos os locais', + 'memories.tripDates': 'Datas da viagem', + 'memories.allPhotos': 'Todas as fotos', + 'memories.confirmShareTitle': 'Compartilhar com membros da viagem?', + 'memories.confirmShareHint': + '{count} fotos serão visíveis para todos os membros desta viagem. Você pode tornar fotos individuais privadas depois.', + 'memories.confirmShareButton': 'Compartilhar fotos', + 'memories.error.loadAlbums': 'Falha ao carregar álbuns', + 'memories.error.linkAlbum': 'Falha ao vincular álbum', + 'memories.error.unlinkAlbum': 'Falha ao desvincular álbum', + 'memories.error.syncAlbum': 'Falha ao sincronizar álbum', + 'memories.error.loadPhotos': 'Falha ao carregar fotos', + 'memories.error.addPhotos': 'Falha ao adicionar fotos', + 'memories.error.removePhoto': 'Falha ao remover foto', + 'memories.error.toggleSharing': 'Falha ao atualizar compartilhamento', + 'memories.saveRouteNotConfigured': + 'A rota de salvamento não está configurada para este provedor', + 'memories.testRouteNotConfigured': + 'A rota de teste não está configurada para este provedor', + 'memories.fillRequiredFields': + 'Por favor preencha todos os campos obrigatórios', +}; +export default memories; diff --git a/shared/src/i18n/br/nav.ts b/shared/src/i18n/br/nav.ts new file mode 100644 index 00000000..43276d98 --- /dev/null +++ b/shared/src/i18n/br/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Viagem', + 'nav.share': 'Compartilhar', + 'nav.settings': 'Configurações', + 'nav.admin': 'Admin', + 'nav.logout': 'Sair', + 'nav.lightMode': 'Modo claro', + 'nav.darkMode': 'Modo escuro', + 'nav.autoMode': 'Automático', + 'nav.administrator': 'Administrador', + 'nav.myTrips': 'Minhas viagens', + 'nav.profile': 'Perfil', + 'nav.bottomSettings': 'Configurações', + 'nav.bottomAdmin': 'Administração', + 'nav.bottomLogout': 'Sair', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/br/notif.ts b/shared/src/i18n/br/notif.ts new file mode 100644 index 00000000..1ada09ca --- /dev/null +++ b/shared/src/i18n/br/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Teste] Notificação', + 'notif.test.simple.text': 'Esta é uma notificação de teste simples.', + 'notif.test.boolean.text': 'Você aceita esta notificação de teste?', + 'notif.test.navigate.text': 'Clique abaixo para ir ao painel.', + 'notif.trip_invite.title': 'Convite para viagem', + 'notif.trip_invite.text': '{actor} convidou você para {trip}', + 'notif.booking_change.title': 'Reserva atualizada', + 'notif.booking_change.text': '{actor} atualizou uma reserva em {trip}', + 'notif.trip_reminder.title': 'Lembrete de viagem', + 'notif.trip_reminder.text': 'Sua viagem {trip} está chegando!', + 'notif.todo_due.title': 'Tarefa com vencimento', + 'notif.todo_due.text': '{todo} em {trip} vence em {due}', + 'notif.vacay_invite.title': 'Convite Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} convidou você para fundir planos de férias', + 'notif.photos_shared.title': 'Fotos compartilhadas', + 'notif.photos_shared.text': '{actor} compartilhou {count} foto(s) em {trip}', + 'notif.collab_message.title': 'Nova mensagem', + 'notif.collab_message.text': '{actor} enviou uma mensagem em {trip}', + 'notif.packing_tagged.title': 'Atribuição de bagagem', + 'notif.packing_tagged.text': '{actor} atribuiu você a {category} em {trip}', + 'notif.version_available.title': 'Nova versão disponível', + 'notif.version_available.text': 'TREK {version} está disponível', + 'notif.action.view_trip': 'Ver viagem', + 'notif.action.view_collab': 'Ver mensagens', + 'notif.action.view_packing': 'Ver bagagem', + 'notif.action.view_photos': 'Ver fotos', + 'notif.action.view_vacay': 'Ver Vacay', + 'notif.action.view_admin': 'Ir para admin', + 'notif.action.view': 'Ver', + 'notif.action.accept': 'Aceitar', + 'notif.action.decline': 'Recusar', + 'notif.generic.title': 'Notificação', + 'notif.generic.text': 'Você tem uma nova notificação', + 'notif.dev.unknown_event.title': '[DEV] Evento desconhecido', + 'notif.dev.unknown_event.text': + 'O tipo de evento "{event}" não está registrado em EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/br/notifications.ts b/shared/src/i18n/br/notifications.ts new file mode 100644 index 00000000..5389bc77 --- /dev/null +++ b/shared/src/i18n/br/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notificações', + 'notifications.markAllRead': 'Marcar tudo como lido', + 'notifications.deleteAll': 'Excluir tudo', + 'notifications.showAll': 'Ver todas as notificações', + 'notifications.empty': 'Sem notificações', + 'notifications.emptyDescription': 'Você está em dia!', + 'notifications.all': 'Todas', + 'notifications.unreadOnly': 'Não lidas', + 'notifications.markRead': 'Marcar como lido', + 'notifications.markUnread': 'Marcar como não lido', + 'notifications.delete': 'Excluir', + 'notifications.system': 'Sistema', + 'notifications.synologySessionCleared.title': 'Synology Photos desconectado', + 'notifications.synologySessionCleared.text': + 'Seu servidor ou conta foi alterado — vá para Configurações para testar sua conexão novamente.', + 'notifications.test.title': 'Notificação de teste de {actor}', + 'notifications.test.text': 'Esta é uma notificação de teste simples.', + 'notifications.test.booleanTitle': '{actor} solicita sua aprovação', + 'notifications.test.booleanText': 'Notificação de teste booleana.', + 'notifications.test.accept': 'Aprovar', + 'notifications.test.decline': 'Recusar', + 'notifications.test.navigateTitle': 'Confira algo', + 'notifications.test.navigateText': 'Notificação de teste de navegação.', + 'notifications.test.goThere': 'Ir lá', + 'notifications.test.adminTitle': 'Transmissão do admin', + 'notifications.test.adminText': + '{actor} enviou uma notificação de teste para todos os admins.', + 'notifications.test.tripTitle': '{actor} postou na sua viagem', + 'notifications.test.tripText': 'Notificação de teste para a viagem "{trip}".', + 'notifications.versionAvailable.title': 'Atualização disponível', + 'notifications.versionAvailable.text': 'TREK {version} já está disponível.', + 'notifications.versionAvailable.button': 'Ver detalhes', +}; +export default notifications; diff --git a/shared/src/i18n/br/oauth.ts b/shared/src/i18n/br/oauth.ts new file mode 100644 index 00000000..72aa5cb7 --- /dev/null +++ b/shared/src/i18n/br/oauth.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Viagens', + 'oauth.scope.group.places': 'Locais', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Bagagem', + 'oauth.scope.group.todos': 'Tarefas', + 'oauth.scope.group.budget': 'Orçamento', + 'oauth.scope.group.reservations': 'Reservas', + 'oauth.scope.group.collab': 'Colaboração', + 'oauth.scope.group.notifications': 'Notificações', + 'oauth.scope.group.vacay': 'Férias', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Clima', + 'oauth.scope.group.journey': 'Jornada', + 'oauth.scope.trips:read.label': 'Ver viagens e itinerários', + 'oauth.scope.trips:read.description': 'Ler viagens, dias, notas e membros', + 'oauth.scope.trips:write.label': 'Editar viagens e itinerários', + 'oauth.scope.trips:write.description': + 'Criar e atualizar viagens, dias, notas e gerenciar membros', + 'oauth.scope.trips:delete.label': 'Excluir viagens', + 'oauth.scope.trips:delete.description': + 'Excluir viagens permanentemente — esta ação é irreversível', + 'oauth.scope.trips:share.label': 'Gerenciar links de compartilhamento', + 'oauth.scope.trips:share.description': + 'Criar, atualizar e revogar links de compartilhamento públicos', + 'oauth.scope.places:read.label': 'Ver locais e dados do mapa', + 'oauth.scope.places:read.description': + 'Ler locais, atribuições de dias, tags e categorias', + 'oauth.scope.places:write.label': 'Gerenciar locais', + 'oauth.scope.places:write.description': + 'Criar, atualizar e excluir locais, atribuições e tags', + 'oauth.scope.atlas:read.label': 'Ver Atlas', + 'oauth.scope.atlas:read.description': + 'Ler países visitados, regiões e lista de desejos', + 'oauth.scope.atlas:write.label': 'Gerenciar Atlas', + 'oauth.scope.atlas:write.description': + 'Marcar países e regiões como visitados, gerenciar lista de desejos', + 'oauth.scope.packing:read.label': 'Ver listas de bagagem', + 'oauth.scope.packing:read.description': + 'Ler itens, malas e responsáveis por categoria', + 'oauth.scope.packing:write.label': 'Gerenciar listas de bagagem', + 'oauth.scope.packing:write.description': + 'Adicionar, atualizar, excluir, marcar e reordenar itens e malas', + 'oauth.scope.todos:read.label': 'Ver listas de tarefas', + 'oauth.scope.todos:read.description': + 'Ler tarefas da viagem e responsáveis por categoria', + 'oauth.scope.todos:write.label': 'Gerenciar listas de tarefas', + 'oauth.scope.todos:write.description': + 'Criar, atualizar, marcar, excluir e reordenar tarefas', + 'oauth.scope.budget:read.label': 'Ver orçamento', + 'oauth.scope.budget:read.description': + 'Ler itens de orçamento e detalhamento de despesas', + 'oauth.scope.budget:write.label': 'Gerenciar orçamento', + 'oauth.scope.budget:write.description': + 'Criar, atualizar e excluir itens de orçamento', + 'oauth.scope.reservations:read.label': 'Ver reservas', + 'oauth.scope.reservations:read.description': + 'Ler reservas e detalhes de acomodação', + 'oauth.scope.reservations:write.label': 'Gerenciar reservas', + 'oauth.scope.reservations:write.description': + 'Criar, atualizar, excluir e reordenar reservas', + 'oauth.scope.collab:read.label': 'Ver colaboração', + 'oauth.scope.collab:read.description': + 'Ler notas colaborativas, enquetes e mensagens', + 'oauth.scope.collab:write.label': 'Gerenciar colaboração', + 'oauth.scope.collab:write.description': + 'Criar, atualizar e excluir notas, enquetes e mensagens', + 'oauth.scope.notifications:read.label': 'Ver notificações', + 'oauth.scope.notifications:read.description': + 'Ler notificações e contagens não lidas', + 'oauth.scope.notifications:write.label': 'Gerenciar notificações', + 'oauth.scope.notifications:write.description': + 'Marcar notificações como lidas e respondê-las', + 'oauth.scope.vacay:read.label': 'Ver planos de férias', + 'oauth.scope.vacay:read.description': + 'Ler dados de planejamento de férias, entradas e estatísticas', + 'oauth.scope.vacay:write.label': 'Gerenciar planos de férias', + 'oauth.scope.vacay:write.description': + 'Criar e gerenciar entradas de férias, feriados e planos de equipe', + 'oauth.scope.geo:read.label': 'Mapas e geocodificação', + 'oauth.scope.geo:read.description': + 'Pesquisar locais, resolver URLs de mapa e geocodificar coordenadas', + 'oauth.scope.weather:read.label': 'Previsão do tempo', + 'oauth.scope.weather:read.description': + 'Obter previsão do tempo para locais e datas da viagem', + 'oauth.scope.journey:read.label': 'Ver jornadas', + 'oauth.scope.journey:read.description': + 'Ler jornadas, entradas e lista de colaboradores', + 'oauth.scope.journey:write.label': 'Gerenciar jornadas', + 'oauth.scope.journey:write.description': + 'Criar, atualizar e excluir jornadas e suas entradas', + 'oauth.scope.journey:share.label': 'Gerenciar links de jornadas', + 'oauth.scope.journey:share.description': + 'Criar, atualizar e revogar links de compartilhamento públicos para jornadas', +}; +export default oauth; diff --git a/shared/src/i18n/br/packing.ts b/shared/src/i18n/br/packing.ts new file mode 100644 index 00000000..2d16020a --- /dev/null +++ b/shared/src/i18n/br/packing.ts @@ -0,0 +1,185 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Lista de mala', + 'packing.empty': 'A lista de mala está vazia', + 'packing.import': 'Importar', + 'packing.importTitle': 'Importar lista de bagagem', + 'packing.importHint': + 'Um item por linha. Formato: Categoria, Nome, Peso (g), Bolsa, checked/unchecked (opcional)', + 'packing.importPlaceholder': + 'Higiene, Escova de dentes\nRoupas, Camisetas, 200\nDocumentos, Passaporte, , Mala de mão\nEletrônicos, Carregador, 50, Mala, checked', + 'packing.importCsv': 'Carregar CSV/TXT', + 'packing.importAction': 'Importar {count}', + 'packing.importSuccess': '{count} itens importados', + 'packing.importError': 'Falha na importação', + 'packing.importEmpty': 'Nenhum item para importar', + 'packing.progress': '{packed} de {total} na mala ({percent}%)', + 'packing.clearChecked': 'Remover {count} marcado(s)', + 'packing.clearCheckedShort': 'Remover {count}', + 'packing.suggestions': 'Sugestões', + 'packing.suggestionsTitle': 'Adicionar sugestões', + 'packing.allSuggested': 'Todas as sugestões adicionadas', + 'packing.allPacked': 'Tudo na mala!', + 'packing.addPlaceholder': 'Adicionar item...', + 'packing.categoryPlaceholder': 'Categoria...', + 'packing.saveAsTemplate': 'Salvar como modelo', + 'packing.templateName': 'Nome do modelo', + 'packing.templateSaved': 'Lista de bagagem salva como modelo', + 'packing.filterAll': 'Todos', + 'packing.filterOpen': 'Abertos', + 'packing.filterDone': 'Prontos', + 'packing.emptyTitle': 'A lista de mala está vazia', + 'packing.emptyHint': 'Adicione itens ou use as sugestões', + 'packing.emptyFiltered': 'Nenhum item corresponde ao filtro', + 'packing.menuRename': 'Renomear', + 'packing.menuCheckAll': 'Marcar todos', + 'packing.menuUncheckAll': 'Desmarcar todos', + 'packing.menuDeleteCat': 'Excluir categoria', + 'packing.noMembers': 'Nenhum membro na viagem', + 'packing.addItem': 'Adicionar item', + 'packing.addItemPlaceholder': 'Nome do item...', + 'packing.addCategory': 'Adicionar categoria', + 'packing.newCategoryPlaceholder': 'Nome da categoria (ex.: Roupas)', + 'packing.applyTemplate': 'Aplicar modelo', + 'packing.template': 'Modelo', + 'packing.templateApplied': '{count} itens adicionados do modelo', + 'packing.templateError': 'Falha ao aplicar modelo', + 'packing.bags': 'Malas', + 'packing.noBag': 'Sem mala', + 'packing.totalWeight': 'Peso total', + 'packing.bagName': 'Nome da mala...', + 'packing.addBag': 'Adicionar mala', + 'packing.changeCategory': 'Alterar categoria', + 'packing.confirm.clearChecked': 'Remover {count} item(ns) marcado(s)?', + 'packing.confirm.deleteCat': + 'Excluir a categoria "{name}" com {count} item(ns)?', + 'packing.defaultCategory': 'Outros', + 'packing.toast.saveError': 'Falha ao salvar', + 'packing.toast.deleteError': 'Falha ao excluir', + 'packing.toast.renameError': 'Falha ao renomear', + 'packing.toast.addError': 'Falha ao adicionar', + 'packing.suggestions.items': [ + { + name: 'Passaporte', + category: 'Documentos', + }, + { + name: 'Documento de identidade', + category: 'Documentos', + }, + { + name: 'Seguro viagem', + category: 'Documentos', + }, + { + name: 'Passagens aéreas', + category: 'Documentos', + }, + { + name: 'Cartão de crédito', + category: 'Finanças', + }, + { + name: 'Dinheiro', + category: 'Finanças', + }, + { + name: 'Visto', + category: 'Documentos', + }, + { + name: 'Camisetas', + category: 'Roupas', + }, + { + name: 'Calças', + category: 'Roupas', + }, + { + name: 'Roupa íntima', + category: 'Roupas', + }, + { + name: 'Meias', + category: 'Roupas', + }, + { + name: 'Jaqueta', + category: 'Roupas', + }, + { + name: 'Pijama', + category: 'Roupas', + }, + { + name: 'Traje de banho', + category: 'Roupas', + }, + { + name: 'Capa de chuva', + category: 'Roupas', + }, + { + name: 'Sapatos confortáveis', + category: 'Roupas', + }, + { + name: 'Escova de dentes', + category: 'Higiene', + }, + { + name: 'Creme dental', + category: 'Higiene', + }, + { + name: 'Shampoo', + category: 'Higiene', + }, + { + name: 'Desodorante', + category: 'Higiene', + }, + { + name: 'Protetor solar', + category: 'Higiene', + }, + { + name: 'Aparelho de barbear', + category: 'Higiene', + }, + { + name: 'Carregador', + category: 'Eletrônicos', + }, + { + name: 'Power bank', + category: 'Eletrônicos', + }, + { + name: 'Fones de ouvido', + category: 'Eletrônicos', + }, + { + name: 'Adaptador de viagem', + category: 'Eletrônicos', + }, + { + name: 'Câmera', + category: 'Eletrônicos', + }, + { + name: 'Medicamento para dor', + category: 'Saúde', + }, + { + name: 'Curativos', + category: 'Saúde', + }, + { + name: 'Desinfetante', + category: 'Saúde', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/br/pdf.ts b/shared/src/i18n/br/pdf.ts new file mode 100644 index 00000000..a890a14d --- /dev/null +++ b/shared/src/i18n/br/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Plano de viagem', + 'pdf.planned': 'Planejado', + 'pdf.costLabel': 'Custo (EUR)', + 'pdf.preview': 'Pré-visualização do PDF', + 'pdf.saveAsPdf': 'Salvar como PDF', +}; +export default pdf; diff --git a/shared/src/i18n/br/perm.ts b/shared/src/i18n/br/perm.ts new file mode 100644 index 00000000..6bdf6c03 --- /dev/null +++ b/shared/src/i18n/br/perm.ts @@ -0,0 +1,64 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Configurações de Permissões', + 'perm.subtitle': 'Controle quem pode realizar ações no aplicativo', + 'perm.saved': 'Configurações de permissões salvas', + 'perm.resetDefaults': 'Restaurar padrões', + 'perm.customized': 'personalizado', + 'perm.level.admin': 'Apenas administrador', + 'perm.level.tripOwner': 'Dono da viagem', + 'perm.level.tripMember': 'Membros da viagem', + 'perm.level.everybody': 'Todos', + 'perm.cat.trip': 'Gerenciamento de Viagens', + 'perm.cat.members': 'Gerenciamento de Membros', + 'perm.cat.files': 'Arquivos', + 'perm.cat.content': 'Conteúdo e Cronograma', + 'perm.cat.extras': 'Orçamento, Bagagem e Colaboração', + 'perm.action.trip_create': 'Criar viagens', + 'perm.action.trip_edit': 'Editar detalhes da viagem', + 'perm.action.trip_delete': 'Excluir viagens', + 'perm.action.trip_archive': 'Arquivar / desarquivar viagens', + 'perm.action.trip_cover_upload': 'Enviar imagem de capa', + 'perm.action.member_manage': 'Adicionar / remover membros', + 'perm.action.file_upload': 'Enviar arquivos', + 'perm.action.file_edit': 'Editar metadados do arquivo', + 'perm.action.file_delete': 'Excluir arquivos', + 'perm.action.place_edit': 'Adicionar / editar / excluir lugares', + 'perm.action.day_edit': 'Editar dias, notas e atribuições', + 'perm.action.reservation_edit': 'Gerenciar reservas', + 'perm.action.budget_edit': 'Gerenciar orçamento', + 'perm.action.packing_edit': 'Gerenciar listas de bagagem', + 'perm.action.collab_edit': 'Colaboração (notas, enquetes, chat)', + 'perm.action.share_manage': 'Gerenciar links de compartilhamento', + 'perm.actionHint.trip_create': 'Quem pode criar novas viagens', + 'perm.actionHint.trip_edit': + 'Quem pode alterar nome, datas, descrição e moeda da viagem', + 'perm.actionHint.trip_delete': 'Quem pode excluir permanentemente uma viagem', + 'perm.actionHint.trip_archive': + 'Quem pode arquivar ou desarquivar uma viagem', + 'perm.actionHint.trip_cover_upload': + 'Quem pode enviar ou alterar a imagem de capa', + 'perm.actionHint.member_manage': + 'Quem pode convidar ou remover membros da viagem', + 'perm.actionHint.file_upload': 'Quem pode enviar arquivos para uma viagem', + 'perm.actionHint.file_edit': + 'Quem pode editar descrições e links dos arquivos', + 'perm.actionHint.file_delete': + 'Quem pode mover arquivos para a lixeira ou excluí-los permanentemente', + 'perm.actionHint.place_edit': + 'Quem pode adicionar, editar ou excluir lugares', + 'perm.actionHint.day_edit': + 'Quem pode editar dias, notas dos dias e atribuições de lugares', + 'perm.actionHint.reservation_edit': + 'Quem pode criar, editar ou excluir reservas', + 'perm.actionHint.budget_edit': + 'Quem pode criar, editar ou excluir itens do orçamento', + 'perm.actionHint.packing_edit': + 'Quem pode gerenciar itens de bagagem e malas', + 'perm.actionHint.collab_edit': + 'Quem pode criar notas, enquetes e enviar mensagens', + 'perm.actionHint.share_manage': + 'Quem pode criar ou excluir links de compartilhamento públicos', +}; +export default perm; diff --git a/shared/src/i18n/br/photos.ts b/shared/src/i18n/br/photos.ts new file mode 100644 index 00000000..d0f6a098 --- /dev/null +++ b/shared/src/i18n/br/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Fotos', + 'photos.subtitle': '{count} fotos para {trip}', + 'photos.dropHere': 'Arraste fotos aqui...', + 'photos.dropHereActive': 'Arraste fotos aqui', + 'photos.captionForAll': 'Legenda (para todos)', + 'photos.captionPlaceholder': 'Legenda opcional...', + 'photos.addCaption': 'Adicionar legenda...', + 'photos.allDays': 'Todos os dias', + 'photos.noPhotos': 'Nenhuma foto ainda', + 'photos.uploadHint': 'Envie suas fotos de viagem', + 'photos.clickToSelect': 'ou clique para selecionar', + 'photos.linkPlace': 'Vincular lugar', + 'photos.noPlace': 'Sem lugar', + 'photos.uploadN': 'Enviar {n} foto(s)', + 'photos.linkDay': 'Vincular dia', + 'photos.noDay': 'Nenhum dia', + 'photos.dayLabel': 'Dia {number}', + 'photos.photoSelected': 'Foto selecionada', + 'photos.photosSelected': 'Fotos selecionadas', + 'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · até 30 fotos', +}; +export default photos; diff --git a/shared/src/i18n/br/places.ts b/shared/src/i18n/br/places.ts new file mode 100644 index 00000000..6519b72c --- /dev/null +++ b/shared/src/i18n/br/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Adicionar lugar/atividade', + 'places.importFile': 'Importar arquivo', + 'places.sidebarDrop': 'Solte para importar', + 'places.importFileHint': + 'Importe arquivos .gpx, .kml ou .kmz de ferramentas como Google My Maps, Google Earth ou um rastreador GPS.', + 'places.importFileDropHere': + 'Clique para selecionar um arquivo ou arraste e solte aqui', + 'places.importFileDropActive': 'Solte o arquivo para selecionar', + 'places.importFileUnsupported': + 'Tipo de arquivo não suportado. Use .gpx, .kml ou .kmz.', + 'places.importFileTooLarge': + 'O arquivo é muito grande. O tamanho máximo de upload é {maxMb} MB.', + 'places.importFileError': 'Importação falhou', + 'places.importAllSkipped': 'Todos os lugares já estavam na viagem.', + 'places.gpxImported': '{count} lugares importados do GPX', + 'places.gpxImportTypes': 'O que deseja importar?', + 'places.gpxImportWaypoints': 'Pontos de caminho', + 'places.gpxImportRoutes': 'Rotas', + 'places.gpxImportTracks': 'Trilhas (com geometria de percurso)', + 'places.gpxImportNoneSelected': 'Selecione pelo menos um tipo para importar.', + 'places.kmlImportTypes': 'O que deseja importar?', + 'places.kmlImportPoints': 'Pontos (Placemarks)', + 'places.kmlImportPaths': 'Caminhos (LineStrings)', + 'places.kmlImportNoneSelected': 'Selecione pelo menos um tipo.', + 'places.selectionCount': '{count} selecionado(s)', + 'places.deleteSelected': 'Excluir seleção', + 'places.kmlKmzImported': '{count} lugares importados de KMZ/KML', + 'places.urlResolved': 'Lugar importado da URL', + 'places.importList': 'Importar lista', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importados: {created} • Ignorados: {skipped}', + 'places.importGoogleList': 'Lista Google', + 'places.importNaverList': 'Lista Naver', + 'places.googleListHint': + 'Cole um link compartilhado de uma lista do Google Maps para importar todos os lugares.', + 'places.googleListImported': '{count} lugares importados de "{list}"', + 'places.googleListError': 'Falha ao importar lista do Google Maps', + 'places.naverListHint': + 'Cole um link compartilhado de uma lista do Naver Maps para importar todos os lugares.', + 'places.naverListImported': '{count} lugares importados de "{list}"', + 'places.naverListError': 'Falha ao importar lista do Naver Maps', + 'places.viewDetails': 'Ver detalhes', + 'places.assignToDay': 'Adicionar a qual dia?', + 'places.all': 'Todos', + 'places.unplanned': 'Não planejados', + 'places.filterTracks': 'Trilhas', + 'places.search': 'Buscar lugares...', + 'places.allCategories': 'Todas as categorias', + 'places.categoriesSelected': 'categorias', + 'places.clearFilter': 'Limpar filtro', + 'places.count': '{count} lugares', + 'places.countSingular': '1 lugar', + 'places.allPlanned': 'Todos os lugares estão planejados', + 'places.noneFound': 'Nenhum lugar encontrado', + 'places.editPlace': 'Editar lugar', + 'places.formName': 'Nome', + 'places.formNamePlaceholder': 'ex.: Torre Eiffel', + 'places.formDescription': 'Descrição', + 'places.formDescriptionPlaceholder': 'Breve descrição...', + 'places.formAddress': 'Endereço', + 'places.formAddressPlaceholder': 'Rua, cidade, país', + 'places.formLat': 'Latitude (ex.: -23.5505)', + 'places.formLng': 'Longitude (ex.: -46.6333)', + 'places.formCategory': 'Categoria', + 'places.noCategory': 'Sem categoria', + 'places.categoryNamePlaceholder': 'Nome da categoria', + 'places.formTime': 'Horário', + 'places.startTime': 'Início', + 'places.endTime': 'Fim', + 'places.endTimeBeforeStart': 'O horário de fim é antes do início', + 'places.timeCollision': 'Sobreposição de horário com:', + 'places.formWebsite': 'Site', + 'places.formNotes': 'Notas', + 'places.formNotesPlaceholder': 'Notas pessoais...', + 'places.formReservation': 'Reserva', + 'places.reservationNotesPlaceholder': + 'Notas da reserva, código de confirmação...', + 'places.mapsSearchPlaceholder': 'Buscar lugares...', + 'places.mapsSearchError': 'Falha na busca de lugares.', + 'places.loadingDetails': 'Carregando detalhes do lugar…', + 'places.osmHint': + 'Busca via OpenStreetMap (sem fotos, horários ou avaliações). Adicione uma chave Google nas configurações para detalhes completos.', + 'places.osmActive': + 'Busca via OpenStreetMap (sem fotos, avaliações ou horário de funcionamento). Adicione uma chave Google em Configurações para mais dados.', + 'places.categoryCreateError': 'Falha ao criar categoria', + 'places.nameRequired': 'Digite um nome', + 'places.saveError': 'Falha ao salvar', +}; +export default places; diff --git a/shared/src/i18n/br/planner.ts b/shared/src/i18n/br/planner.ts new file mode 100644 index 00000000..032a8cef --- /dev/null +++ b/shared/src/i18n/br/planner.ts @@ -0,0 +1,69 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Lugares', + 'planner.bookings': 'Reservas', + 'planner.packingList': 'Lista de mala', + 'planner.documents': 'Documentos', + 'planner.dayPlan': 'Plano do dia', + 'planner.reservations': 'Reservas', + 'planner.minTwoPlaces': + 'São necessários pelo menos 2 lugares com coordenadas', + 'planner.noGeoPlaces': 'Nenhum lugar com coordenadas disponível', + 'planner.routeCalculated': 'Rota calculada', + 'planner.routeCalcFailed': 'Não foi possível calcular a rota', + 'planner.routeError': 'Erro ao calcular a rota', + 'planner.icsExportFailed': 'Falha ao exportar ICS', + 'planner.routeOptimized': 'Rota otimizada', + 'planner.reservationUpdated': 'Reserva atualizada', + 'planner.reservationAdded': 'Reserva adicionada', + 'planner.confirmDeleteReservation': 'Excluir reserva?', + 'planner.reservationDeleted': 'Reserva excluída', + 'planner.days': 'Dias', + 'planner.allPlaces': 'Todos os lugares', + 'planner.totalPlaces': '{n} lugares no total', + 'planner.noDaysPlanned': 'Nenhum dia planejado ainda', + 'planner.editTrip': 'Editar viagem →', + 'planner.placeOne': '1 lugar', + 'planner.placeN': '{n} lugares', + 'planner.addNote': 'Adicionar nota', + 'planner.noEntries': 'Nenhuma entrada neste dia', + 'planner.addPlace': 'Adicionar lugar/atividade', + 'planner.addPlaceShort': '+ Adicionar lugar/atividade', + 'planner.resPending': 'Reserva pendente · ', + 'planner.resConfirmed': 'Reserva confirmada · ', + 'planner.notePlaceholder': 'Nota…', + 'planner.noteTimePlaceholder': 'Horário (opcional)', + 'planner.noteExamplePlaceholder': + 'ex.: metrô às 14:30 da estação central, barco do cais 7, pausa para almoço…', + 'planner.totalCost': 'Custo total', + 'planner.searchPlaces': 'Buscar lugares…', + 'planner.allCategories': 'Todas as categorias', + 'planner.noPlacesFound': 'Nenhum lugar encontrado', + 'planner.addFirstPlace': 'Adicionar primeiro lugar', + 'planner.noReservations': 'Nenhuma reserva', + 'planner.addFirstReservation': 'Adicionar primeira reserva', + 'planner.new': 'Novo', + 'planner.addToDay': '+ Dia', + 'planner.calculating': 'Calculando…', + 'planner.route': 'Rota', + 'planner.optimize': 'Otimizar', + 'planner.openGoogleMaps': 'Abrir no Google Maps', + 'planner.selectDayHint': + 'Selecione um dia na lista à esquerda para ver o plano do dia', + 'planner.noPlacesForDay': 'Nenhum lugar neste dia ainda', + 'planner.addPlacesLink': 'Adicionar lugares →', + 'planner.minTotal': 'mín. total', + 'planner.noReservation': 'Sem reserva', + 'planner.removeFromDay': 'Remover do dia', + 'planner.addToThisDay': 'Adicionar ao dia', + 'planner.overview': 'Visão geral', + 'planner.noDays': 'Nenhum dia ainda', + 'planner.editTripToAddDays': 'Edite a viagem para adicionar dias', + 'planner.dayCount': '{n} dias', + 'planner.clickToUnlock': 'Clique para desbloquear', + 'planner.keepPosition': 'Manter posição durante a otimização da rota', + 'planner.dayDetails': 'Detalhes do dia', + 'planner.dayN': 'Dia {n}', +}; +export default planner; diff --git a/shared/src/i18n/br/register.ts b/shared/src/i18n/br/register.ts new file mode 100644 index 00000000..1c595db8 --- /dev/null +++ b/shared/src/i18n/br/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'As senhas não coincidem', + 'register.passwordTooShort': 'A senha deve ter pelo menos 8 caracteres', + 'register.failed': 'Falha no cadastro', + 'register.getStarted': 'Começar', + 'register.subtitle': 'Crie uma conta e comece a planejar suas viagens.', + 'register.feature1': 'Viagens ilimitadas', + 'register.feature2': 'Mapa interativo', + 'register.feature3': 'Gerencie lugares e categorias', + 'register.feature4': 'Acompanhe reservas', + 'register.feature5': 'Listas de malas', + 'register.feature6': 'Fotos e arquivos', + 'register.createAccount': 'Criar conta', + 'register.startPlanning': 'Comece a planejar', + 'register.minChars': 'Mín. 6 caracteres', + 'register.confirmPassword': 'Confirmar senha', + 'register.repeatPassword': 'Repita a senha', + 'register.registering': 'Cadastrando...', + 'register.register': 'Cadastrar', + 'register.hasAccount': 'Já tem conta?', + 'register.signIn': 'Entrar', +}; +export default register; diff --git a/shared/src/i18n/br/reservations.ts b/shared/src/i18n/br/reservations.ts new file mode 100644 index 00000000..f47325b3 --- /dev/null +++ b/shared/src/i18n/br/reservations.ts @@ -0,0 +1,118 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Reservas', + 'reservations.empty': 'Nenhuma reserva ainda', + 'reservations.emptyHint': 'Adicione reservas de voos, hotéis e mais', + 'reservations.add': 'Adicionar reserva', + 'reservations.addManual': 'Reserva manual', + 'reservations.placeHint': + 'Dica: o ideal é criar reservas a partir de um lugar para vinculá-las ao plano do dia.', + 'reservations.confirmed': 'Confirmada', + 'reservations.pending': 'Pendente', + 'reservations.summary': '{confirmed} confirmada(s), {pending} pendente(s)', + 'reservations.fromPlan': 'Do plano', + 'reservations.showFiles': 'Mostrar arquivos', + 'reservations.editTitle': 'Editar reserva', + 'reservations.status': 'Status', + 'reservations.datetime': 'Data e hora', + 'reservations.startTime': 'Horário de início', + 'reservations.endTime': 'Horário de término', + 'reservations.date': 'Data', + 'reservations.time': 'Hora', + 'reservations.timeAlt': 'Hora (alternativa, ex.: 19:30)', + 'reservations.notes': 'Notas', + 'reservations.notesPlaceholder': 'Notas adicionais...', + 'reservations.meta.airline': 'Companhia aérea', + 'reservations.meta.flightNumber': 'Nº do voo', + 'reservations.meta.from': 'De', + 'reservations.meta.to': 'Para', + 'reservations.needsReview': 'Verificar', + 'reservations.needsReviewHint': + 'Aeroporto não pôde ser identificado automaticamente — confirme o local.', + 'reservations.searchLocation': 'Buscar estação, porto, endereço...', + 'reservations.meta.trainNumber': 'Nº do trem', + 'reservations.meta.platform': 'Plataforma', + 'reservations.meta.seat': 'Assento', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in até', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Hospedagem', + 'reservations.meta.pickAccommodation': 'Vincular à hospedagem', + 'reservations.meta.noAccommodation': 'Nenhuma', + 'reservations.meta.hotelPlace': 'Hospedagem', + 'reservations.meta.pickHotel': 'Selecionar hospedagem', + 'reservations.meta.fromDay': 'De', + 'reservations.meta.toDay': 'Até', + 'reservations.meta.selectDay': 'Selecionar dia', + 'reservations.type.flight': 'Voo', + 'reservations.type.hotel': 'Hospedagem', + 'reservations.type.restaurant': 'Restaurante', + 'reservations.type.train': 'Trem', + 'reservations.type.car': 'Carro', + 'reservations.type.cruise': 'Cruzeiro', + 'reservations.type.event': 'Evento', + 'reservations.type.tour': 'Passeio', + 'reservations.type.other': 'Outro', + 'reservations.confirm.delete': + 'Tem certeza de que deseja excluir a reserva "{name}"?', + 'reservations.confirm.deleteTitle': 'Excluir reserva?', + 'reservations.confirm.deleteBody': '"{name}" será excluído permanentemente.', + 'reservations.toast.updated': 'Reserva atualizada', + 'reservations.toast.removed': 'Reserva excluída', + 'reservations.toast.fileUploaded': 'Arquivo enviado', + 'reservations.toast.uploadError': 'Falha no envio', + 'reservations.newTitle': 'Nova reserva', + 'reservations.bookingType': 'Tipo de reserva', + 'reservations.titleLabel': 'Título', + 'reservations.titlePlaceholder': 'ex.: LATAM LA800, Hotel Copacabana...', + 'reservations.locationAddress': 'Local / endereço', + 'reservations.locationPlaceholder': 'Endereço, aeroporto, hotel...', + 'reservations.confirmationCode': 'Código da reserva', + 'reservations.confirmationPlaceholder': 'ex.: ABC12345', + 'reservations.day': 'Dia', + 'reservations.noDay': 'Sem dia', + 'reservations.place': 'Lugar', + 'reservations.noPlace': 'Sem lugar', + 'reservations.pendingSave': 'será salvo…', + 'reservations.uploading': 'Enviando...', + 'reservations.attachFile': 'Anexar arquivo', + 'reservations.linkExisting': 'Vincular arquivo existente', + 'reservations.toast.saveError': 'Falha ao salvar', + 'reservations.toast.updateError': 'Falha ao atualizar', + 'reservations.toast.deleteError': 'Falha ao excluir', + 'reservations.confirm.remove': 'Remover a reserva "{name}"?', + 'reservations.linkAssignment': 'Vincular à atribuição do dia', + 'reservations.pickAssignment': 'Selecione uma atribuição do seu plano...', + 'reservations.noAssignment': 'Sem vínculo (avulsa)', + 'reservations.price': 'Preço', + 'reservations.budgetCategory': 'Categoria de orçamento', + 'reservations.budgetCategoryPlaceholder': 'ex. Transporte, Acomodação', + 'reservations.budgetCategoryAuto': 'Automático (pelo tipo de reserva)', + 'reservations.budgetHint': + 'Uma entrada de orçamento será criada automaticamente ao salvar.', + 'reservations.departureDate': 'Partida', + 'reservations.arrivalDate': 'Chegada', + 'reservations.departureTime': 'Hora partida', + 'reservations.arrivalTime': 'Hora chegada', + 'reservations.pickupDate': 'Retirada', + 'reservations.returnDate': 'Devolução', + 'reservations.pickupTime': 'Hora retirada', + 'reservations.returnTime': 'Hora devolução', + 'reservations.endDate': 'Data final', + 'reservations.meta.departureTimezone': 'TZ partida', + 'reservations.meta.arrivalTimezone': 'TZ chegada', + 'reservations.span.departure': 'Partida', + 'reservations.span.arrival': 'Chegada', + 'reservations.span.inTransit': 'Em trânsito', + 'reservations.span.pickup': 'Retirada', + 'reservations.span.return': 'Devolução', + 'reservations.span.active': 'Ativo', + 'reservations.span.start': 'Início', + 'reservations.span.end': 'Fim', + 'reservations.span.ongoing': 'Em andamento', + 'reservations.validation.endBeforeStart': + 'A data/hora final deve ser posterior à data/hora inicial', + 'reservations.addBooking': 'Adicionar reserva', +}; +export default reservations; diff --git a/shared/src/i18n/br/settings.ts b/shared/src/i18n/br/settings.ts new file mode 100644 index 00000000..dfda0a8e --- /dev/null +++ b/shared/src/i18n/br/settings.ts @@ -0,0 +1,298 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Configurações', + 'settings.subtitle': 'Ajuste suas preferências pessoais', + 'settings.tabs.display': 'Exibição', + 'settings.tabs.map': 'Mapa', + 'settings.tabs.notifications': 'Notificações', + 'settings.tabs.integrations': 'Integrações', + 'settings.tabs.account': 'Conta', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Sobre', + 'settings.map': 'Mapa', + 'settings.mapTemplate': 'Modelo de mapa', + 'settings.mapTemplatePlaceholder.select': 'Selecione o modelo...', + 'settings.mapDefaultHint': 'Deixe vazio para OpenStreetMap (padrão)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL do modelo de blocos do mapa', + 'settings.mapProvider': 'Provedor de mapa', + 'settings.mapProviderHint': + 'Afeta os mapas do Planejador de Viagem e Diário. Atlas sempre usa Leaflet.', + 'settings.mapLeafletSubtitle': 'Clássico 2D, quaisquer blocos raster', + 'settings.mapMapboxSubtitle': 'Blocos vetoriais, prédios 3D & terreno', + 'settings.mapExperimental': 'Experimental', + 'settings.mapMapboxToken': 'Token de acesso Mapbox', + 'settings.mapMapboxTokenHint': 'Token público (pk.*) de', + 'settings.mapMapboxTokenLink': 'mapbox.com → Tokens de acesso', + 'settings.mapStyle': 'Estilo do mapa', + 'settings.mapStylePlaceholder': 'Selecionar um estilo Mapbox', + 'settings.mapStyleHint': 'Preset ou sua própria URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': 'Prédios 3D & terreno', + 'settings.map3dHint': + 'Inclinação + extrusões 3D reais de prédios — funciona em todo estilo, incluindo satélite.', + 'settings.mapHighQuality': 'Modo alta qualidade', + 'settings.mapHighQualityHint': + 'Antialiasing + projeção global para bordas mais nítidas e uma visão realista do mundo.', + 'settings.mapHighQualityWarning': + 'Pode afetar o desempenho em dispositivos menos potentes.', + 'settings.mapTipLabel': 'Dica:', + 'settings.mapTip': + 'Clique direito e arraste para girar/inclinar o mapa. Clique do meio para adicionar um local (o clique direito é reservado para rotação).', + 'settings.latitude': 'Latitude', + 'settings.longitude': 'Longitude', + 'settings.saveMap': 'Salvar mapa', + 'settings.apiKeys': 'Chaves de API', + 'settings.mapsKey': 'Chave da API Google Maps', + 'settings.mapsKeyHint': + 'Para busca de lugares. Requer Places API (New). Obtenha em console.cloud.google.com', + 'settings.weatherKey': 'Chave OpenWeatherMap', + 'settings.weatherKeyHint': + 'Para dados meteorológicos. Grátis em openweathermap.org/api', + 'settings.keyPlaceholder': 'Digite a chave...', + 'settings.configured': 'Configurada', + 'settings.saveKeys': 'Salvar chaves', + 'settings.display': 'Exibição', + 'settings.colorMode': 'Tema de cores', + 'settings.light': 'Claro', + 'settings.dark': 'Escuro', + 'settings.auto': 'Automático', + 'settings.language': 'Idioma', + 'settings.temperature': 'Unidade de temperatura', + 'settings.timeFormat': 'Formato de hora', + 'settings.blurBookingCodes': 'Ocultar códigos de reserva', + 'settings.notifications': 'Notificações', + 'settings.notifyTripInvite': 'Convites de viagem', + 'settings.notifyBookingChange': 'Alterações de reserva', + 'settings.notifyTripReminder': 'Lembretes de viagem', + 'settings.notifyTodoDue': 'Tarefa com vencimento', + 'settings.notifyVacayInvite': 'Convites de fusão Vacay', + 'settings.notifyPhotosShared': 'Fotos compartilhadas (Immich)', + 'settings.notifyCollabMessage': 'Mensagens de chat (Colab)', + 'settings.notifyPackingTagged': 'Lista de mala: atribuições', + 'settings.notifyWebhook': 'Notificações webhook', + 'settings.notificationsDisabled': + 'As notificações não estão configuradas. Peça a um administrador para ativar notificações por e-mail ou webhook.', + 'settings.notificationsActive': 'Canal ativo', + 'settings.notificationsManagedByAdmin': + 'Os eventos de notificação são configurados pelo administrador.', + 'settings.on': 'Ligado', + 'settings.off': 'Desligado', + 'settings.account': 'Conta', + 'settings.about': 'Sobre', + 'settings.about.reportBug': 'Reportar um bug', + 'settings.about.reportBugHint': 'Encontrou um problema? Nos avise', + 'settings.about.featureRequest': 'Solicitar recurso', + 'settings.about.featureRequestHint': 'Sugira um novo recurso', + 'settings.about.wikiHint': 'Documentação e guias', + 'settings.about.supporters.badge': 'Apoiadores Mensais', + 'settings.about.supporters.title': 'Companheiros de viagem do TREK', + 'settings.about.supporters.subtitle': + 'Enquanto você planeja sua próxima rota, essas pessoas planejam junto o futuro do TREK. A contribuição mensal delas vai direto para o desenvolvimento e horas reais investidas — para o TREK continuar Open Source.', + 'settings.about.supporters.since': 'apoiador desde {date}', + 'settings.about.supporters.tierEmpty': 'Seja o primeiro', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK é um planejador de viagens auto-hospedado que ajuda você a organizar suas viagens da primeira ideia à última lembrança. Planejamento diário, orçamento, listas de bagagem, fotos e muito mais — tudo em um só lugar, no seu próprio servidor.', + 'settings.about.madeWith': 'Feito com', + 'settings.about.madeBy': + 'por Maurice e uma crescente comunidade open-source.', + 'settings.username': 'Nome de usuário', + 'settings.email': 'E-mail', + 'settings.role': 'Função', + 'settings.roleAdmin': 'Administrador', + 'settings.oidcLinked': 'Vinculado a', + 'settings.changePassword': 'Alterar senha', + 'settings.currentPassword': 'Senha atual', + 'settings.currentPasswordRequired': 'A senha atual é obrigatória', + 'settings.newPassword': 'Nova senha', + 'settings.confirmPassword': 'Confirmar nova senha', + 'settings.updatePassword': 'Atualizar senha', + 'settings.passwordRequired': 'Informe a senha atual e a nova', + 'settings.passwordTooShort': 'A senha deve ter pelo menos 8 caracteres', + 'settings.passwordMismatch': 'As senhas não coincidem', + 'settings.passwordWeak': + 'A senha deve ter maiúscula, minúscula, número e um caractere especial', + 'settings.passwordChanged': 'Senha alterada com sucesso', + 'settings.deleteAccount': 'Excluir conta', + 'settings.deleteAccountTitle': 'Excluir sua conta?', + 'settings.deleteAccountWarning': + 'Sua conta e todas as viagens, lugares e arquivos serão excluídos permanentemente. Esta ação não pode ser desfeita.', + 'settings.deleteAccountConfirm': 'Excluir permanentemente', + 'settings.deleteBlockedTitle': 'Exclusão não permitida', + 'settings.deleteBlockedMessage': + 'Você é o único administrador. Promova outro usuário a administrador antes de excluir sua conta.', + 'settings.roleUser': 'Usuário', + 'settings.saveProfile': 'Salvar perfil', + 'settings.toast.mapSaved': 'Configurações do mapa salvas', + 'settings.toast.keysSaved': 'Chaves de API salvas', + 'settings.toast.displaySaved': 'Configurações de exibição salvas', + 'settings.toast.profileSaved': 'Perfil salvo', + 'settings.uploadAvatar': 'Enviar foto do perfil', + 'settings.removeAvatar': 'Remover foto do perfil', + 'settings.avatarUploaded': 'Foto do perfil atualizada', + 'settings.avatarRemoved': 'Foto do perfil removida', + 'settings.avatarError': 'Falha no envio', + 'settings.mfa.title': 'Autenticação em duas etapas (2FA)', + 'settings.mfa.description': + 'Adiciona uma segunda etapa ao entrar com e-mail e senha. Use um app autenticador (Google Authenticator, Authy, etc.).', + 'settings.mfa.requiredByPolicy': + 'O administrador exige autenticação em dois fatores. Configure um app autenticador abaixo antes de continuar.', + 'settings.mfa.backupTitle': 'Códigos de backup', + 'settings.mfa.backupDescription': + 'Use estes códigos únicos se perder acesso ao app autenticador.', + 'settings.mfa.backupWarning': + 'Salve estes códigos agora. Cada código pode ser usado apenas uma vez.', + 'settings.mfa.backupCopy': 'Copiar códigos', + 'settings.mfa.backupDownload': 'Baixar TXT', + 'settings.mfa.backupPrint': 'Imprimir / PDF', + 'settings.mfa.backupCopied': 'Códigos de backup copiados', + 'settings.mfa.enabled': 'O 2FA está ativado na sua conta.', + 'settings.mfa.disabled': 'O 2FA não está ativado.', + 'settings.mfa.setup': 'Configurar autenticador', + 'settings.mfa.scanQr': + 'Leia este QR code no app ou digite o segredo manualmente.', + 'settings.mfa.secretLabel': 'Chave secreta (entrada manual)', + 'settings.mfa.codePlaceholder': 'Código de 6 dígitos', + 'settings.mfa.enable': 'Ativar 2FA', + 'settings.mfa.cancelSetup': 'Cancelar', + 'settings.mfa.disableTitle': 'Desativar 2FA', + 'settings.mfa.disableHint': + 'Digite sua senha e um código atual do autenticador.', + 'settings.mfa.disable': 'Desativar 2FA', + 'settings.mfa.toastEnabled': 'Autenticação em duas etapas ativada', + 'settings.mfa.toastDisabled': 'Autenticação em duas etapas desativada', + 'settings.mfa.demoBlocked': 'Indisponível no modo demonstração', + 'settings.mcp.title': 'Configuração MCP', + 'settings.mcp.endpoint': 'Endpoint MCP', + 'settings.mcp.clientConfig': 'Configuração do cliente', + 'settings.mcp.clientConfigHint': + 'Substitua por um token de API da lista abaixo. O caminho para o npx pode precisar ser ajustado para o seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Substitua e pelas credenciais exibidas no cliente OAuth 2.1 criado acima. O mcp-remote abrirá seu navegador para concluir a autorização na primeira conexão. O caminho para o npx pode precisar ser ajustado para seu sistema (ex.: C:\\PROGRA~1\\nodejs\\npx.cmd no Windows).', + 'settings.mcp.copy': 'Copiar', + 'settings.mcp.copied': 'Copiado!', + 'settings.mcp.apiTokens': 'Tokens de API', + 'settings.mcp.createToken': 'Criar novo token', + 'settings.mcp.noTokens': + 'Nenhum token ainda. Crie um para conectar clientes MCP.', + 'settings.mcp.tokenCreatedAt': 'Criado em', + 'settings.mcp.tokenUsedAt': 'Usado em', + 'settings.mcp.deleteTokenTitle': 'Excluir token', + 'settings.mcp.deleteTokenMessage': + 'Este token deixará de funcionar imediatamente. Qualquer cliente MCP que o utilize perderá o acesso.', + 'settings.mcp.modal.createTitle': 'Criar token de API', + 'settings.mcp.modal.tokenName': 'Nome do token', + 'settings.mcp.modal.tokenNamePlaceholder': + 'ex.: Claude Desktop, Notebook do trabalho', + 'settings.mcp.modal.creating': 'Criando…', + 'settings.mcp.modal.create': 'Criar token', + 'settings.mcp.modal.createdTitle': 'Token criado', + 'settings.mcp.modal.createdWarning': + 'Este token será exibido apenas uma vez. Copie e guarde agora — não poderá ser recuperado.', + 'settings.mcp.modal.done': 'Concluído', + 'settings.mcp.toast.created': 'Token criado', + 'settings.mcp.toast.createError': 'Falha ao criar token', + 'settings.mcp.toast.deleted': 'Token excluído', + 'settings.mcp.toast.deleteError': 'Falha ao excluir token', + 'settings.mcp.apiTokensDeprecated': + 'Os tokens de API estão obsoletos e serão removidos em uma versão futura. Por favor, use Clientes OAuth 2.1.', + 'settings.oauth.clients': 'Clientes OAuth 2.1', + 'settings.oauth.clientsHint': + 'Registre clientes OAuth 2.1 para permitir que aplicações MCP de terceiros (Claude Web, Cursor, etc.) se conectem sem tokens estáticos.', + 'settings.oauth.createClient': 'Novo cliente', + 'settings.oauth.noClients': 'Nenhum cliente OAuth registrado.', + 'settings.oauth.clientId': 'ID do cliente', + 'settings.oauth.clientSecret': 'Segredo do cliente', + 'settings.oauth.deleteClient': 'Excluir cliente', + 'settings.oauth.deleteClientMessage': + 'Este cliente e todas as sessões ativas serão removidos permanentemente. Qualquer aplicação que o utilize perderá o acesso imediatamente.', + 'settings.oauth.rotateSecret': 'Renovar segredo', + 'settings.oauth.rotateSecretMessage': + 'Um novo segredo de cliente será gerado e todas as sessões existentes serão invalidadas imediatamente. Atualize sua aplicação antes de fechar esta janela.', + 'settings.oauth.rotateSecretConfirm': 'Renovar', + 'settings.oauth.rotateSecretConfirming': 'Renovando…', + 'settings.oauth.rotateSecretDoneTitle': 'Novo segredo gerado', + 'settings.oauth.rotateSecretDoneWarning': + 'Este segredo é exibido apenas uma vez. Copie-o agora e atualize sua aplicação — todas as sessões anteriores foram invalidadas.', + 'settings.oauth.activeSessions': 'Sessões OAuth ativas', + 'settings.oauth.sessionScopes': 'Escopos', + 'settings.oauth.sessionExpires': 'Expira', + 'settings.oauth.revoke': 'Revogar', + 'settings.oauth.revokeSession': 'Revogar sessão', + 'settings.oauth.revokeSessionMessage': + 'Isso revogará imediatamente o acesso desta sessão OAuth.', + 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth', + 'settings.oauth.modal.presets': 'Configurações rápidas', + 'settings.oauth.modal.clientName': 'Nome da aplicação', + 'settings.oauth.modal.clientNamePlaceholder': 'ex.: Claude Web, Meu app MCP', + 'settings.oauth.modal.redirectUris': 'URIs de redirecionamento', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Uma URI por linha. HTTPS obrigatório (localhost isento). Correspondência exata.', + 'settings.oauth.modal.scopes': 'Escopos permitidos', + 'settings.oauth.modal.scopesHint': + 'list_trips e get_trip_summary estão sempre disponíveis — sem escopo necessário. Permitem à IA descobrir IDs de viagem.', + 'settings.oauth.modal.selectAll': 'Selecionar tudo', + 'settings.oauth.modal.deselectAll': 'Desmarcar tudo', + 'settings.oauth.modal.creating': 'Registrando…', + 'settings.oauth.modal.create': 'Registrar cliente', + 'settings.oauth.modal.createdTitle': 'Cliente registrado', + 'settings.oauth.modal.createdWarning': + 'O segredo do cliente é exibido apenas uma vez. Copie-o agora — não pode ser recuperado.', + 'settings.oauth.toast.createError': 'Falha ao registrar cliente OAuth', + 'settings.oauth.toast.deleted': 'Cliente OAuth excluído', + 'settings.oauth.toast.deleteError': 'Falha ao excluir cliente OAuth', + 'settings.oauth.toast.revoked': 'Sessão revogada', + 'settings.oauth.toast.revokeError': 'Falha ao revogar sessão', + 'settings.oauth.toast.rotateError': 'Falha ao renovar segredo do cliente', + 'settings.oauth.modal.machineClient': + 'Cliente de máquina (sem login no navegador)', + 'settings.oauth.modal.machineClientHint': + 'Usa o grant client_credentials — sem URIs de redirecionamento. O token é emitido diretamente via client_id + client_secret e age como você dentro dos escopos selecionados.', + 'settings.oauth.modal.machineClientUsage': + 'Obter token: POST /oauth/token com grant_type=client_credentials, client_id e client_secret. Sem navegador, sem refresh token.', + 'settings.oauth.badge.machine': 'máquina', + 'settings.mustChangePassword': + 'Você deve alterar sua senha antes de continuar. Defina uma nova senha abaixo.', + 'settings.bookingLabels': 'Rótulos das rotas de reservas', + 'settings.bookingLabelsHint': + 'Mostra nomes de estações / aeroportos no mapa. Desativado, apenas o ícone aparece.', + 'settings.notifyVersionAvailable': 'Nova versão disponível', + 'settings.notificationPreferences.noChannels': + 'Nenhum canal de notificação configurado. Peça a um administrador para configurar notificações por e-mail ou webhook.', + 'settings.webhookUrl.label': 'URL do webhook', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Insira a URL do seu webhook do Discord, Slack ou personalizado para receber notificações.', + 'settings.webhookUrl.saved': 'URL do webhook salva', + 'settings.webhookUrl.test': 'Testar', + 'settings.webhookUrl.testSuccess': 'Webhook de teste enviado com sucesso', + 'settings.webhookUrl.testFailed': 'Falha no webhook de teste', + 'settings.ntfyUrl.topicLabel': 'Tópico Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL do servidor Ntfy (opcional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Insira seu tópico Ntfy para receber notificações push. Deixe o servidor em branco para usar o padrão configurado pelo seu administrador.', + 'settings.ntfyUrl.tokenLabel': 'Token de acesso (opcional)', + 'settings.ntfyUrl.tokenHint': 'Necessário para tópicos protegidos por senha.', + 'settings.ntfyUrl.saved': 'Configurações do Ntfy salvas', + 'settings.ntfyUrl.test': 'Testar', + 'settings.ntfyUrl.testSuccess': + 'Notificação de teste do Ntfy enviada com sucesso', + 'settings.ntfyUrl.testFailed': 'Falha na notificação de teste do Ntfy', + 'settings.ntfyUrl.tokenCleared': 'Token de acesso removido', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/br/share.ts b/shared/src/i18n/br/share.ts new file mode 100644 index 00000000..b4750f87 --- /dev/null +++ b/shared/src/i18n/br/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Link público', + 'share.linkHint': + 'Crie um link que qualquer pessoa pode usar para ver esta viagem sem fazer login. Somente leitura — sem edição possível.', + 'share.createLink': 'Criar link', + 'share.deleteLink': 'Excluir link', + 'share.createError': 'Não foi possível criar o link', + 'share.permMap': 'Mapa e plano', + 'share.permBookings': 'Reservas', + 'share.permPacking': 'Mala', + 'share.permBudget': 'Orçamento', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/br/shared.ts b/shared/src/i18n/br/shared.ts new file mode 100644 index 00000000..7f1a40cf --- /dev/null +++ b/shared/src/i18n/br/shared.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link expirado ou inválido', + 'shared.expiredHint': + 'Este link de viagem compartilhado não está mais ativo.', + 'shared.readOnly': 'Visualização somente leitura', + 'shared.tabPlan': 'Plano', + 'shared.tabBookings': 'Reservas', + 'shared.tabPacking': 'Bagagem', + 'shared.tabBudget': 'Orçamento', + 'shared.tabChat': 'Chat', + 'shared.days': 'dias', + 'shared.places': 'lugares', + 'shared.other': 'Outros', + 'shared.totalBudget': 'Orçamento total', + 'shared.messages': 'mensagens', + 'shared.sharedVia': 'Compartilhado via', + 'shared.confirmed': 'Confirmado', + 'shared.pending': 'Pendente', +}; +export default shared; diff --git a/shared/src/i18n/br/stats.ts b/shared/src/i18n/br/stats.ts new file mode 100644 index 00000000..7eaae0d4 --- /dev/null +++ b/shared/src/i18n/br/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Países', + 'stats.cities': 'Cidades', + 'stats.trips': 'Viagens', + 'stats.places': 'Lugares', + 'stats.worldProgress': 'Progresso no mundo', + 'stats.visited': 'visitados', + 'stats.remaining': 'restantes', + 'stats.visitedCountries': 'Países visitados', +}; +export default stats; diff --git a/shared/src/i18n/br/system_notice.ts b/shared/src/i18n/br/system_notice.ts new file mode 100644 index 00000000..819d4ceb --- /dev/null +++ b/shared/src/i18n/br/system_notice.ts @@ -0,0 +1,64 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Bem-vindo ao TREK', + 'system_notice.welcome_v1.body': + 'Seu planejador de viagens tudo-em-um. Crie roteiros, compartilhe viagens com amigos e fique organizado — online ou offline.', + 'system_notice.welcome_v1.cta_label': 'Planejar uma viagem', + 'system_notice.welcome_v1.hero_alt': + 'Destino de viagem pitoresco com a interface do TREK', + 'system_notice.welcome_v1.highlight_plan': + 'Roteiros dia a dia para qualquer viagem', + 'system_notice.welcome_v1.highlight_share': + 'Colabore com seus companheiros de viagem', + 'system_notice.welcome_v1.highlight_offline': 'Funciona offline no celular', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Aviso anterior', + 'system_notice.pager.next': 'Próximo aviso', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Ir para o aviso {n}', + 'system_notice.pager.position': 'Aviso {current} de {total}', + 'system_notice.v3_photos.title': 'Fotos foram movidas na versão 3.0', + 'system_notice.v3_photos.body': + '**Fotos** no Planejador de Viagens foram removidas. Suas fotos estão seguras — o TREK nunca modificou sua biblioteca Immich ou Synology.\n\nAs fotos agora vivem no addon **Journey**. Journey é opcional — se ainda não estiver disponível, peça ao seu admin para ativá-lo em Admin → Addons.', + 'system_notice.v3_journey.title': 'Conheça o Journey — diário de viagem', + 'system_notice.v3_journey.body': + 'Documente suas viagens como histórias ricas com cronologias, galerias de fotos e mapas interativos.', + 'system_notice.v3_journey.cta_label': 'Abrir Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Linha do tempo e galeria diária', + 'system_notice.v3_journey.highlight_photos': 'Importar do Immich ou Synology', + 'system_notice.v3_journey.highlight_share': + 'Compartilhar publicamente — sem login', + 'system_notice.v3_journey.highlight_export': + 'Exportar como álbum de fotos PDF', + 'system_notice.v3_features.title': 'Mais destaques na versão 3.0', + 'system_notice.v3_features.body': + 'Algumas outras novidades que vale a pena conhecer nesta versão.', + 'system_notice.v3_features.highlight_dashboard': + 'Redesign do painel mobile-first', + 'system_notice.v3_features.highlight_offline': + 'Modo offline completo como PWA', + 'system_notice.v3_features.highlight_search': + 'Autocompleção de lugares em tempo real', + 'system_notice.v3_features.highlight_import': + 'Importar lugares de arquivos KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: atualização OAuth 2.1', + 'system_notice.v3_mcp.body': + 'A integração MCP foi completamente reformulada. OAuth 2.1 agora é o método de autenticação recomendado. Tokens estáticos (trek_…) foram descontinuados e serão removidos em uma versão futura.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recomendado (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 escopos de permissão granulares', + 'system_notice.v3_mcp.highlight_deprecated': + 'Tokens estáticos trek_ descontinuados', + 'system_notice.v3_mcp.highlight_tools': + 'Conjunto de ferramentas e prompts expandido', + 'system_notice.v3_thankyou.title': 'Uma nota pessoal minha', + 'system_notice.v3_thankyou.body': + 'Antes de seguir em frente — quero fazer uma pausa.\n\nO TREK começou como um projeto paralelo que criei para minhas próprias viagens. Nunca imaginei que cresceria a ponto de 4.000 de vocês confiarem nele para planejar suas aventuras. Cada estrela, cada issue, cada pedido de recurso — eu leio todos, e eles me mantêm firme nas noites longas entre um trabalho em tempo integral e a universidade.\n\nQuero que saibam: o TREK sempre será open source, sempre self-hosted, sempre de vocês. Sem rastreamento, sem assinaturas, sem pegadinhas. Apenas uma ferramenta feita por alguém que ama viajar tanto quanto vocês.\n\nAgradecimento especial ao [jubnl](https://github.com/jubnl) — você se tornou um colaborador incrível. Muito do que torna a versão 3.0 especial tem a sua marca. Obrigado por acreditar neste projeto quando ele ainda era bem cru.\n\nE a cada um de vocês que reportou um bug, traduziu uma string, compartilhou o TREK com um amigo ou simplesmente o usou para planejar uma viagem — **obrigado**. Vocês são a razão de tudo isso existir.\n\nQue venham muitas mais aventuras juntos.\n\n— Maurice\n\n---\n\n[Junte-se à comunidade no Discord](https://discord.gg/7Q6M6jDwzf)\n\nSe o TREK torna suas viagens melhores, um [cafezinho](https://ko-fi.com/mauriceboe) sempre mantém as luzes acesas.', + 'system_notice.v3014_whitespace_collision.title': + 'Ação necessária: conflito de conta de usuário', + 'system_notice.v3014_whitespace_collision.body': + 'A atualização 3.0.14 detectou um ou mais conflitos de nome de usuário ou e-mail causados por espaços em branco no início ou fim dos valores armazenados. As contas afetadas foram renomeadas automaticamente. Verifique os logs do servidor por linhas começando com **[migration] WHITESPACE COLLISION** para identificar quais contas precisam de revisão.', +}; +export default system_notice; diff --git a/shared/src/i18n/br/todo.ts b/shared/src/i18n/br/todo.ts new file mode 100644 index 00000000..53a3133d --- /dev/null +++ b/shared/src/i18n/br/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Lista de bagagem', + 'todo.subtab.todo': 'A fazer', + 'todo.completed': 'concluído(s)', + 'todo.filter.all': 'Todos', + 'todo.filter.open': 'Aberto', + 'todo.filter.done': 'Concluído', + 'todo.uncategorized': 'Sem categoria', + 'todo.namePlaceholder': 'Nome da tarefa', + 'todo.descriptionPlaceholder': 'Descrição (opcional)', + 'todo.unassigned': 'Não atribuído', + 'todo.noCategory': 'Sem categoria', + 'todo.hasDescription': 'Com descrição', + 'todo.addItem': 'Nova tarefa', + 'todo.sidebar.sortBy': 'Ordenar por', + 'todo.priority': 'Prioridade', + 'todo.newCategoryLabel': 'nova', + 'todo.newCategory': 'Nome da categoria', + 'todo.addCategory': 'Adicionar categoria', + 'todo.newItem': 'Nova tarefa', + 'todo.empty': 'Nenhuma tarefa ainda. Adicione uma tarefa para começar!', + 'todo.filter.my': 'Minhas tarefas', + 'todo.filter.overdue': 'Atrasada', + 'todo.sidebar.tasks': 'Tarefas', + 'todo.sidebar.categories': 'Categorias', + 'todo.detail.title': 'Tarefa', + 'todo.detail.description': 'Descrição', + 'todo.detail.category': 'Categoria', + 'todo.detail.dueDate': 'Data de vencimento', + 'todo.detail.assignedTo': 'Atribuído a', + 'todo.detail.delete': 'Excluir', + 'todo.detail.save': 'Salvar alterações', + 'todo.detail.create': 'Criar tarefa', + 'todo.detail.priority': 'Prioridade', + 'todo.detail.noPriority': 'Nenhuma', + 'todo.sortByPrio': 'Prioridade', +}; +export default todo; diff --git a/shared/src/i18n/br/transport.ts b/shared/src/i18n/br/transport.ts new file mode 100644 index 00000000..3a78d361 --- /dev/null +++ b/shared/src/i18n/br/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Adicionar transporte', + 'transport.modalTitle.create': 'Adicionar transporte', + 'transport.modalTitle.edit': 'Editar transporte', + 'transport.title': 'Transportes', + 'transport.addManual': 'Transporte Manual', +}; +export default transport; diff --git a/shared/src/i18n/br/trip.ts b/shared/src/i18n/br/trip.ts new file mode 100644 index 00000000..d725a110 --- /dev/null +++ b/shared/src/i18n/br/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plano', + 'trip.tabs.transports': 'Transportes', + 'trip.tabs.reservations': 'Reservas', + 'trip.tabs.reservationsShort': 'Reservas', + 'trip.tabs.packing': 'Lista de mala', + 'trip.tabs.packingShort': 'Mala', + 'trip.tabs.lists': 'Listas', + 'trip.tabs.listsShort': 'Listas', + 'trip.tabs.budget': 'Orçamento', + 'trip.tabs.files': 'Arquivos', + 'trip.loading': 'Carregando viagem...', + 'trip.mobilePlan': 'Plano', + 'trip.mobilePlaces': 'Lugares', + 'trip.toast.placeUpdated': 'Lugar atualizado', + 'trip.toast.placeAdded': 'Lugar adicionado', + 'trip.toast.placeDeleted': 'Lugar excluído', + 'trip.toast.selectDay': 'Selecione um dia primeiro', + 'trip.toast.assignedToDay': 'Lugar atribuído ao dia', + 'trip.toast.reorderError': 'Falha ao reordenar', + 'trip.toast.reservationUpdated': 'Reserva atualizada', + 'trip.toast.reservationAdded': 'Reserva adicionada', + 'trip.toast.deleted': 'Excluído', + 'trip.confirm.deletePlace': 'Tem certeza de que deseja excluir este lugar?', + 'trip.confirm.deletePlaces': 'Excluir {count} lugares?', + 'trip.toast.placesDeleted': '{count} lugares excluídos', + 'trip.loadingPhotos': 'Carregando fotos dos lugares...', +}; +export default trip; diff --git a/shared/src/i18n/br/trips.ts b/shared/src/i18n/br/trips.ts new file mode 100644 index 00000000..a027c8fd --- /dev/null +++ b/shared/src/i18n/br/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.reminder': 'Lembrete', + 'trips.reminderNone': 'Nenhum', + 'trips.reminderDay': 'dia', + 'trips.reminderDays': 'dias', + 'trips.reminderCustom': 'Personalizado', + 'trips.memberRemoved': '{username} removido', + 'trips.memberRemoveError': 'Falha ao remover', + 'trips.memberAdded': '{username} adicionado', + 'trips.memberAddError': 'Falha ao adicionar', + 'trips.reminderDaysBefore': 'dias antes da partida', + 'trips.reminderDisabledHint': + 'Os lembretes de viagem estão desativados. Ative-os em Admin > Configurações > Notificações.', +}; +export default trips; diff --git a/shared/src/i18n/br/undo.ts b/shared/src/i18n/br/undo.ts new file mode 100644 index 00000000..b9377e52 --- /dev/null +++ b/shared/src/i18n/br/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Desfazer', + 'undo.tooltip': 'Desfazer: {action}', + 'undo.assignPlace': 'Local atribuído ao dia', + 'undo.removeAssignment': 'Local removido do dia', + 'undo.reorder': 'Locais reordenados', + 'undo.optimize': 'Rota otimizada', + 'undo.deletePlace': 'Local excluído', + 'undo.deletePlaces': 'Lugares excluídos', + 'undo.moveDay': 'Local movido para outro dia', + 'undo.lock': 'Bloqueio do local alternado', + 'undo.importGpx': 'Importação de GPX', + 'undo.importKeyholeMarkup': 'Importação de KMZ/KML', + 'undo.importGoogleList': 'Importação do Google Maps', + 'undo.importNaverList': 'Importação do Naver Maps', + 'undo.addPlace': 'Local adicionado', + 'undo.done': 'Desfeito: {action}', +}; +export default undo; diff --git a/shared/src/i18n/br/vacay.ts b/shared/src/i18n/br/vacay.ts new file mode 100644 index 00000000..038d29d3 --- /dev/null +++ b/shared/src/i18n/br/vacay.ts @@ -0,0 +1,108 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Planeje e gerencie dias de férias', + 'vacay.settings': 'Configurações', + 'vacay.year': 'Ano', + 'vacay.addYear': 'Adicionar próximo ano', + 'vacay.addPrevYear': 'Adicionar ano anterior', + 'vacay.removeYear': 'Remover ano', + 'vacay.removeYearConfirm': 'Remover {year}?', + 'vacay.removeYearHint': + 'Todas as entradas de férias e feriados da empresa deste ano serão excluídas permanentemente.', + 'vacay.remove': 'Remover', + 'vacay.persons': 'Pessoas', + 'vacay.noPersons': 'Nenhuma pessoa adicionada', + 'vacay.addPerson': 'Adicionar pessoa', + 'vacay.editPerson': 'Editar pessoa', + 'vacay.removePerson': 'Remover pessoa', + 'vacay.removePersonConfirm': 'Remover {name}?', + 'vacay.removePersonHint': + 'Todas as entradas de férias desta pessoa serão excluídas permanentemente.', + 'vacay.personName': 'Nome', + 'vacay.personNamePlaceholder': 'Digite o nome', + 'vacay.color': 'Cor', + 'vacay.add': 'Adicionar', + 'vacay.legend': 'Legenda', + 'vacay.publicHoliday': 'Feriado nacional', + 'vacay.companyHoliday': 'Feriado da empresa', + 'vacay.weekend': 'Fim de semana', + 'vacay.modeVacation': 'Férias', + 'vacay.modeCompany': 'Feriado da empresa', + 'vacay.entitlement': 'Direito', + 'vacay.entitlementDays': 'Dias', + 'vacay.used': 'Usados', + 'vacay.remaining': 'Restantes', + 'vacay.carriedOver': 'de {year}', + 'vacay.blockWeekends': 'Bloquear fins de semana', + 'vacay.weekendDays': 'Dias de fim de semana', + 'vacay.mon': 'Seg', + 'vacay.tue': 'Ter', + 'vacay.wed': 'Qua', + 'vacay.thu': 'Qui', + 'vacay.fri': 'Sex', + 'vacay.sat': 'Sáb', + 'vacay.sun': 'Dom', + 'vacay.blockWeekendsHint': + 'Impedir entradas de férias aos sábados e domingos', + 'vacay.publicHolidays': 'Feriados nacionais', + 'vacay.publicHolidaysHint': 'Marcar feriados nacionais no calendário', + 'vacay.selectCountry': 'Selecione o país', + 'vacay.selectRegion': 'Selecione a região (opcional)', + 'vacay.addCalendar': 'Adicionar calendário', + 'vacay.calendarLabel': 'Rótulo (opcional)', + 'vacay.calendarColor': 'Cor', + 'vacay.noCalendars': 'Nenhum calendário de feriados adicionado ainda', + 'vacay.companyHolidays': 'Feriados da empresa', + 'vacay.companyHolidaysHint': + 'Permitir marcar dias de feriado em toda a empresa', + 'vacay.companyHolidaysNoDeduct': + 'Feriados da empresa não contam como dias de férias.', + 'vacay.weekStart': 'Semana começa em', + 'vacay.weekStartHint': + 'Escolha se a semana começa na segunda-feira ou no domingo', + 'vacay.carryOver': 'Acúmulo', + 'vacay.carryOverHint': + 'Levar automaticamente os dias de férias restantes para o ano seguinte', + 'vacay.sharing': 'Compartilhamento', + 'vacay.sharingHint': + 'Compartilhe seu plano de férias com outros usuários do TREK', + 'vacay.owner': 'Proprietário', + 'vacay.shareEmailPlaceholder': 'E-mail do usuário TREK', + 'vacay.shareSuccess': 'Plano compartilhado com sucesso', + 'vacay.shareError': 'Não foi possível compartilhar o plano', + 'vacay.dissolve': 'Encerrar fusão', + 'vacay.dissolveHint': + 'Separar os calendários novamente. Suas entradas serão mantidas.', + 'vacay.dissolveAction': 'Encerrar', + 'vacay.dissolved': 'Calendário separado', + 'vacay.fusedWith': 'Fundido com', + 'vacay.you': 'você', + 'vacay.noData': 'Sem dados', + 'vacay.changeColor': 'Alterar cor', + 'vacay.inviteUser': 'Convidar usuário', + 'vacay.inviteHint': + 'Convide outro usuário TREK para compartilhar um calendário de férias combinado.', + 'vacay.selectUser': 'Selecionar usuário', + 'vacay.sendInvite': 'Enviar convite', + 'vacay.inviteSent': 'Convite enviado', + 'vacay.inviteError': 'Não foi possível enviar o convite', + 'vacay.pending': 'pendente', + 'vacay.noUsersAvailable': 'Nenhum usuário disponível', + 'vacay.accept': 'Aceitar', + 'vacay.decline': 'Recusar', + 'vacay.acceptFusion': 'Aceitar e fundir', + 'vacay.inviteTitle': 'Pedido de fusão', + 'vacay.inviteWantsToFuse': + 'quer compartilhar um calendário de férias com você.', + 'vacay.fuseInfo1': + 'Ambos verão todas as entradas de férias em um calendário compartilhado.', + 'vacay.fuseInfo2': 'Ambos podem criar e editar entradas um do outro.', + 'vacay.fuseInfo3': + 'Ambos podem excluir entradas e alterar direitos de férias.', + 'vacay.fuseInfo4': + 'Configurações como feriados nacionais e da empresa são compartilhadas.', + 'vacay.fuseInfo5': + 'A fusão pode ser encerrada a qualquer momento por qualquer parte. Suas entradas serão preservadas.', +}; +export default vacay; diff --git a/shared/src/i18n/cs/admin.ts b/shared/src/i18n/cs/admin.ts new file mode 100644 index 00000000..c7fa9742 --- /dev/null +++ b/shared/src/i18n/cs/admin.ts @@ -0,0 +1,362 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Oznámení', + 'admin.notifications.hint': + 'Vyberte kanál oznámení. Současně může být aktivní pouze jeden.', + 'admin.notifications.none': 'Vypnuto', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Uložit nastavení oznámení', + 'admin.notifications.saved': 'Nastavení oznámení uloženo', + 'admin.notifications.testWebhook': 'Odeslat testovací webhook', + 'admin.notifications.testWebhookSuccess': 'Testovací webhook úspěšně odeslán', + 'admin.notifications.testWebhookFailed': + 'Odeslání testovacího webhooku se nezdařilo', + 'admin.smtp.title': 'E-mail a oznámení', + 'admin.smtp.hint': 'Konfigurace SMTP pro odesílání e-mailových oznámení.', + 'admin.smtp.testButton': 'Odeslat testovací e-mail', + 'admin.webhook.hint': + 'Odesílat oznámení na externí webhook (Discord, Slack atd.).', + 'admin.smtp.testSuccess': 'Testovací e-mail byl úspěšně odeslán', + 'admin.smtp.testFailed': 'Odeslání testovacího e-mailu se nezdařilo', + 'admin.title': 'Administrace', + 'admin.subtitle': 'Správa uživatelů a systémová nastavení', + 'admin.tabs.users': 'Uživatelé', + 'admin.tabs.categories': 'Kategorie', + 'admin.tabs.backup': 'Zálohování', + 'admin.stats.users': 'Uživatelé', + 'admin.stats.trips': 'Cesty', + 'admin.stats.places': 'Místa', + 'admin.stats.photos': 'Fotky', + 'admin.stats.files': 'Soubory', + 'admin.table.user': 'Uživatel', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Role', + 'admin.table.created': 'Vytvořeno', + 'admin.table.lastLogin': 'Poslední přihlášení', + 'admin.table.actions': 'Akce', + 'admin.you': '(Vy)', + 'admin.editUser': 'Upravit uživatele', + 'admin.newPassword': 'Nové heslo', + 'admin.newPasswordHint': 'Ponechte prázdné pro zachování současného hesla', + 'admin.deleteUser': + 'Smazat uživatele „{name}“? Všechny jeho cesty budou trvale smazány.', + 'admin.deleteUserTitle': 'Smazat uživatele', + 'admin.newPasswordPlaceholder': 'Zadejte nové heslo…', + 'admin.toast.loadError': 'Nepodařilo se načíst data administrace', + 'admin.toast.userUpdated': 'Uživatel byl aktualizován', + 'admin.toast.updateError': 'Aktualizace se nezdařila', + 'admin.toast.userDeleted': 'Uživatel byl smazán', + 'admin.toast.deleteError': 'Smazání se nezdařilo', + 'admin.toast.cannotDeleteSelf': 'Nemůžete smazat svůj vlastní účet', + 'admin.toast.userCreated': 'Uživatel byl vytvořen', + 'admin.toast.createError': 'Nepodařilo se vytvořit uživatele', + 'admin.toast.fieldsRequired': + 'Uživatelské jméno, e-mail a heslo jsou povinné', + 'admin.createUser': 'Vytvořit uživatele', + 'admin.invite.title': 'Pozvánky', + 'admin.invite.subtitle': 'Vytvářejte jednorázové registrační odkazy', + 'admin.invite.create': 'Vytvořit odkaz', + 'admin.invite.createAndCopy': 'Vytvořit a zkopírovat', + 'admin.invite.empty': 'Zatím nebyly vytvořeny žádné pozvánky', + 'admin.invite.maxUses': 'Max. použití', + 'admin.invite.expiry': 'Vyprší za', + 'admin.invite.uses': 'použito', + 'admin.invite.expiresAt': 'vyprší', + 'admin.invite.createdBy': 'vytvořil', + 'admin.invite.active': 'Aktivní', + 'admin.invite.expired': 'Expirované', + 'admin.invite.usedUp': 'Využito', + 'admin.invite.copied': 'Odkaz byl zkopírován do schránky', + 'admin.invite.copyLink': 'Kopírovat odkaz', + 'admin.invite.deleted': 'Pozvánka smazána', + 'admin.invite.createError': 'Nepodařilo se vytvořit pozvánku', + 'admin.invite.deleteError': 'Nepodařilo se smazat pozvánku', + 'admin.tabs.settings': 'Nastavení', + 'admin.allowRegistration': 'Povolit registraci', + 'admin.allowRegistrationHint': 'Noví uživatelé se mohou sami registrovat', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Vyžadovat dvoufázové ověření (2FA)', + 'admin.requireMfaHint': + 'Uživatelé bez 2FA musí dokončit nastavení v Nastavení před použitím aplikace.', + 'admin.apiKeys': 'API klíče', + 'admin.apiKeysHint': + 'Volitelné. Povoluje rozšířená data o místech (fotky, počasí).', + 'admin.mapsKey': 'Google Maps API klíč', + 'admin.mapsKeyHint': + 'Povinné pro hledání míst. Získáte na console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Bez API klíče se pro hledání používá OpenStreetMap. S Google klíčem lze načítat fotky, hodnocení a otevírací dobu.', + 'admin.recommended': 'Doporučeno', + 'admin.weatherKey': 'OpenWeatherMap API klíč', + 'admin.weatherKeyHint': 'Pro data o počasí. Zdarma na openweathermap.org', + 'admin.validateKey': 'Testovat', + 'admin.keyValid': 'Připojeno', + 'admin.keyInvalid': 'Neplatný', + 'admin.keySaved': 'API klíče byly uloženy', + 'admin.oidcTitle': 'Jednotné přihlášení (OIDC)', + 'admin.oidcSubtitle': + 'Povolit přihlášení přes externí poskytovatele (Google, Apple, Authentik, Keycloak).', + 'admin.oidcDisplayName': 'Zobrazované jméno', + 'admin.oidcIssuer': 'URL vydavatele (Issuer)', + 'admin.oidcIssuerHint': + 'OpenID Connect Issuer URL, např. https://accounts.google.com', + 'admin.oidcSaved': 'Konfigurace OIDC uložena', + 'admin.oidcOnlyMode': 'Zakázat ověřování heslem', + 'admin.oidcOnlyModeHint': + 'Pokud je zapnuto, je povolen pouze SSO login. Registrace i přihlášení heslem budou zablokovány.', + 'admin.fileTypes': 'Povolené typy souborů', + 'admin.fileTypesHint': + 'Nastavte, které typy souborů mohou uživatelé nahrávat.', + 'admin.fileTypesFormat': + 'Přípony oddělené čárkou (např. jpg,png,pdf,doc). Použijte * pro všechny typy.', + 'admin.fileTypesSaved': 'Nastavení souborů uloženo', + 'admin.placesPhotos.title': 'Fotografie míst', + 'admin.placesPhotos.subtitle': + 'Načítání fotografií z Google Places API. Zakázáním ušetříte kvótu API. Fotografie z Wikimedia nejsou ovlivněny.', + 'admin.placesAutocomplete.title': 'Automatické doplňování míst', + 'admin.placesAutocomplete.subtitle': + 'Použití Google Places API pro návrhy vyhledávání. Zakázáním ušetříte kvótu API.', + 'admin.placesDetails.title': 'Podrobnosti o místě', + 'admin.placesDetails.subtitle': + 'Načítání podrobných informací o místě (hodiny, hodnocení, web) z Google Places API. Zakázáním ušetříte kvótu API.', + 'admin.bagTracking.title': 'Sledování zavazadel', + 'admin.bagTracking.subtitle': + 'Povolit váhu a přiřazení k zavazadlům u položek balení', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Zasílání zpráv v reálném čase', + 'admin.collab.notes.title': 'Poznámky', + 'admin.collab.notes.subtitle': 'Sdílené poznámky a dokumenty', + 'admin.collab.polls.title': 'Ankety', + 'admin.collab.polls.subtitle': 'Skupinové ankety a hlasování', + 'admin.collab.whatsnext.title': 'Co dál', + 'admin.collab.whatsnext.subtitle': 'Návrhy aktivit a další kroky', + 'admin.tabs.config': 'Personalizace', + 'admin.tabs.defaults': 'Výchozí nastavení uživatele', + 'admin.defaultSettings.title': 'Výchozí nastavení uživatele', + 'admin.defaultSettings.description': + 'Nastavte výchozí hodnoty pro celou instanci. Uživatelé, kteří nezměnili nastavení, uvidí tyto hodnoty. Jejich vlastní změny mají vždy přednost.', + 'admin.defaultSettings.saved': 'Výchozí nastavení uloženo', + 'admin.defaultSettings.reset': 'Obnovit na vestavěnou výchozí hodnotu', + 'admin.defaultSettings.resetToBuiltIn': 'obnovit', + 'admin.tabs.templates': 'Šablony seznamů', + 'admin.packingTemplates.title': 'Šablony pro balení', + 'admin.packingTemplates.subtitle': + 'Vytvářejte opakovaně použitelné seznamy pro své cesty', + 'admin.packingTemplates.create': 'Nová šablona', + 'admin.packingTemplates.namePlaceholder': + 'Název šablony (např. Dovolená u moře)', + 'admin.packingTemplates.empty': 'Zatím nejsou vytvořeny žádné šablony', + 'admin.packingTemplates.items': 'položek', + 'admin.packingTemplates.categories': 'kategorií', + 'admin.packingTemplates.itemName': 'Název položky', + 'admin.packingTemplates.itemCategory': 'Kategorie', + 'admin.packingTemplates.categoryName': 'Název kategorie (např. Oblečení)', + 'admin.packingTemplates.addCategory': 'Přidat kategorii', + 'admin.packingTemplates.created': 'Šablona vytvořena', + 'admin.packingTemplates.deleted': 'Šablona smazána', + 'admin.packingTemplates.loadError': 'Nepodařilo se načíst šablony', + 'admin.packingTemplates.createError': 'Nepodařilo se vytvořit šablonu', + 'admin.packingTemplates.deleteError': 'Nepodařilo se smazat šablonu', + 'admin.packingTemplates.saveError': 'Uložení se nezdařilo', + 'admin.tabs.addons': 'Doplňky', + 'admin.addons.title': 'Doplňky', + 'admin.addons.subtitle': 'Zapněte nebo vypněte funkce a přizpůsobte si TREK.', + 'admin.addons.catalog.memories.name': 'Fotky (Immich)', + 'admin.addons.catalog.memories.description': + 'Sdílejte cestovní fotky přes vaši instanci Immich', + 'admin.addons.catalog.packing.name': 'Seznamy', + 'admin.addons.catalog.packing.description': + 'Balicí seznamy a úkoly pro vaše výlety', + 'admin.addons.catalog.budget.name': 'Rozpočet', + 'admin.addons.catalog.budget.description': + 'Sledování výdajů a plánování rozpočtu cesty', + 'admin.addons.catalog.documents.name': 'Dokumenty', + 'admin.addons.catalog.documents.description': + 'Ukládání a správa cestovních dokladů', + 'admin.addons.catalog.vacay.name': 'Dovolená (Vacay)', + 'admin.addons.catalog.vacay.description': + 'Osobní plánovač dovolené s kalendářem', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Mapa světa s navštívenými zeměmi a statistikami', + 'admin.addons.catalog.collab.name': 'Spolupráce', + 'admin.addons.catalog.collab.description': + 'Poznámky v reálném čase, hlasování a chat pro plánování', + 'admin.addons.enabled': 'Povoleno', + 'admin.addons.disabled': 'Zakázáno', + 'admin.addons.type.trip': 'Cesta', + 'admin.addons.type.global': 'Globální', + 'admin.addons.type.integration': 'Integrace', + 'admin.addons.tripHint': 'Dostupné jako karta v rámci každé cesty', + 'admin.addons.globalHint': 'Dostupné jako samostatná sekce v hlavní navigaci', + 'admin.addons.integrationHint': + 'Backendové služby a API integrace bez vlastní stránky', + 'admin.addons.toast.updated': 'Doplněk byl aktualizován', + 'admin.addons.toast.error': 'Aktualizace doplňku se nezdařila', + 'admin.addons.noAddons': 'Žádné doplňky nejsou k dispozici', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol pro integraci AI asistentů', + 'admin.addons.subtitleBefore': + 'Zapněte nebo vypněte funkce a přizpůsobte si ', + 'admin.addons.subtitleAfter': '.', + 'admin.tabs.audit': 'Audit', + 'admin.audit.subtitle': + 'Bezpečnostní a administrátorské události (zálohy, uživatelé, 2FA, nastavení).', + 'admin.audit.empty': 'Zatím žádné záznamy auditu.', + 'admin.audit.refresh': 'Obnovit', + 'admin.audit.loadMore': 'Načíst další', + 'admin.audit.showing': '{count} načteno · {total} celkem', + 'admin.audit.col.time': 'Čas', + 'admin.audit.col.user': 'Uživatel', + 'admin.audit.col.action': 'Akce', + 'admin.audit.col.resource': 'Zdroj', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Detaily', + 'admin.tabs.mcpTokens': 'MCP přístup', + 'admin.mcpTokens.title': 'MCP přístup', + 'admin.mcpTokens.subtitle': + 'Správa OAuth relací a API tokenů všech uživatelů', + 'admin.mcpTokens.sectionTitle': 'API tokeny', + 'admin.mcpTokens.owner': 'Vlastník', + 'admin.mcpTokens.tokenName': 'Název tokenu', + 'admin.mcpTokens.created': 'Vytvořen', + 'admin.mcpTokens.lastUsed': 'Naposledy použit', + 'admin.mcpTokens.never': 'Nikdy', + 'admin.mcpTokens.empty': 'Zatím nebyly vytvořeny žádné MCP tokeny', + 'admin.mcpTokens.deleteTitle': 'Smazat token', + 'admin.mcpTokens.deleteMessage': + 'Tento token bude okamžitě zneplatněn. Uživatel ztratí MCP přístup přes tento token.', + 'admin.mcpTokens.deleteSuccess': 'Token smazán', + 'admin.mcpTokens.deleteError': 'Nepodařilo se smazat token', + 'admin.mcpTokens.loadError': 'Nepodařilo se načíst tokeny', + 'admin.oauthSessions.sectionTitle': 'OAuth relace', + 'admin.oauthSessions.clientName': 'Klient', + 'admin.oauthSessions.owner': 'Vlastník', + 'admin.oauthSessions.scopes': 'Oprávnění', + 'admin.oauthSessions.created': 'Vytvořeno', + 'admin.oauthSessions.empty': 'Žádné aktivní OAuth relace', + 'admin.oauthSessions.revokeTitle': 'Zrušit relaci', + 'admin.oauthSessions.revokeMessage': + 'Tato OAuth relace bude okamžitě zrušena. Klient ztratí přístup k MCP.', + 'admin.oauthSessions.revokeSuccess': 'Relace zrušena', + 'admin.oauthSessions.revokeError': 'Nepodařilo se zrušit relaci', + 'admin.oauthSessions.loadError': 'Nepodařilo se načíst OAuth relace', + 'admin.tabs.github': 'GitHub', + 'admin.github.title': 'Historie verzí', + 'admin.github.subtitle': 'Nejnovější aktualizace z {repo}', + 'admin.github.latest': 'Nejnovější', + 'admin.github.prerelease': 'Předběžná verze', + 'admin.github.showDetails': 'Zobrazit podrobnosti', + 'admin.github.hideDetails': 'Skrýt podrobnosti', + 'admin.github.loadMore': 'Načíst další', + 'admin.github.loading': 'Načítání...', + 'admin.github.error': 'Nepodařilo se načíst verze', + 'admin.github.by': 'od', + 'admin.github.support': 'Pomáhá udržovat vývoj TREK', + 'admin.weather.title': 'Data o počasí', + 'admin.weather.badge': 'Od 24. března 2026', + 'admin.weather.description': + 'TREK používá Open-Meteo jako zdroj dat. Je to bezplatná open-source služba – není vyžadován API klíč.', + 'admin.weather.forecast': 'Předpověď na 16 dní', + 'admin.weather.forecastDesc': 'Dříve 5 dní (OpenWeatherMap)', + 'admin.weather.climate': 'Historická klimatická data', + 'admin.weather.climateDesc': + 'Průměry za posledních 85 let pro dny mimo 16denní předpověď', + 'admin.weather.requests': '10 000 požadavků denně', + 'admin.weather.requestsDesc': 'Zdarma, bez nutnosti klíče', + 'admin.weather.locationHint': + 'Počasí se určuje podle prvního místa se souřadnicemi v daném dni.', + 'admin.update.available': 'Dostupná aktualizace', + 'admin.update.text': + 'TREK {version} je k dispozici. Aktuálně používáte verzi {current}.', + 'admin.update.button': 'Zobrazit na GitHubu', + 'admin.update.install': 'Instalovat aktualizaci', + 'admin.update.confirmTitle': 'Instalovat aktualizaci?', + 'admin.update.confirmText': + 'TREK bude aktualizován z verze {current} na {version}. Server se poté automaticky restartuje.', + 'admin.update.dataInfo': + 'Všechna vaše data (cesty, uživatelé, API klíče, soubory) budou zachována.', + 'admin.update.warning': 'Aplikace bude během restartu krátce nedostupná.', + 'admin.update.confirm': 'Aktualizovat nyní', + 'admin.update.installing': 'Aktualizace probíhá…', + 'admin.update.success': + 'Aktualizace byla nainstalována! Server se restartuje…', + 'admin.update.failed': 'Aktualizace se nezdařila', + 'admin.update.backupHint': 'Před aktualizací doporučujeme vytvořit zálohu.', + 'admin.update.backupLink': 'Přejít na zálohování', + 'admin.update.howTo': 'Jak aktualizovat', + 'admin.update.dockerText': + 'Váš TREK běží v Dockeru. Pro aktualizaci na verzi {version} spusťte na svém serveru tyto příkazy:', + 'admin.update.reloadHint': 'Prosím obnovte stránku za několik sekund.', + 'admin.tabs.permissions': 'Oprávnění', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'In-app oznámení jsou vždy aktivní a nelze je globálně vypnout.', + 'admin.notifications.adminWebhookPanel.title': 'Admin webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'Tento webhook se používá výhradně pro admin oznámení (např. upozornění na verze). Je nezávislý na uživatelských webhooků a odesílá automaticky, pokud je nastavena URL.', + 'admin.notifications.adminWebhookPanel.saved': 'URL admin webhooku uložena', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Testovací webhook byl úspěšně odeslán', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Testovací webhook selhal', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin webhook odesílá automaticky, pokud je nastavena URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Umožňuje uživatelům nakonfigurovat vlastní témata ntfy pro přijímání push notifikací. Níže nastavte výchozí server pro předvyplnění nastavení uživatelů.', + 'admin.notifications.testNtfy': 'Odeslat testovací Ntfy', + 'admin.notifications.testNtfySuccess': 'Testovací Ntfy bylo úspěšně odesláno', + 'admin.notifications.testNtfyFailed': 'Testovací Ntfy selhalo', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Toto téma Ntfy se používá výhradně pro admin oznámení (např. upozornění na verze). Je nezávislé na tématech uživatelů a odesílá vždy, když je nakonfigurováno.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serveru Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Slouží také jako výchozí server pro ntfy notifikace uživatelů. Ponechte prázdné pro použití ntfy.sh. Uživatelé si to mohou změnit ve vlastním nastavení.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': + 'Přístupový token (volitelné)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Přístupový token admina byl vymazán', + 'admin.notifications.adminNtfyPanel.saved': 'Nastavení admin Ntfy uloženo', + 'admin.notifications.adminNtfyPanel.test': 'Odeslat testovací Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Testovací Ntfy bylo úspěšně odesláno', + 'admin.notifications.adminNtfyPanel.testFailed': 'Testovací Ntfy selhalo', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin Ntfy odesílá vždy, když je nakonfigurováno téma', + 'admin.notifications.adminNotificationsHint': + 'Nastavte, které kanály doručují admin oznámení (např. upozornění na verze). Webhook odesílá automaticky, pokud je nastavena URL admin webhooku.', + 'admin.notifications.tripReminders.title': 'Připomínky výletů', + 'admin.notifications.tripReminders.hint': + 'Odešle upozornění před začátkem výletu (vyžaduje nastavené dny připomínky na výletu).', + 'admin.notifications.tripReminders.enabled': 'Připomínky výletů aktivovány', + 'admin.notifications.tripReminders.disabled': + 'Připomínky výletů deaktivovány', + 'admin.tabs.notifications': 'Oznámení', + 'admin.addons.catalog.journey.name': 'Cestovní deník', + 'admin.addons.catalog.journey.description': + 'Sledování cest a cestovní deník s odbaveními, fotkami a denními příběhy', +}; +export default admin; diff --git a/shared/src/i18n/cs/airport.ts b/shared/src/i18n/cs/airport.ts new file mode 100644 index 00000000..612edbe6 --- /dev/null +++ b/shared/src/i18n/cs/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Kód letiště nebo město (např. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/cs/atlas.ts b/shared/src/i18n/cs/atlas.ts new file mode 100644 index 00000000..ad7a7014 --- /dev/null +++ b/shared/src/i18n/cs/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Vaše stopa ve světě', + 'atlas.countries': 'Země', + 'atlas.trips': 'Cesty', + 'atlas.places': 'Místa', + 'atlas.unmark': 'Odebrat', + 'atlas.confirmMark': 'Označit tuto zemi jako navštívenou?', + 'atlas.confirmUnmark': 'Odebrat tuto zemi ze seznamu navštívených?', + 'atlas.confirmUnmarkRegion': 'Odebrat tento region ze seznamu navštívených?', + 'atlas.markVisited': 'Označit jako navštívené', + 'atlas.markVisitedHint': 'Přidat tuto zemi do seznamu navštívených', + 'atlas.markRegionVisitedHint': 'Přidat tento region do seznamu navštívených', + 'atlas.addToBucket': 'Přidat do seznamu přání (Bucket list)', + 'atlas.addPoi': 'Přidat místo', + 'atlas.bucketNamePlaceholder': 'Název (země, město, místo...)', + 'atlas.month': 'Měsíc', + 'atlas.addToBucketHint': 'Uložit jako místo, které chcete navštívit', + 'atlas.bucketWhen': 'Kdy plánujete návštěvu?', + 'atlas.statsTab': 'Statistiky', + 'atlas.bucketTab': 'Bucket List', + 'atlas.addBucket': 'Přidat na Bucket List', + 'atlas.bucketNotesPlaceholder': 'Poznámky (volitelné)', + 'atlas.bucketEmpty': 'Váš seznam přání je prázdný', + 'atlas.bucketEmptyHint': 'Přidejte místa, která sníte navštívit', + 'atlas.days': 'Dní', + 'atlas.visitedCountries': 'Navštívené země', + 'atlas.cities': 'Města', + 'atlas.noData': 'Zatím žádná cestovatelská data', + 'atlas.noDataHint': + 'Vytvořte cestu a přidejte místa, abyste viděli svou mapu světa', + 'atlas.lastTrip': 'Poslední cesta', + 'atlas.nextTrip': 'Další cesta', + 'atlas.daysLeft': 'dní zbývá', + 'atlas.streak': 'Série', + 'atlas.year': 'rok', + 'atlas.years': 'roky/let', + 'atlas.yearInRow': 'rok v řadě', + 'atlas.yearsInRow': 'let v řadě', + 'atlas.tripIn': 'cesta v roce', + 'atlas.tripsIn': 'cest v roce', + 'atlas.since': 'od', + 'atlas.europe': 'Evropa', + 'atlas.asia': 'Asie', + 'atlas.northAmerica': 'S. Amerika', + 'atlas.southAmerica': 'J. Amerika', + 'atlas.africa': 'Afrika', + 'atlas.oceania': 'Oceánie', + 'atlas.other': 'Ostatní', + 'atlas.firstVisit': 'První cesta', + 'atlas.lastVisitLabel': 'Poslední cesta', + 'atlas.tripSingular': 'Cesta', + 'atlas.tripPlural': 'Cesty', + 'atlas.placeVisited': 'Navštívené místo', + 'atlas.placesVisited': 'Navštívená místa', + 'atlas.searchCountry': 'Hledat zemi...', +}; +export default atlas; diff --git a/shared/src/i18n/cs/backup.ts b/shared/src/i18n/cs/backup.ts new file mode 100644 index 00000000..d41efea0 --- /dev/null +++ b/shared/src/i18n/cs/backup.ts @@ -0,0 +1,76 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Záloha dat', + 'backup.subtitle': 'Databáze a všechny nahrané soubory', + 'backup.refresh': 'Obnovit', + 'backup.upload': 'Nahrát zálohu', + 'backup.uploading': 'Nahrávání…', + 'backup.create': 'Vytvořit zálohu', + 'backup.creating': 'Vytváření…', + 'backup.empty': 'Zatím žádné zálohy', + 'backup.createFirst': 'Vytvořit první zálohu', + 'backup.download': 'Stáhnout', + 'backup.restore': 'Obnovit', + 'backup.confirm.restore': + 'Obnovit zálohu „{name}"?\n\nVšechna aktuální data budou nahrazena zálohou.', + 'backup.confirm.uploadRestore': + 'Nahrát a obnovit zálohu „{name}"?\n\nVšechna aktuální data budou přepsána.', + 'backup.confirm.delete': 'Smazat zálohu „{name}"?', + 'backup.toast.loadError': 'Nepodařilo se načíst zálohy', + 'backup.toast.created': 'Záloha byla úspěšně vytvořena', + 'backup.toast.createError': 'Nepodařilo se vytvořit zálohu', + 'backup.toast.restored': 'Záloha obnovena. Stránka se znovu načte…', + 'backup.toast.restoreError': 'Obnovení se nezdařilo', + 'backup.toast.uploadError': 'Nahrávání se nezdařilo', + 'backup.toast.deleted': 'Záloha smazána', + 'backup.toast.deleteError': 'Smazání se nezdařilo', + 'backup.toast.downloadError': 'Stahování se nezdařilo', + 'backup.toast.settingsSaved': 'Nastavení automatického zálohování uloženo', + 'backup.toast.settingsError': 'Nepodařilo se uložit nastavení', + 'backup.auto.title': 'Automatické zálohování', + 'backup.auto.subtitle': 'Automatické zálohování podle plánu', + 'backup.auto.enable': 'Povolit automatické zálohování', + 'backup.auto.enableHint': + 'Zálohy budou vytvářeny automaticky podle zvoleného plánu', + 'backup.auto.interval': 'Interval', + 'backup.auto.hour': 'Spustit v hodinu', + 'backup.auto.hourHint': 'Místní čas serveru (formát {format})', + 'backup.auto.dayOfWeek': 'Den v týdnu', + 'backup.auto.dayOfMonth': 'Den v měsíci', + 'backup.auto.dayOfMonthHint': + 'Omezeno na 1–28 pro kompatibilitu se všemi měsíci', + 'backup.auto.scheduleSummary': 'Plán', + 'backup.auto.summaryDaily': 'Každý den v {hour}:00', + 'backup.auto.summaryWeekly': 'Každý {day} v {hour}:00', + 'backup.auto.summaryMonthly': '{day}. každého měsíce v {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Automatické zálohování je konfigurováno přes Docker proměnné prostředí. Pro změnu nastavení aktualizujte docker-compose.yml a restartujte kontejner.', + 'backup.auto.copyEnv': 'Zkopírovat Docker proměnné', + 'backup.auto.envCopied': 'Docker proměnné prostředí zkopírovány do schránky', + 'backup.auto.keepLabel': 'Smazat staré zálohy po', + 'backup.dow.sunday': 'Ne', + 'backup.dow.monday': 'Po', + 'backup.dow.tuesday': 'Út', + 'backup.dow.wednesday': 'St', + 'backup.dow.thursday': 'Čt', + 'backup.dow.friday': 'Pá', + 'backup.dow.saturday': 'So', + 'backup.interval.hourly': 'Každou hodinu', + 'backup.interval.daily': 'Denně', + 'backup.interval.weekly': 'Týdně', + 'backup.interval.monthly': 'Měsíčně', + 'backup.keep.1day': '1 den', + 'backup.keep.3days': '3 dny', + 'backup.keep.7days': '7 dní', + 'backup.keep.14days': '14 dní', + 'backup.keep.30days': '30 dní', + 'backup.keep.forever': 'Uchovávat navždy', + 'backup.restoreConfirmTitle': 'Obnovit zálohu?', + 'backup.restoreWarning': + 'Všechna aktuální data (cesty, místa, uživatelé, nahrané soubory) budou trvale nahrazena zálohou. Tuto akci nelze vrátit.', + 'backup.restoreTip': 'Tip: Před obnovením vytvořte zálohu aktuálního stavu.', + 'backup.restoreConfirm': 'Ano, obnovit', +}; +export default backup; diff --git a/shared/src/i18n/cs/budget.ts b/shared/src/i18n/cs/budget.ts new file mode 100644 index 00000000..0c8d8c62 --- /dev/null +++ b/shared/src/i18n/cs/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Rozpočet', + 'budget.exportCsv': 'Exportovat CSV', + 'budget.emptyTitle': 'Zatím nebyl vytvořen žádný rozpočet', + 'budget.emptyText': + 'Vytvořte kategorie a položky pro plánování cestovního rozpočtu', + 'budget.emptyPlaceholder': 'Zadejte název kategorie...', + 'budget.createCategory': 'Vytvořit kategorii', + 'budget.category': 'Kategorie', + 'budget.categoryName': 'Název kategorie', + 'budget.table.name': 'Název', + 'budget.table.total': 'Celkem', + 'budget.table.persons': 'Osoby', + 'budget.table.days': 'Dní', + 'budget.table.perPerson': 'Na osobu', + 'budget.table.perDay': 'Za den', + 'budget.table.perPersonDay': 'Os. / den', + 'budget.table.note': 'Poznámka', + 'budget.table.date': 'Datum', + 'budget.newEntry': 'Nová položka', + 'budget.defaultEntry': 'Nová položka', + 'budget.defaultCategory': 'Nová kategorie', + 'budget.total': 'Celkem', + 'budget.totalBudget': 'Celkový rozpočet', + 'budget.byCategory': 'Podle kategorie', + 'budget.editTooltip': 'Klikněte pro úpravu', + 'budget.linkedToReservation': 'Propojeno s rezervací — název upravte tam', + 'budget.confirm.deleteCategory': + 'Opravdu chcete smazat kategorii „{name}” s {count} položkami?', + 'budget.deleteCategory': 'Smazat kategorii', + 'budget.perPerson': 'Na osobu', + 'budget.paid': 'Zaplaceno', + 'budget.open': 'Nezaplaceno', + 'budget.noMembers': 'Žádní členové nebyli přiřazeni', + 'budget.settlement': 'Vyúčtování', + 'budget.settlementInfo': + 'Klikněte na avatar člena u rozpočtové položky pro zelené označení – to znamená, že zaplatil. Vyúčtování pak ukazuje, kdo komu a kolik dluží.', + 'budget.netBalances': 'Čisté zůstatky', + 'budget.categoriesLabel': 'kategorie', +}; +export default budget; diff --git a/shared/src/i18n/cs/categories.ts b/shared/src/i18n/cs/categories.ts new file mode 100644 index 00000000..8d0b2a06 --- /dev/null +++ b/shared/src/i18n/cs/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Kategorie', + 'categories.subtitle': 'Správa kategorií pro místa', + 'categories.new': 'Nová kategorie', + 'categories.empty': 'Zatím žádné kategorie', + 'categories.namePlaceholder': 'Název kategorie', + 'categories.icon': 'Ikona', + 'categories.color': 'Barva', + 'categories.customColor': 'Vybrat vlastní barvu', + 'categories.preview': 'Náhled', + 'categories.defaultName': 'Kategorie', + 'categories.update': 'Aktualizovat', + 'categories.create': 'Vytvořit', + 'categories.confirm.delete': + 'Smazat kategorii? Místa v této kategorii nebudou smazána.', + 'categories.toast.loadError': 'Nepodařilo se načíst kategorie', + 'categories.toast.nameRequired': 'Prosím zadejte název', + 'categories.toast.updated': 'Kategorie aktualizována', + 'categories.toast.created': 'Kategorie vytvořena', + 'categories.toast.saveError': 'Uložení se nezdařilo', + 'categories.toast.deleted': 'Kategorie smazána', + 'categories.toast.deleteError': 'Smazání se nezdařilo', +}; +export default categories; diff --git a/shared/src/i18n/cs/collab.ts b/shared/src/i18n/cs/collab.ts new file mode 100644 index 00000000..7d314168 --- /dev/null +++ b/shared/src/i18n/cs/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Poznámky', + 'collab.tabs.polls': 'Hlasování', + 'collab.whatsNext.title': 'Co následuje', + 'collab.whatsNext.today': 'Dnes', + 'collab.whatsNext.tomorrow': 'Zítra', + 'collab.whatsNext.empty': 'Žádné nadcházející aktivity', + 'collab.whatsNext.until': 'do', + 'collab.whatsNext.emptyHint': 'Aktivity s časem se zde zobrazí', + 'collab.chat.send': 'Odeslat', + 'collab.chat.placeholder': 'Napište zprávu...', + 'collab.chat.empty': 'Začněte konverzaci', + 'collab.chat.emptyHint': 'Zprávy jsou sdíleny se všemi členy cesty', + 'collab.chat.emptyDesc': + 'Sdílejte nápady, plány a novinky se svou cestovatelskou skupinou', + 'collab.chat.today': 'Dnes', + 'collab.chat.yesterday': 'Včera', + 'collab.chat.deletedMessage': 'smazal zprávu', + 'collab.chat.reply': 'Odpovědět', + 'collab.chat.loadMore': 'Načíst starší zprávy', + 'collab.chat.justNow': 'právě teď', + 'collab.chat.minutesAgo': 'před {n} min', + 'collab.chat.hoursAgo': 'před {n} h', + 'collab.notes.title': 'Poznámky', + 'collab.notes.new': 'Nová poznámka', + 'collab.notes.empty': 'Zatím žádné poznámky', + 'collab.notes.emptyHint': 'Začněte zapisovat nápady a plány', + 'collab.notes.all': 'Vše', + 'collab.notes.titlePlaceholder': 'Poznámka...', + 'collab.notes.noCategory': 'Bez kategorie', + 'collab.notes.color': 'Barva', + 'collab.notes.save': 'Uložit', + 'collab.notes.cancel': 'Zrušit', + 'collab.notes.edit': 'Upravit', + 'collab.notes.delete': 'Smazat', + 'collab.notes.pin': 'Připnout', + 'collab.notes.unpin': 'Odepnout', + 'collab.notes.daysAgo': 'před {n} dny', + 'collab.notes.categorySettings': 'Spravovat kategorie', + 'collab.notes.create': 'Vytvořit', + 'collab.notes.website': 'Webové stránky', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Přiložit soubory', + 'collab.notes.noCategoriesYet': 'Zatím žádné kategorie', + 'collab.notes.emptyDesc': 'Vytvořte poznámku a začněte', + 'collab.notes.contentPlaceholder': 'Napište něco...', + 'collab.notes.categoryPlaceholder': 'Kategorie', + 'collab.notes.newCategory': 'Nová kategorie...', + 'collab.notes.category': 'Kategorie', + 'collab.polls.title': 'Hlasování', + 'collab.polls.new': 'Nové hlasování', + 'collab.polls.empty': 'Zatím žádná hlasování', + 'collab.polls.emptyHint': 'Zeptejte se skupiny a hlasujte společně', + 'collab.polls.question': 'Otázka', + 'collab.polls.questionPlaceholder': 'Co bychom měli dělat?', + 'collab.polls.addOption': '+ Přidat možnost', + 'collab.polls.optionPlaceholder': 'Možnost {n}', + 'collab.polls.create': 'Vytvořit hlasování', + 'collab.polls.close': 'Uzavřít', + 'collab.polls.closed': 'Uzavřeno', + 'collab.polls.votes': '{n} hlasů', + 'collab.polls.vote': '{n} hlas', + 'collab.polls.multipleChoice': 'Více možností', + 'collab.polls.multiChoice': 'Více možností', + 'collab.polls.deadline': 'Termín', + 'collab.polls.option': 'Možnost', + 'collab.polls.options': 'Možnosti', + 'collab.polls.delete': 'Smazat', + 'collab.polls.closedSection': 'Uzavřené', +}; +export default collab; diff --git a/shared/src/i18n/cs/common.ts b/shared/src/i18n/cs/common.ts new file mode 100644 index 00000000..cb53ab46 --- /dev/null +++ b/shared/src/i18n/cs/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Uložit', + 'common.showMore': 'Zobrazit více', + 'common.showLess': 'Zobrazit méně', + 'common.cancel': 'Zrušit', + 'common.clear': 'Vymazat', + 'common.delete': 'Smazat', + 'common.edit': 'Upravit', + 'common.add': 'Přidat', + 'common.loading': 'Načítání...', + 'common.import': 'Importovat', + 'common.select': 'Vybrat', + 'common.selectAll': 'Vybrat vše', + 'common.deselectAll': 'Zrušit výběr všeho', + 'common.error': 'Chyba', + 'common.unknownError': 'Neznámá chyba', + 'common.tooManyAttempts': 'Příliš mnoho pokusů. Zkuste to prosím znovu.', + 'common.back': 'Zpět', + 'common.all': 'Vše', + 'common.close': 'Zavřít', + 'common.open': 'Otevřít', + 'common.upload': 'Nahrát', + 'common.search': 'Hledat', + 'common.confirm': 'Potvrdit', + 'common.ok': 'OK', + 'common.yes': 'Ano', + 'common.no': 'Ne', + 'common.or': 'nebo', + 'common.none': 'Žádné', + 'common.date': 'Datum', + 'common.rename': 'Přejmenovat', + 'common.discardChanges': 'Zahodit změny', + 'common.discard': 'Zahodit', + 'common.name': 'Jméno', + 'common.email': 'E-mail', + 'common.password': 'Heslo', + 'common.saving': 'Ukládání...', + 'common.expand': 'Rozbalit', + 'common.collapse': 'Sbalit', + 'common.saved': 'Uloženo', + 'common.update': 'Aktualizovat', + 'common.change': 'Změnit', + 'common.uploading': 'Nahrávání…', + 'common.backToPlanning': 'Zpět k plánování', + 'common.reset': 'Resetovat', + 'common.copy': 'Kopírovat', + 'common.copied': 'Zkopírováno', + 'common.justNow': 'právě teď', + 'common.hoursAgo': 'před {count} h', + 'common.daysAgo': 'před {count} d', +}; +export default common; diff --git a/shared/src/i18n/cs/dashboard.ts b/shared/src/i18n/cs/dashboard.ts new file mode 100644 index 00000000..48326bbb --- /dev/null +++ b/shared/src/i18n/cs/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Moje cesty', + 'dashboard.subtitle.loading': 'Načítání cest...', + 'dashboard.subtitle.trips': '{count} cest ({archived} archivováno)', + 'dashboard.subtitle.empty': 'Začněte svou první cestu', + 'dashboard.subtitle.activeOne': '{count} aktivní cesta', + 'dashboard.subtitle.activeMany': '{count} aktivních cest', + 'dashboard.subtitle.archivedSuffix': ' · {count} archivováno', + 'dashboard.newTrip': 'Nová cesta', + 'dashboard.gridView': 'Mřížka', + 'dashboard.listView': 'Seznam', + 'dashboard.currency': 'Měna', + 'dashboard.timezone': 'Časová pásma', + 'dashboard.localTime': 'Místní čas', + 'dashboard.timezoneCustomTitle': 'Vlastní pásmo', + 'dashboard.timezoneCustomLabelPlaceholder': 'Popisek (volitelné)', + 'dashboard.timezoneCustomTzPlaceholder': 'např. America/New_York', + 'dashboard.timezoneCustomAdd': 'Přidat', + 'dashboard.timezoneCustomErrorEmpty': 'Zadejte identifikátor pásma', + 'dashboard.timezoneCustomErrorInvalid': + 'Neplatné pásmo. Použijte formát jako např. Europe/Prague', + 'dashboard.timezoneCustomErrorDuplicate': 'Již bylo přidáno', + 'dashboard.emptyTitle': 'Zatím žádné cesty', + 'dashboard.emptyText': 'Vytvořte svou první cestu a začněte plánovat!', + 'dashboard.emptyButton': 'Vytvořit první cestu', + 'dashboard.nextTrip': 'Další cesta', + 'dashboard.shared': 'Sdílené', + 'dashboard.sharedBy': 'Sdílí {name}', + 'dashboard.days': 'Dní', + 'dashboard.places': 'Míst', + 'dashboard.members': 'Cestovní parťáci', + 'dashboard.archive': 'Archivovat', + 'dashboard.copyTrip': 'Kopírovat', + 'dashboard.copySuffix': 'kopie', + 'dashboard.restore': 'Obnovit', + 'dashboard.archived': 'Archivováno', + 'dashboard.status.ongoing': 'Probíhající', + 'dashboard.status.today': 'Dnes', + 'dashboard.status.tomorrow': 'Zítra', + 'dashboard.status.past': 'Proběhlé', + 'dashboard.status.daysLeft': 'zbývá {count} dní', + 'dashboard.toast.loadError': 'Nepodařilo se načíst cesty', + 'dashboard.toast.created': 'Cesta byla úspěšně vytvořena!', + 'dashboard.toast.createError': 'Nepodařilo se vytvořit cestu', + 'dashboard.toast.updated': 'Cesta byla aktualizována!', + 'dashboard.toast.updateError': 'Nepodařilo se aktualizovat cestu', + 'dashboard.toast.deleted': 'Cesta byla smazána', + 'dashboard.toast.deleteError': 'Nepodařilo se smazat cestu', + 'dashboard.toast.archived': 'Cesta byla archivována', + 'dashboard.toast.archiveError': 'Nepodařilo se archivovat cestu', + 'dashboard.toast.restored': 'Cesta byla obnovena', + 'dashboard.toast.restoreError': 'Nepodařilo se obnovit cestu', + 'dashboard.toast.copied': 'Cesta byla zkopírována!', + 'dashboard.toast.copyError': 'Nepodařilo se zkopírovat cestu', + 'dashboard.confirm.delete': + 'Smazat cestu „{title}”? Všechna místa a plány budou trvale smazány.', + 'dashboard.editTrip': 'Upravit cestu', + 'dashboard.createTrip': 'Vytvořit novou cestu', + 'dashboard.tripTitle': 'Název', + 'dashboard.tripTitlePlaceholder': 'např. Léto v Japonsku', + 'dashboard.tripDescription': 'Popis', + 'dashboard.tripDescriptionPlaceholder': 'O čem je tato cesta?', + 'dashboard.startDate': 'Datum začátku', + 'dashboard.endDate': 'Datum konce', + 'dashboard.dayCount': 'Počet dnů', + 'dashboard.dayCountHint': + 'Kolik dnů naplánovat, když nejsou nastavena data cesty.', + 'dashboard.noDateHint': + 'Datum nezadáno – výchozí délka nastavena na 7 dní. Toto lze kdykoli změnit.', + 'dashboard.coverImage': 'Úvodní obrázek', + 'dashboard.addCoverImage': 'Vybrat úvodní obrázek (nebo přetáhnout sem)', + 'dashboard.addMembers': 'Spolucestující', + 'dashboard.addMember': 'Přidat člena', + 'dashboard.coverSaved': 'Úvodní obrázek uložen', + 'dashboard.coverUploadError': 'Nahrávání se nezdařilo', + 'dashboard.coverRemoveError': 'Odstranění se nezdařilo', + 'dashboard.titleRequired': 'Název je povinný', + 'dashboard.endDateError': 'Datum konce musí být po datu začátku', + 'dashboard.greeting.morning': 'Dobré ráno,', + 'dashboard.greeting.afternoon': 'Dobré odpoledne,', + 'dashboard.greeting.evening': 'Dobrý večer,', + 'dashboard.mobile.liveNow': 'Živě', + 'dashboard.mobile.tripProgress': 'Průběh cesty', + 'dashboard.mobile.daysLeft': 'Zbývá {count} dní', + 'dashboard.mobile.places': 'Místa', + 'dashboard.mobile.buddies': 'Spolucestující', + 'dashboard.mobile.newTrip': 'Nová cesta', + 'dashboard.mobile.currency': 'Měna', + 'dashboard.mobile.timezone': 'Časové pásmo', + 'dashboard.mobile.upcomingTrips': 'Nadcházející cesty', + 'dashboard.mobile.yourTrips': 'Vaše cesty', + 'dashboard.mobile.trips': 'cesty', + 'dashboard.mobile.starts': 'Začátek', + 'dashboard.mobile.duration': 'Doba trvání', + 'dashboard.mobile.day': 'den', + 'dashboard.mobile.days': 'dní', + 'dashboard.mobile.ongoing': 'Probíhající', + 'dashboard.mobile.startsToday': 'Začíná dnes', + 'dashboard.mobile.tomorrow': 'Zítra', + 'dashboard.mobile.inDays': 'Za {count} dní', + 'dashboard.mobile.inMonths': 'Za {count} měsíců', + 'dashboard.mobile.completed': 'Dokončeno', + 'dashboard.mobile.currencyConverter': 'Převodník měn', +}; +export default dashboard; diff --git a/shared/src/i18n/cs/day.ts b/shared/src/i18n/cs/day.ts new file mode 100644 index 00000000..2d8331e6 --- /dev/null +++ b/shared/src/i18n/cs/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Pravděpodobnost srážek', + 'day.precipitation': 'Srážky', + 'day.wind': 'Vítr', + 'day.sunrise': 'Východ slunce', + 'day.sunset': 'Západ slunce', + 'day.hourlyForecast': 'Hodinová předpověď', + 'day.climateHint': + 'Historické průměry — reálná předpověď je k dispozici do 16 dnů od tohoto data.', + 'day.noWeather': + 'Nejsou k dispozici žádná data o počasí. Přidejte místo se souřadnicemi.', + 'day.overview': 'Denní přehled', + 'day.accommodation': 'Ubytování', + 'day.addAccommodation': 'Přidat ubytování', + 'day.hotelDayRange': 'Použít na dny', + 'day.noPlacesForHotel': 'Nejprve přidejte místa ke své cestě', + 'day.allDays': 'Vše', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Do', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Potvrzení', + 'day.editAccommodation': 'Upravit ubytování', + 'day.reservations': 'Rezervace', +}; +export default day; diff --git a/shared/src/i18n/cs/dayplan.ts b/shared/src/i18n/cs/dayplan.ts new file mode 100644 index 00000000..7330e0ac --- /dev/null +++ b/shared/src/i18n/cs/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Exportovat kalendář (ICS)', + 'dayplan.emptyDay': 'Na tento den nejsou naplánována žádná místa', + 'dayplan.addNote': 'Přidat poznámku', + 'dayplan.editNote': 'Upravit poznámku', + 'dayplan.noteAdd': 'Přidat poznámku', + 'dayplan.noteEdit': 'Upravit poznámku', + 'dayplan.noteTitle': 'Poznámka', + 'dayplan.noteSubtitle': 'Poznámka ke dni', + 'dayplan.totalCost': 'Celkové náklady', + 'dayplan.days': 'Dny', + 'dayplan.dayN': 'Den {n}', + 'dayplan.calculating': 'Počítání...', + 'dayplan.route': 'Trasa', + 'dayplan.optimize': 'Optimalizovat', + 'dayplan.optimized': 'Trasa optimalizována', + 'dayplan.routeError': 'Nepodařilo se vypočítat trasu', + 'dayplan.toast.needTwoPlaces': + 'Pro optimalizaci trasy jsou potřeba alespoň dvě místa', + 'dayplan.toast.routeOptimized': 'Trasa byla optimalizována', + 'dayplan.toast.noGeoPlaces': + 'Nebyla nalezena žádná místa se souřadnicemi pro výpočet trasy', + 'dayplan.confirmed': 'Potvrzeno', + 'dayplan.pendingRes': 'Čeká na potvrzení', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Exportovat denní plán do PDF', + 'dayplan.pdfError': 'Export do PDF se nezdařil', + 'dayplan.cannotReorderTransport': + 'Rezervace s pevným časem nelze přeuspořádat', + 'dayplan.confirmRemoveTimeTitle': 'Odebrat čas?', + 'dayplan.confirmRemoveTimeBody': + 'Toto místo má pevný čas ({time}). Přesunutím se čas odebere a povolí se volné řazení.', + 'dayplan.confirmRemoveTimeAction': 'Odebrat čas a přesunout', + 'dayplan.cannotDropOnTimed': + 'Položky nelze umístit mezi záznamy s pevným časem', + 'dayplan.cannotBreakChronology': + 'Toto by porušilo chronologické pořadí naplánovaných položek a rezervací', + 'dayplan.mobile.addPlace': 'Přidat místo', + 'dayplan.mobile.searchPlaces': 'Hledat místa...', + 'dayplan.mobile.allAssigned': 'Všechna místa přiřazena', + 'dayplan.mobile.noMatch': 'Žádná shoda', + 'dayplan.mobile.createNew': 'Vytvořit nové místo', +}; +export default dayplan; diff --git a/shared/src/i18n/cs/externalNotifications.ts b/shared/src/i18n/cs/externalNotifications.ts new file mode 100644 index 00000000..727355ed --- /dev/null +++ b/shared/src/i18n/cs/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const cs: NotificationLocale = { + email: { + footer: 'Toto jsi obdržel/a, protože máš povoleny upozornění v TREK.', + manage: 'Spravovat předvolby v nastavení', + madeWith: 'Made with', + openTrek: 'Otevřít TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Pozvánka do "${p.trip}"`, + body: `${p.actor} pozval ${p.invitee || 'člena'} na výlet "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nová rezervace: ${p.booking}`, + body: `${p.actor} přidal rezervaci "${p.booking}" (${p.type}) k "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Připomínka výletu: ${p.trip}`, + body: `Váš výlet "${p.trip}" se blíží!`, + }), + todo_due: (p) => ({ + title: `Úkol se blíží: ${p.todo}`, + body: `"${p.todo}" ve výletě "${p.trip}" má termín ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Pozvánka Vacay Fusion', + body: `${p.actor} vás pozval ke spojení dovolenkových plánů. Otevřete TREK pro přijetí nebo odmítnutí.`, + }), + photos_shared: (p) => ({ + title: `${p.count} sdílených fotek`, + body: `${p.actor} sdílel ${p.count} foto v "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nová zpráva v "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Balení: ${p.category}`, + body: `${p.actor} vás přiřadil do kategorie "${p.category}" v "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nová verze TREK dostupná', + body: `TREK ${p.version} je nyní dostupný. Navštivte administrátorský panel pro aktualizaci.`, + }), + synology_session_cleared: () => ({ + title: 'Relace Synology byla zrušena', + body: 'Váš účet nebo URL Synology se změnil. Byli jste odhlášeni ze Synology Photos.', + }), + }, + passwordReset: { + subject: 'Obnovení hesla', + greeting: 'Ahoj', + body: 'Obdrželi jsme žádost o obnovení hesla k tvému účtu TREK. Klikni na tlačítko níže a nastav nové heslo.', + ctaIntro: 'Obnovit heslo', + expiry: 'Odkaz vyprší za 60 minut.', + ignore: + 'Pokud jsi o obnovení nežádal/a, tento e-mail ignoruj — heslo zůstane beze změny.', + }, +}; + +export default cs; diff --git a/shared/src/i18n/cs/files.ts b/shared/src/i18n/cs/files.ts new file mode 100644 index 00000000..dd21fdb9 --- /dev/null +++ b/shared/src/i18n/cs/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Soubory', + 'files.pageTitle': 'Soubory a dokumenty', + 'files.subtitle': '{count} souborů pro {trip}', + 'files.download': 'Stáhnout', + 'files.openError': 'Soubor nelze otevřít', + 'files.downloadPdf': 'Stáhnout PDF', + 'files.count': '{count} souborů', + 'files.countSingular': '1 soubor', + 'files.uploaded': '{count} nahráno', + 'files.uploadError': 'Nahrávání se nezdařilo', + 'files.dropzone': 'Přetáhněte soubory sem', + 'files.dropzoneHint': 'nebo klikněte pro výběr', + 'files.allowedTypes': + 'Obrázky, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Nahrávání...', + 'files.filterAll': 'Vše', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Obrázky', + 'files.filterDocs': 'Dokumenty', + 'files.filterCollab': 'Poznámky spolupráce', + 'files.sourceCollab': 'Z poznámek spolupráce', + 'files.empty': 'Zatím žádné soubory', + 'files.emptyHint': 'Nahrajte soubory k vaší cestě', + 'files.openTab': 'Otevřít v nové kartě', + 'files.confirm.delete': 'Opravdu chcete smazat tento soubor?', + 'files.toast.deleted': 'Soubor byl smazán', + 'files.toast.deleteError': 'Nepodařilo se smazat soubor', + 'files.sourcePlan': 'Denní plán', + 'files.sourceBooking': 'Rezervace', + 'files.sourceTransport': 'Doprava', + 'files.attach': 'Přiložit', + 'files.pasteHint': 'Můžete také vložit obrázek ze schránky (Ctrl+V)', + 'files.trash': 'Koš', + 'files.trashEmpty': 'Koš je prázdný', + 'files.emptyTrash': 'Vysypat koš', + 'files.restore': 'Obnovit', + 'files.star': 'Označit hvězdičkou', + 'files.unstar': 'Odebrat hvězdičku', + 'files.assign': 'Přiřadit', + 'files.assignTitle': 'Přiřadit soubor', + 'files.assignPlace': 'Místo', + 'files.assignBooking': 'Rezervace', + 'files.assignTransport': 'Doprava', + 'files.unassigned': 'Nepřiřazeno', + 'files.unlink': 'Zrušit propojení', + 'files.toast.trashed': 'Přesunuto do koše', + 'files.toast.restored': 'Soubor byl obnoven', + 'files.toast.trashEmptied': 'Koš byl vysypán', + 'files.toast.assigned': 'Soubor byl přiřazen', + 'files.toast.assignError': 'Přiřazení se nezdařilo', + 'files.toast.restoreError': 'Obnovení se nezdařilo', + 'files.confirm.permanentDelete': + 'Trvale smazat tento soubor? Tuto akci nelze vrátit.', + 'files.confirm.emptyTrash': + 'Trvale smazat všechny soubory v koši? Tuto akci nelze vrátit.', + 'files.noteLabel': 'Poznámka', + 'files.notePlaceholder': 'Přidat poznámku...', +}; +export default files; diff --git a/shared/src/i18n/cs/index.ts b/shared/src/i18n/cs/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/cs/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/cs/inspector.ts b/shared/src/i18n/cs/inspector.ts new file mode 100644 index 00000000..0af3899b --- /dev/null +++ b/shared/src/i18n/cs/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Otevřeno', + 'inspector.closed': 'Zavřeno', + 'inspector.openingHours': 'Otevírací doba', + 'inspector.showHours': 'Zobrazit otevírací dobu', + 'inspector.files': 'Soubory', + 'inspector.filesCount': '{count} souborů', + 'inspector.removeFromDay': 'Odebrat ze dne', + 'inspector.remove': 'Odstranit', + 'inspector.addToDay': 'Přidat ke dni', + 'inspector.confirmedRes': 'Potvrzená rezervace', + 'inspector.pendingRes': 'Čekající rezervace', + 'inspector.google': 'Otevřít v Google Mapách', + 'inspector.website': 'Otevřít webové stránky', + 'inspector.addRes': 'Rezervace', + 'inspector.editRes': 'Upravit rezervaci', + 'inspector.participants': 'Účastníci', + 'inspector.trackStats': 'Data trasy', +}; +export default inspector; diff --git a/shared/src/i18n/cs/journey.ts b/shared/src/i18n/cs/journey.ts new file mode 100644 index 00000000..93d2a96e --- /dev/null +++ b/shared/src/i18n/cs/journey.ts @@ -0,0 +1,239 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Hledat cesty…', + 'journey.search.noResults': 'Žádné cesty neodpovídají „{query}"', + 'journey.title': 'Cestovní deník', + 'journey.subtitle': 'Zaznamenávejte své cesty průběžně', + 'journey.new': 'Nový cestovní deník', + 'journey.create': 'Vytvořit', + 'journey.titlePlaceholder': 'Kam jedete?', + 'journey.empty': 'Zatím žádné cestovní deníky', + 'journey.emptyHint': 'Začněte dokumentovat svůj další výlet', + 'journey.deleted': 'Cestovní deník smazán', + 'journey.createError': 'Nepodařilo se vytvořit cestovní deník', + 'journey.deleteError': 'Nepodařilo se smazat cestovní deník', + 'journey.deleteConfirmTitle': 'Smazat', + 'journey.deleteConfirmMessage': + 'Smazat „{title}"? Tuto akci nelze vrátit zpět.', + 'journey.deleteConfirmGeneric': 'Opravdu to chcete smazat?', + 'journey.notFound': 'Cestovní deník nenalezen', + 'journey.photos': 'Fotky', + 'journey.timelineEmpty': 'Zatím žádné zastávky', + 'journey.timelineEmptyHint': + 'Přidejte odbavení nebo napište záznam do deníku', + 'journey.status.draft': 'Koncept', + 'journey.status.active': 'Aktivní', + 'journey.status.completed': 'Dokončeno', + 'journey.status.upcoming': 'Nadcházející', + 'journey.status.archived': 'Archivováno', + 'journey.checkin.add': 'Odbavit se', + 'journey.checkin.namePlaceholder': 'Název místa', + 'journey.checkin.notesPlaceholder': 'Poznámky (volitelné)', + 'journey.checkin.save': 'Uložit', + 'journey.checkin.error': 'Nepodařilo se uložit odbavení', + 'journey.entry.add': 'Deník', + 'journey.entry.edit': 'Upravit záznam', + 'journey.entry.titlePlaceholder': 'Název (volitelný)', + 'journey.entry.bodyPlaceholder': 'Co se dnes stalo?', + 'journey.entry.save': 'Uložit', + 'journey.entry.error': 'Nepodařilo se uložit záznam', + 'journey.photo.add': 'Fotka', + 'journey.photo.uploadError': 'Nahrávání selhalo', + 'journey.share.share': 'Sdílet', + 'journey.share.public': 'Veřejný', + 'journey.share.linkCopied': 'Veřejný odkaz zkopírován', + 'journey.share.disabled': 'Veřejné sdílení vypnuto', + 'journey.editor.titlePlaceholder': 'Pojmenujte tento okamžik...', + 'journey.editor.bodyPlaceholder': 'Vyprávějte příběh tohoto dne...', + 'journey.editor.placePlaceholder': 'Místo (volitelné)', + 'journey.editor.tagsPlaceholder': + 'Tagy: skrytý klenot, nejlepší jídlo, musím se vrátit...', + 'journey.visibility.private': 'Soukromý', + 'journey.visibility.shared': 'Sdílený', + 'journey.visibility.public': 'Veřejný', + 'journey.emptyState.title': 'Váš příběh začíná zde', + 'journey.emptyState.subtitle': + 'Odba‍vte se na místě nebo napište svůj první záznam do deníku', + 'journey.frontpage.subtitle': + 'Proměňte své cesty v příběhy, na které nikdy nezapomenete', + 'journey.frontpage.createJourney': 'Vytvořit cestovní deník', + 'journey.frontpage.activeJourney': 'Aktivní cestovní deník', + 'journey.frontpage.allJourneys': 'Všechny cestovní deníky', + 'journey.frontpage.journeys': 'cestovní deníky', + 'journey.frontpage.createNew': 'Vytvořit nový cestovní deník', + 'journey.frontpage.createNewSub': + 'Vyberte cesty, pište příběhy, sdílejte dobrodružství', + 'journey.frontpage.live': 'Živě', + 'journey.frontpage.synced': 'Synchronizováno', + 'journey.frontpage.continueWriting': 'Pokračovat v psaní', + 'journey.frontpage.updated': 'Aktualizováno {time}', + 'journey.frontpage.suggestionLabel': 'Cesta právě skončila', + 'journey.frontpage.suggestionText': + 'Proměňte {title} v cestovní deník', + 'journey.frontpage.dismiss': 'Zavřít', + 'journey.frontpage.journeyName': 'Název cestovního deníku', + 'journey.frontpage.namePlaceholder': 'např. Jihovýchodní Asie 2026', + 'journey.frontpage.selectTrips': 'Vybrat cesty', + 'journey.frontpage.tripsSelected': 'cest vybráno', + 'journey.frontpage.trips': 'cesty', + 'journey.frontpage.placesImported': 'míst bude importováno', + 'journey.frontpage.places': 'místa', + 'journey.detail.backToJourney': 'Zpět na cestovní deník', + 'journey.detail.syncedWithTrips': 'Synchronizováno s cestami', + 'journey.detail.addEntry': 'Přidat záznam', + 'journey.detail.newEntry': 'Nový záznam', + 'journey.detail.editEntry': 'Upravit záznam', + 'journey.detail.noEntries': 'Zatím žádné záznamy', + 'journey.detail.noEntriesHint': + 'Přidejte cestu pro začátek s kostrovými záznamy', + 'journey.detail.noPhotos': 'Zatím žádné fotky', + 'journey.detail.noPhotosHint': + 'Nahrajte fotky k záznamům nebo procházejte knihovnu Immich/Synology', + 'journey.detail.journeyStats': 'Statistiky cesty', + 'journey.detail.syncedTrips': 'Synchronizované cesty', + 'journey.detail.noTripsLinked': 'Zatím žádné propojené cesty', + 'journey.detail.contributors': 'Přispěvatelé', + 'journey.detail.readMore': 'Číst dále', + 'journey.detail.prosCons': 'Klady a zápory', + 'journey.detail.photos': 'fotky', + 'journey.detail.day': 'Den {number}', + 'journey.detail.places': 'míst', + 'journey.stats.days': 'Dny', + 'journey.stats.cities': 'Města', + 'journey.stats.entries': 'Záznamy', + 'journey.stats.photos': 'Fotky', + 'journey.stats.places': 'Místa', + 'journey.skeletons.show': 'Zobrazit návrhy', + 'journey.skeletons.hide': 'Skrýt návrhy', + 'journey.verdict.lovedIt': 'Skvělé', + 'journey.verdict.couldBeBetter': 'Mohlo by být lepší', + 'journey.synced.places': 'místa', + 'journey.synced.synced': 'synchronizováno', + 'journey.editor.discardChangesConfirm': 'Máte neuložené změny. Zahodit?', + 'journey.editor.uploadFailed': 'Nahrávání fotek selhalo', + 'journey.editor.uploadPhotos': 'Nahrát fotky', + 'journey.editor.uploading': 'Nahrávání...', + 'journey.editor.uploadingProgress': 'Nahrávání {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} z {total} fotek selhalo — uložte znovu pro opakování', + 'journey.editor.fromGallery': 'Z galerie', + 'journey.editor.allPhotosAdded': 'Všechny fotky již přidány', + 'journey.editor.writeStory': 'Napište svůj příběh...', + 'journey.editor.prosCons': 'Klady a zápory', + 'journey.editor.pros': 'Klady', + 'journey.editor.cons': 'Zápory', + 'journey.editor.proPlaceholder': 'Něco skvělého...', + 'journey.editor.conPlaceholder': 'Ne tak skvělé...', + 'journey.editor.addAnother': 'Přidat další', + 'journey.editor.date': 'Datum', + 'journey.editor.location': 'Místo', + 'journey.editor.searchLocation': 'Hledat místo...', + 'journey.editor.mood': 'Nálada', + 'journey.editor.weather': 'Počasí', + 'journey.editor.photoFirst': '1.', + 'journey.editor.makeFirst': 'Nastavit jako 1.', + 'journey.editor.searching': 'Hledání...', + 'journey.mood.amazing': 'Úžasný', + 'journey.mood.good': 'Dobrý', + 'journey.mood.neutral': 'Neutrální', + 'journey.mood.rough': 'Těžký', + 'journey.weather.sunny': 'Slunečno', + 'journey.weather.partly': 'Polojasno', + 'journey.weather.cloudy': 'Zataženo', + 'journey.weather.rainy': 'Deštivo', + 'journey.weather.stormy': 'Bouřlivo', + 'journey.weather.cold': 'Sněžení', + 'journey.trips.linkTrip': 'Propojit cestu', + 'journey.trips.searchTrip': 'Hledat cestu', + 'journey.trips.searchPlaceholder': 'Název cesty nebo cíl...', + 'journey.trips.noTripsAvailable': 'Žádné dostupné cesty', + 'journey.trips.link': 'Propojit', + 'journey.trips.tripLinked': 'Cesta propojena', + 'journey.trips.linkFailed': 'Propojení cesty selhalo', + 'journey.trips.addTrip': 'Přidat cestu', + 'journey.trips.unlinkTrip': 'Odpojit cestu', + 'journey.trips.unlinkMessage': + 'Odpojit „{title}"? Všechny synchronizované záznamy a fotky z této cesty budou trvale smazány. Tuto akci nelze vrátit zpět.', + 'journey.trips.unlink': 'Odpojit', + 'journey.trips.tripUnlinked': 'Cesta odpojena', + 'journey.trips.unlinkFailed': 'Odpojení cesty selhalo', + 'journey.trips.noTripsLinkedSettings': 'Žádné propojené cesty', + 'journey.contributors.invite': 'Pozvat přispěvatele', + 'journey.contributors.searchUser': 'Hledat uživatele', + 'journey.contributors.searchPlaceholder': 'Uživatelské jméno nebo e-mail...', + 'journey.contributors.noUsers': 'Žádní uživatelé nenalezeni', + 'journey.contributors.role': 'Role', + 'journey.contributors.added': 'Přispěvatel přidán', + 'journey.contributors.addFailed': 'Přidání přispěvatele selhalo', + 'journey.share.publicShare': 'Veřejné sdílení', + 'journey.share.createLink': 'Vytvořit odkaz ke sdílení', + 'journey.share.linkCreated': 'Odkaz ke sdílení vytvořen', + 'journey.share.createFailed': 'Vytvoření odkazu selhalo', + 'journey.share.copy': 'Kopírovat', + 'journey.share.copied': 'Zkopírováno!', + 'journey.share.timeline': 'Časová osa', + 'journey.share.gallery': 'Galerie', + 'journey.share.map': 'Mapa', + 'journey.share.removeLink': 'Odstranit odkaz ke sdílení', + 'journey.share.linkDeleted': 'Odkaz ke sdílení smazán', + 'journey.share.deleteFailed': 'Smazání selhalo', + 'journey.share.updateFailed': 'Aktualizace selhala', + 'journey.invite.role': 'Role', + 'journey.invite.viewer': 'Čtenář', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Pozvat', + 'journey.invite.inviting': 'Zveme...', + 'journey.settings.title': 'Nastavení cestovního deníku', + 'journey.settings.coverImage': 'Titulní obrázek', + 'journey.settings.changeCover': 'Změnit obal', + 'journey.settings.addCover': 'Přidat titulní obrázek', + 'journey.settings.name': 'Název', + 'journey.settings.subtitle': 'Podtitul', + 'journey.settings.subtitlePlaceholder': 'např. Thajsko, Vietnam a Kambodža', + 'journey.settings.endJourney': 'Archivovat cestu', + 'journey.settings.reopenJourney': 'Obnovit cestu', + 'journey.settings.archived': 'Cesta archivována', + 'journey.settings.reopened': 'Cesta znovu otevřena', + 'journey.settings.endDescription': + 'Skryje odznak Živě. Kdykoli jej lze znovu otevřít.', + 'journey.settings.delete': 'Smazat', + 'journey.settings.deleteJourney': 'Smazat cestovní deník', + 'journey.settings.deleteMessage': + 'Smazat „{title}"? Všechny záznamy a fotky budou ztraceny.', + 'journey.settings.saved': 'Nastavení uloženo', + 'journey.settings.saveFailed': 'Uložení selhalo', + 'journey.settings.coverUpdated': 'Obal aktualizován', + 'journey.settings.coverFailed': 'Nahrávání selhalo', + 'journey.settings.failedToDelete': 'Smazání se nezdařilo', + 'journey.entries.deleteTitle': 'Smazat záznam', + 'journey.photosUploaded': '{count} fotografií nahráno', + 'journey.photosUploadFailed': 'Některé fotky se nepodařilo nahrát', + 'journey.photosAdded': '{count} fotografií přidáno', + 'journey.public.notFound': 'Nenalezeno', + 'journey.public.notFoundMessage': + 'Tento cestovní deník neexistuje nebo odkaz vypršel.', + 'journey.public.readOnly': 'Pouze ke čtení · Veřejný cestovní deník', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Sdíleno přes', + 'journey.public.madeWith': 'Vytvořeno pomocí', + 'journey.pdf.journeyBook': 'Cestovní kniha', + 'journey.pdf.madeWith': 'Vytvořeno pomocí TREK', + 'journey.pdf.day': 'Den', + 'journey.pdf.theEnd': 'Konec', + 'journey.pdf.saveAsPdf': 'Uložit jako PDF', + 'journey.pdf.pages': 'stran', + 'journey.picker.tripPeriod': 'Období cesty', + 'journey.picker.dateRange': 'Časové období', + 'journey.picker.allPhotos': 'Všechny fotky', + 'journey.picker.albums': 'Alba', + 'journey.picker.selected': 'vybráno', + 'journey.picker.addTo': 'Přidat do', + 'journey.picker.newGallery': 'Nová galerie', + 'journey.picker.selectAll': 'Vybrat vše', + 'journey.picker.deselectAll': 'Zrušit výběr', + 'journey.picker.noAlbums': 'Žádná alba nenalezena', + 'journey.picker.selectDate': 'Vyberte datum', + 'journey.picker.search': 'Hledat', +}; +export default journey; diff --git a/shared/src/i18n/cs/login.ts b/shared/src/i18n/cs/login.ts new file mode 100644 index 00000000..1855273b --- /dev/null +++ b/shared/src/i18n/cs/login.ts @@ -0,0 +1,95 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Přihlášení se nezdařilo. Zkontrolujte prosím své údaje.', + 'login.tagline': 'Vaše cesty.\nVáš plán.', + 'login.description': + 'Plánujte cesty společně s interaktivními mapami, rozpočty a synchronizací v reálném čase.', + 'login.features.maps': 'Interaktivní mapy', + 'login.features.mapsDesc': 'Google Places, trasy a shlukování bodů', + 'login.features.realtime': 'Synchronizace v reálném čase', + 'login.features.realtimeDesc': 'Plánujte společně přes WebSocket', + 'login.features.budget': 'Sledování rozpočtu', + 'login.features.budgetDesc': 'Kategorie, grafy a náklady na osobu', + 'login.features.collab': 'Spolupráce', + 'login.features.collabDesc': 'Více uživatelů se sdílenými cestami', + 'login.features.packing': 'Seznamy věcí', + 'login.features.packingDesc': 'Kategorie, pokrok v balení a návrhy', + 'login.features.bookings': 'Rezervace', + 'login.features.bookingsDesc': 'Lety, hotely, restaurace a další', + 'login.features.files': 'Dokumenty', + 'login.features.filesDesc': 'Nahrávejte a spravujte dokumenty', + 'login.features.routes': 'Chytré trasy', + 'login.features.routesDesc': + 'Automatická optimalizace a export do Google Maps', + 'login.selfHosted': 'Self-hosted · Open Source · Vaše data zůstávají u vás', + 'login.title': 'Přihlásit se', + 'login.subtitle': 'Vítejte zpět', + 'login.signingIn': 'Přihlašování…', + 'login.signIn': 'Přihlásit se', + 'login.createAdmin': 'Vytvořit účet administrátora', + 'login.createAdminHint': 'Nastavte první administrátorský účet pro TREK.', + 'login.setNewPassword': 'Nastavit nové heslo', + 'login.setNewPasswordHint': 'Před pokračováním musíte změnit heslo.', + 'login.createAccount': 'Vytvořit účet', + 'login.createAccountHint': 'Zaregistrujte si nový účet.', + 'login.creating': 'Vytváření…', + 'login.noAccount': 'Nemáte účet?', + 'login.hasAccount': 'Již máte účet?', + 'login.register': 'Registrovat se', + 'login.emailPlaceholder': 'vas@email.cz', + 'login.username': 'Uživatelské jméno', + 'login.oidc.registrationDisabled': + 'Registrace je zakázána. Kontaktujte svého administrátora.', + 'login.oidc.noEmail': 'Od poskytovatele nebyl přijat žádný e-mail.', + 'login.oidc.tokenFailed': 'Ověření se nezdařilo.', + 'login.oidc.invalidState': 'Neplatná relace. Zkuste to prosím znovu.', + 'login.demoFailed': 'Přihlášení do dema se nezdařilo', + 'login.oidcSignIn': 'Přihlásit se přes {name}', + 'login.oidcOnly': + 'Ověřování heslem je zakázáno. Přihlaste se prosím přes SSO poskytovatele.', + 'login.oidcLoggedOut': + 'Byl jste odhlášen. Přihlaste se znovu přes SSO poskytovatele.', + 'login.demoHint': 'Vyzkoušejte demo – registrace není nutná', + 'login.mfaTitle': 'Dvoufaktorové ověření', + 'login.mfaSubtitle': 'Zadejte 6místný kód z vaší autentizační aplikace.', + 'login.mfaCodeLabel': 'Ověřovací kód', + 'login.mfaCodeRequired': 'Zadejte kód z aplikace.', + 'login.mfaHint': + 'Otevřete Google Authenticator, Authy nebo jinou TOTP aplikaci.', + 'login.mfaBack': '← Zpět k přihlášení', + 'login.mfaVerify': 'Ověřit', + 'login.invalidInviteLink': 'Neplatný nebo vypršelý odkaz s pozvánkou', + 'login.oidcFailed': 'Přihlášení přes OIDC se nezdařilo', + 'login.usernameRequired': 'Uživatelské jméno je povinné', + 'login.passwordMinLength': 'Heslo musí mít alespoň 8 znaků', + 'login.forgotPassword': 'Zapomenuté heslo?', + 'login.forgotPasswordTitle': 'Obnovení hesla', + 'login.forgotPasswordBody': + 'Zadej e-mail použitý při registraci. Pokud účet existuje, pošleme odkaz pro obnovení.', + 'login.forgotPasswordSubmit': 'Odeslat odkaz', + 'login.forgotPasswordSentTitle': 'Zkontroluj e-mail', + 'login.forgotPasswordSentBody': + 'Pokud k tomuto e-mailu existuje účet, odkaz je na cestě. Platnost vyprší za 60 minut.', + 'login.forgotPasswordSmtpHintOff': + 'Upozornění: správce nemá nakonfigurovaný SMTP, takže se odkaz pro obnovení zapíše do konzole serveru místo odeslání e-mailem.', + 'login.backToLogin': 'Zpět na přihlášení', + 'login.newPassword': 'Nové heslo', + 'login.confirmPassword': 'Potvrď nové heslo', + 'login.passwordsDontMatch': 'Hesla se neshodují', + 'login.mfaCode': 'Kód 2FA', + 'login.resetPasswordTitle': 'Nastavit nové heslo', + 'login.resetPasswordBody': + 'Vyber silné heslo, které jsi tu ještě nepoužil. Minimálně 8 znaků.', + 'login.resetPasswordMfaBody': + 'Zadej 2FA kód nebo záložní kód pro dokončení obnovení.', + 'login.resetPasswordSubmit': 'Obnovit heslo', + 'login.resetPasswordVerify': 'Ověřit a obnovit', + 'login.resetPasswordSuccessTitle': 'Heslo aktualizováno', + 'login.resetPasswordSuccessBody': 'Nyní se můžeš přihlásit novým heslem.', + 'login.resetPasswordInvalidLink': 'Neplatný odkaz', + 'login.resetPasswordInvalidLinkBody': + 'Odkaz chybí nebo je poškozený. Pro pokračování si vyžádej nový.', + 'login.resetPasswordFailed': 'Obnovení se nezdařilo. Odkaz mohl vypršet.', +}; +export default login; diff --git a/shared/src/i18n/cs/map.ts b/shared/src/i18n/cs/map.ts new file mode 100644 index 00000000..68a3f20e --- /dev/null +++ b/shared/src/i18n/cs/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Spojení', + 'map.showConnections': 'Zobrazit trasy rezervací', + 'map.hideConnections': 'Skrýt trasy rezervací', +}; +export default map; diff --git a/shared/src/i18n/cs/members.ts b/shared/src/i18n/cs/members.ts new file mode 100644 index 00000000..88300252 --- /dev/null +++ b/shared/src/i18n/cs/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Sdílet cestu', + 'members.inviteUser': 'Pozvat uživatele', + 'members.selectUser': 'Vyberte uživatele…', + 'members.invite': 'Pozvat', + 'members.allHaveAccess': 'Všichni uživatelé již mají přístup.', + 'members.access': 'Přístup', + 'members.person': 'osoba', + 'members.persons': 'osob', + 'members.you': 'vy', + 'members.owner': 'Vlastník', + 'members.leaveTrip': 'Opustit cestu', + 'members.removeAccess': 'Odebrat přístup', + 'members.confirmLeave': 'Opustit cestu? Ztratíte přístup.', + 'members.confirmRemove': 'Odebrat přístup tomuto uživateli?', + 'members.loadError': 'Nepodařilo se načíst členy', + 'members.added': 'přidán/a', + 'members.addError': 'Nepodařilo se přidat', + 'members.removed': 'Člen odebrán', + 'members.removeError': 'Nepodařilo se odebrat', +}; +export default members; diff --git a/shared/src/i18n/cs/memories.ts b/shared/src/i18n/cs/memories.ts new file mode 100644 index 00000000..fabcbb60 --- /dev/null +++ b/shared/src/i18n/cs/memories.ts @@ -0,0 +1,81 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Fotky', + 'memories.notConnected': 'Immich není připojen', + 'memories.notConnectedHint': + 'Připojte svoji instanci Immich v Nastavení, abyste zde viděli fotky z cest.', + 'memories.notConnectedMultipleHint': + 'Pro přidání fotek k tomuto výletu připojte v Nastavení jednoho z těchto poskytovatelů fotek: {provider_names}.', + 'memories.noDates': 'Přidejte data k cestě pro načtení fotek.', + 'memories.noPhotos': 'Nenalezeny žádné fotky', + 'memories.noPhotosHint': + 'V Immich nebyly nalezeny žádné fotky pro období této cesty.', + 'memories.photosFound': 'fotek', + 'memories.fromOthers': 'od ostatních', + 'memories.sharePhotos': 'Sdílet fotky', + 'memories.sharing': 'Sdílení', + 'memories.reviewTitle': 'Zkontrolujte své fotky', + 'memories.reviewHint': 'Klikněte na fotky pro vyloučení ze sdílení.', + 'memories.shareCount': 'Sdílet {count} fotek', + 'memories.providerUrl': 'URL serveru', + 'memories.providerApiKey': 'API klíč', + 'memories.providerUsername': 'Uživatelské jméno', + 'memories.providerPassword': 'Heslo', + 'memories.providerOTP': 'MFA kód (pokud je povoleno)', + 'memories.skipSSLVerification': 'Přeskočit ověření SSL certifikátu', + 'memories.immichAutoUpload': + 'Zrcadlit fotky journey při nahrávání také do Immich', + 'memories.providerUrlHintSynology': + 'Zahrňte cestu aplikace Photos do URL, např. https://nas:5001/photo', + 'memories.testConnection': 'Otestovat připojení', + 'memories.testShort': 'Otestovat', + 'memories.testFirst': 'Nejprve otestujte připojení', + 'memories.connected': 'Připojeno', + 'memories.disconnected': 'Nepřipojeno', + 'memories.connectionSuccess': 'Připojeno k Immich', + 'memories.connectionError': 'Nepodařilo se připojit k Immich', + 'memories.saved': 'Nastavení {provider_name} uloženo', + 'memories.providerDisconnectedBanner': + 'Vaše připojení k {provider_name} bylo ztraceno. Obnovte připojení v Nastavení pro zobrazení fotek.', + 'memories.saveError': 'Nepodařilo se uložit nastavení {provider_name}', + 'memories.addPhotos': 'Přidat fotky', + 'memories.linkAlbum': 'Propojit album', + 'memories.selectAlbum': 'Vybrat album z Immich', + 'memories.selectAlbumMultiple': 'Vybrat album', + 'memories.noAlbums': 'Žádná alba nenalezena', + 'memories.syncAlbum': 'Synchronizovat album', + 'memories.unlinkAlbum': 'Odpojit', + 'memories.photos': 'fotek', + 'memories.selectPhotos': 'Vybrat fotky z Immich', + 'memories.selectPhotosMultiple': 'Vybrat fotky', + 'memories.selectHint': 'Klepněte na fotky pro jejich výběr.', + 'memories.selected': 'vybráno', + 'memories.addSelected': 'Přidat {count} fotek', + 'memories.alreadyAdded': 'Přidáno', + 'memories.private': 'Soukromé', + 'memories.stopSharing': 'Zastavit sdílení', + 'memories.oldest': 'Nejstarší', + 'memories.newest': 'Nejnovější', + 'memories.allLocations': 'Všechna místa', + 'memories.tripDates': 'Data cesty', + 'memories.allPhotos': 'Všechny fotky', + 'memories.confirmShareTitle': 'Sdílet se členy cesty?', + 'memories.confirmShareHint': + '{count} fotek bude viditelných pro všechny členy této cesty. Jednotlivé fotky můžete později nastavit jako soukromé.', + 'memories.confirmShareButton': 'Sdílet fotky', + 'memories.error.loadAlbums': 'Načtení alb se nezdařilo', + 'memories.error.linkAlbum': 'Propojení alba se nezdařilo', + 'memories.error.unlinkAlbum': 'Odpojení alba se nezdařilo', + 'memories.error.syncAlbum': 'Synchronizace alba se nezdařila', + 'memories.error.loadPhotos': 'Načtení fotek se nezdařilo', + 'memories.error.addPhotos': 'Přidání fotek se nezdařilo', + 'memories.error.removePhoto': 'Odebrání fotky se nezdařilo', + 'memories.error.toggleSharing': 'Aktualizace sdílení se nezdařila', + 'memories.saveRouteNotConfigured': + 'Trasa uložení není nakonfigurována pro tohoto poskytovatele', + 'memories.testRouteNotConfigured': + 'Testovací trasa není nakonfigurována pro tohoto poskytovatele', + 'memories.fillRequiredFields': 'Prosím vyplňte všechna povinná pole', +}; +export default memories; diff --git a/shared/src/i18n/cs/nav.ts b/shared/src/i18n/cs/nav.ts new file mode 100644 index 00000000..a3b404a7 --- /dev/null +++ b/shared/src/i18n/cs/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Cesta', + 'nav.share': 'Sdílet', + 'nav.settings': 'Nastavení', + 'nav.admin': 'Administrace', + 'nav.logout': 'Odhlásit se', + 'nav.lightMode': 'Světlý režim', + 'nav.darkMode': 'Tmavý režim', + 'nav.autoMode': 'Automatický režim', + 'nav.administrator': 'Administrátor', + 'nav.myTrips': 'Moje cesty', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Nastavení', + 'nav.bottomAdmin': 'Nastavení správce', + 'nav.bottomLogout': 'Odhlásit se', + 'nav.bottomAdminBadge': 'Správce', +}; +export default nav; diff --git a/shared/src/i18n/cs/notif.ts b/shared/src/i18n/cs/notif.ts new file mode 100644 index 00000000..91cc23eb --- /dev/null +++ b/shared/src/i18n/cs/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Oznámení', + 'notif.test.simple.text': 'Toto je jednoduché testovací oznámení.', + 'notif.test.boolean.text': 'Přijmete toto testovací oznámení?', + 'notif.test.navigate.text': 'Klikněte níže pro přechod na přehled.', + 'notif.trip_invite.title': 'Pozvánka na výlet', + 'notif.trip_invite.text': '{actor} vás pozval na {trip}', + 'notif.booking_change.title': 'Rezervace aktualizována', + 'notif.booking_change.text': '{actor} aktualizoval rezervaci v {trip}', + 'notif.trip_reminder.title': 'Připomínka výletu', + 'notif.trip_reminder.text': 'Váš výlet {trip} se blíží!', + 'notif.todo_due.title': 'Úkol se blíží', + 'notif.todo_due.text': '{todo} ve výletě {trip} má termín {due}', + 'notif.vacay_invite.title': 'Pozvánka Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} vás pozval ke spojení dovolenkových plánů', + 'notif.photos_shared.title': 'Fotky sdíleny', + 'notif.photos_shared.text': '{actor} sdílel {count} foto v {trip}', + 'notif.collab_message.title': 'Nová zpráva', + 'notif.collab_message.text': '{actor} poslal zprávu v {trip}', + 'notif.packing_tagged.title': 'Přiřazení balení', + 'notif.packing_tagged.text': '{actor} vás přiřadil k {category} v {trip}', + 'notif.version_available.title': 'Nová verze dostupná', + 'notif.version_available.text': 'TREK {version} je nyní dostupný', + 'notif.action.view_trip': 'Zobrazit výlet', + 'notif.action.view_collab': 'Zobrazit zprávy', + 'notif.action.view_packing': 'Zobrazit balení', + 'notif.action.view_photos': 'Zobrazit fotky', + 'notif.action.view_vacay': 'Zobrazit Vacay', + 'notif.action.view_admin': 'Jít do adminu', + 'notif.action.view': 'Zobrazit', + 'notif.action.accept': 'Přijmout', + 'notif.action.decline': 'Odmítnout', + 'notif.generic.title': 'Oznámení', + 'notif.generic.text': 'Máte nové oznámení', + 'notif.dev.unknown_event.title': '[DEV] Neznámá událost', + 'notif.dev.unknown_event.text': + 'Typ události "{event}" není registrován v EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/cs/notifications.ts b/shared/src/i18n/cs/notifications.ts new file mode 100644 index 00000000..999f26f7 --- /dev/null +++ b/shared/src/i18n/cs/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Oznámení', + 'notifications.markAllRead': 'Označit vše jako přečtené', + 'notifications.deleteAll': 'Smazat vše', + 'notifications.showAll': 'Zobrazit všechna oznámení', + 'notifications.empty': 'Žádná oznámení', + 'notifications.emptyDescription': 'Vše máte přečteno!', + 'notifications.all': 'Vše', + 'notifications.unreadOnly': 'Nepřečtené', + 'notifications.markRead': 'Označit jako přečtené', + 'notifications.markUnread': 'Označit jako nepřečtené', + 'notifications.delete': 'Smazat', + 'notifications.system': 'Systém', + 'notifications.synologySessionCleared.title': 'Synology Photos odpojeno', + 'notifications.synologySessionCleared.text': + 'Váš server nebo účet se změnil — přejděte do Nastavení a znovu otestujte připojení.', + 'notifications.test.title': 'Testovací oznámení od {actor}', + 'notifications.test.text': 'Toto je jednoduché testovací oznámení.', + 'notifications.test.booleanTitle': '{actor} žádá o vaše schválení', + 'notifications.test.booleanText': 'Testovací oznámení s volbou.', + 'notifications.test.accept': 'Schválit', + 'notifications.test.decline': 'Odmítnout', + 'notifications.test.navigateTitle': 'Podívejte se na toto', + 'notifications.test.navigateText': 'Testovací navigační oznámení.', + 'notifications.test.goThere': 'Přejít tam', + 'notifications.test.adminTitle': 'Hromadná zpráva pro správce', + 'notifications.test.adminText': + '{actor} odeslal testovací oznámení všem správcům.', + 'notifications.test.tripTitle': '{actor} přispěl do vašeho výletu', + 'notifications.test.tripText': 'Testovací oznámení pro výlet "{trip}".', + 'notifications.versionAvailable.title': 'Dostupná aktualizace', + 'notifications.versionAvailable.text': 'TREK {version} je nyní k dispozici.', + 'notifications.versionAvailable.button': 'Zobrazit podrobnosti', +}; +export default notifications; diff --git a/shared/src/i18n/cs/oauth.ts b/shared/src/i18n/cs/oauth.ts new file mode 100644 index 00000000..5ff46d29 --- /dev/null +++ b/shared/src/i18n/cs/oauth.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Výlety', + 'oauth.scope.group.places': 'Místa', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Balení', + 'oauth.scope.group.todos': 'Úkoly', + 'oauth.scope.group.budget': 'Rozpočet', + 'oauth.scope.group.reservations': 'Rezervace', + 'oauth.scope.group.collab': 'Spolupráce', + 'oauth.scope.group.notifications': 'Oznámení', + 'oauth.scope.group.vacay': 'Dovolená', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Počasí', + 'oauth.scope.group.journey': 'Cestovní deník', + 'oauth.scope.trips:read.label': 'Zobrazit výlety a itineráře', + 'oauth.scope.trips:read.description': 'Číst výlety, dny, poznámky a členy', + 'oauth.scope.trips:write.label': 'Upravit výlety a itineráře', + 'oauth.scope.trips:write.description': + 'Vytvářet a aktualizovat výlety, dny, poznámky a spravovat členy', + 'oauth.scope.trips:delete.label': 'Mazat výlety', + 'oauth.scope.trips:delete.description': + 'Trvale smazat celé výlety — tato akce je nevratná', + 'oauth.scope.trips:share.label': 'Spravovat sdílené odkazy', + 'oauth.scope.trips:share.description': + 'Vytvářet, aktualizovat a rušit veřejné sdílené odkazy', + 'oauth.scope.places:read.label': 'Zobrazit místa a mapová data', + 'oauth.scope.places:read.description': + 'Číst místa, denní přiřazení, štítky a kategorie', + 'oauth.scope.places:write.label': 'Spravovat místa', + 'oauth.scope.places:write.description': + 'Vytvářet, aktualizovat a mazat místa, přiřazení a štítky', + 'oauth.scope.atlas:read.label': 'Zobrazit Atlas', + 'oauth.scope.atlas:read.description': + 'Číst navštívené země, regiony a seznam přání', + 'oauth.scope.atlas:write.label': 'Spravovat Atlas', + 'oauth.scope.atlas:write.description': + 'Označovat navštívené země a regiony, spravovat seznam přání', + 'oauth.scope.packing:read.label': 'Zobrazit seznamy balení', + 'oauth.scope.packing:read.description': + 'Číst položky, tašky a přiřazení kategorií', + 'oauth.scope.packing:write.label': 'Spravovat seznamy balení', + 'oauth.scope.packing:write.description': + 'Přidávat, aktualizovat, mazat, označovat a řadit položky a tašky', + 'oauth.scope.todos:read.label': 'Zobrazit seznamy úkolů', + 'oauth.scope.todos:read.description': + 'Číst úkoly výletu a přiřazení kategorií', + 'oauth.scope.todos:write.label': 'Spravovat seznamy úkolů', + 'oauth.scope.todos:write.description': + 'Vytvářet, aktualizovat, označovat, mazat a řadit úkoly', + 'oauth.scope.budget:read.label': 'Zobrazit rozpočet', + 'oauth.scope.budget:read.description': + 'Číst položky rozpočtu a přehled výdajů', + 'oauth.scope.budget:write.label': 'Spravovat rozpočet', + 'oauth.scope.budget:write.description': + 'Vytvářet, aktualizovat a mazat položky rozpočtu', + 'oauth.scope.reservations:read.label': 'Zobrazit rezervace', + 'oauth.scope.reservations:read.description': + 'Číst rezervace a podrobnosti ubytování', + 'oauth.scope.reservations:write.label': 'Spravovat rezervace', + 'oauth.scope.reservations:write.description': + 'Vytvářet, aktualizovat, mazat a řadit rezervace', + 'oauth.scope.collab:read.label': 'Zobrazit spolupráci', + 'oauth.scope.collab:read.description': + 'Číst poznámky, ankety a zprávy spolupráce', + 'oauth.scope.collab:write.label': 'Spravovat spolupráci', + 'oauth.scope.collab:write.description': + 'Vytvářet, aktualizovat a mazat poznámky, ankety a zprávy', + 'oauth.scope.notifications:read.label': 'Zobrazit oznámení', + 'oauth.scope.notifications:read.description': + 'Číst oznámení v aplikaci a počty nepřečtených', + 'oauth.scope.notifications:write.label': 'Spravovat oznámení', + 'oauth.scope.notifications:write.description': + 'Označovat oznámení jako přečtená a reagovat na ně', + 'oauth.scope.vacay:read.label': 'Zobrazit plány dovolené', + 'oauth.scope.vacay:read.description': + 'Číst data plánování dovolené, záznamy a statistiky', + 'oauth.scope.vacay:write.label': 'Spravovat plány dovolené', + 'oauth.scope.vacay:write.description': + 'Vytvářet a spravovat záznamy dovolené, svátky a týmové plány', + 'oauth.scope.geo:read.label': 'Mapy a geokódování', + 'oauth.scope.geo:read.description': + 'Vyhledávat místa, řešit URL map a zpětně geokódovat souřadnice', + 'oauth.scope.weather:read.label': 'Předpovědi počasí', + 'oauth.scope.weather:read.description': + 'Získávat předpovědi počasí pro místa a data výletu', + 'oauth.scope.journey:read.label': 'Zobrazit cestovní deníky', + 'oauth.scope.journey:read.description': + 'Číst cestovní deníky, záznamy a seznam přispěvatelů', + 'oauth.scope.journey:write.label': 'Spravovat cestovní deníky', + 'oauth.scope.journey:write.description': + 'Vytvářet, aktualizovat a mazat cestovní deníky a jejich záznamy', + 'oauth.scope.journey:share.label': 'Spravovat odkazy na cestovní deníky', + 'oauth.scope.journey:share.description': + 'Vytvářet, aktualizovat a rušit veřejné sdílené odkazy na cestovní deníky', +}; +export default oauth; diff --git a/shared/src/i18n/cs/packing.ts b/shared/src/i18n/cs/packing.ts new file mode 100644 index 00000000..4bbb20c6 --- /dev/null +++ b/shared/src/i18n/cs/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Seznam věcí', + 'packing.empty': 'Seznam věcí je prázdný', + 'packing.import': 'Importovat', + 'packing.importTitle': 'Importovat seznam', + 'packing.importHint': + 'Jedna položka na řádek. Formát: Kategorie, Název, Váha v g (volitelné), Zavazadlo (volitelné), checked/unchecked (volitelné)', + 'packing.importPlaceholder': + 'Hygiena, Zubní kartáček\nOblečení, Trička, 200\nDokumenty, Pas, , Příruční zavazadlo\nElektronika, Nabíječka, 50, Kufr, checked', + 'packing.importCsv': 'Načíst CSV/TXT', + 'packing.importAction': 'Importovat {count}', + 'packing.importSuccess': '{count} položek importováno', + 'packing.importError': 'Import se nezdařil', + 'packing.importEmpty': 'Žádné položky k importu', + 'packing.progress': '{packed} z {total} zabaleno ({percent} %)', + 'packing.clearChecked': 'Odstranit {count} hotových', + 'packing.clearCheckedShort': 'Odstranit {count}', + 'packing.suggestions': 'Návrhy', + 'packing.suggestionsTitle': 'Přidat návrhy', + 'packing.allSuggested': 'Všechny návrhy byly přidány', + 'packing.allPacked': 'Vše je zabaleno!', + 'packing.addPlaceholder': 'Přidat novou položku...', + 'packing.categoryPlaceholder': 'Kategorie...', + 'packing.filterAll': 'Vše', + 'packing.filterOpen': 'K zabalení', + 'packing.filterDone': 'Hotovo', + 'packing.emptyTitle': 'Seznam věcí je prázdný', + 'packing.emptyHint': 'Přidejte položky nebo použijte návrhy', + 'packing.emptyFiltered': 'Žádné položky neodpovídají filtru', + 'packing.menuRename': 'Přejmenovat', + 'packing.menuCheckAll': 'Označit vše', + 'packing.menuUncheckAll': 'Odznačit vše', + 'packing.menuDeleteCat': 'Smazat kategorii', + 'packing.noMembers': 'Žádní členové cesty', + 'packing.addItem': 'Přidat položku', + 'packing.addItemPlaceholder': 'Název položky...', + 'packing.addCategory': 'Přidat kategorii', + 'packing.newCategoryPlaceholder': 'Název kategorie (např. Oblečení)', + 'packing.applyTemplate': 'Použít šablonu', + 'packing.template': 'Šablona', + 'packing.templateApplied': '{count} položek přidáno ze šablony', + 'packing.templateError': 'Šablonu se nepodařilo použít', + 'packing.saveAsTemplate': 'Uložit jako šablonu', + 'packing.templateName': 'Název šablony', + 'packing.templateSaved': 'Seznam balení uložen jako šablona', + 'packing.bags': 'Zavazadla', + 'packing.noBag': 'Nepřiřazeno', + 'packing.totalWeight': 'Celková váha', + 'packing.bagName': 'Název zavazadla...', + 'packing.addBag': 'Přidat zavazadlo', + 'packing.changeCategory': 'Změnit kategorii', + 'packing.confirm.clearChecked': + 'Opravdu chcete odstranit {count} zabalených položek?', + 'packing.confirm.deleteCat': + 'Opravdu chcete smazat kategorii „{name}" s {count} položkami?', + 'packing.defaultCategory': 'Ostatní', + 'packing.toast.saveError': 'Uložení se nezdařilo', + 'packing.toast.deleteError': 'Smazání se nezdařilo', + 'packing.toast.renameError': 'Přejmenování se nezdařilo', + 'packing.toast.addError': 'Přidání se nezdařilo', + 'packing.suggestions.items': [ + { + name: 'Pas', + category: 'Dokumenty', + }, + { + name: 'Občanský průkaz', + category: 'Dokumenty', + }, + { + name: 'Cestovní pojištění', + category: 'Dokumenty', + }, + { + name: 'Letenky', + category: 'Dokumenty', + }, + { + name: 'Platební karta', + category: 'Finance', + }, + { + name: 'Hotovost', + category: 'Finance', + }, + { + name: 'Víza', + category: 'Dokumenty', + }, + { + name: 'Trička', + category: 'Oblečení', + }, + { + name: 'Kalhoty', + category: 'Oblečení', + }, + { + name: 'Spodní prádlo', + category: 'Oblečení', + }, + { + name: 'Ponožky', + category: 'Oblečení', + }, + { + name: 'Bunda', + category: 'Oblečení', + }, + { + name: 'Pyžamo', + category: 'Oblečení', + }, + { + name: 'Plavky', + category: 'Oblečení', + }, + { + name: 'Pláštěnka', + category: 'Oblečení', + }, + { + name: 'Pohodlné boty', + category: 'Oblečení', + }, + { + name: 'Zubní kartáček', + category: 'Hygiena', + }, + { + name: 'Zubní pasta', + category: 'Hygiena', + }, + { + name: 'Šampón', + category: 'Hygiena', + }, + { + name: 'Deodorant', + category: 'Hygiena', + }, + { + name: 'Opalovací krém', + category: 'Hygiena', + }, + { + name: 'Holicí strojek', + category: 'Hygiena', + }, + { + name: 'Nabíječka', + category: 'Elektronika', + }, + { + name: 'Powerbanka', + category: 'Elektronika', + }, + { + name: 'Sluchátka', + category: 'Elektronika', + }, + { + name: 'Cestovní adaptér', + category: 'Elektronika', + }, + { + name: 'Fotoaparát', + category: 'Elektronika', + }, + { + name: 'Léky proti bolesti', + category: 'Zdraví', + }, + { + name: 'Náplasti', + category: 'Zdraví', + }, + { + name: 'Dezinfekce', + category: 'Zdraví', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/cs/pdf.ts b/shared/src/i18n/cs/pdf.ts new file mode 100644 index 00000000..dae561e7 --- /dev/null +++ b/shared/src/i18n/cs/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Cestovní plán', + 'pdf.planned': 'Naplánováno', + 'pdf.costLabel': 'Náklady EUR', + 'pdf.preview': 'Náhled PDF', + 'pdf.saveAsPdf': 'Uložit jako PDF', +}; +export default pdf; diff --git a/shared/src/i18n/cs/perm.ts b/shared/src/i18n/cs/perm.ts new file mode 100644 index 00000000..aedda02d --- /dev/null +++ b/shared/src/i18n/cs/perm.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Nastavení oprávnění', + 'perm.subtitle': 'Určete, kdo může provádět akce v aplikaci', + 'perm.saved': 'Nastavení oprávnění uloženo', + 'perm.resetDefaults': 'Obnovit výchozí', + 'perm.customized': 'upraveno', + 'perm.level.admin': 'Pouze administrátor', + 'perm.level.tripOwner': 'Vlastník výletu', + 'perm.level.tripMember': 'Členové výletu', + 'perm.level.everybody': 'Všichni', + 'perm.cat.trip': 'Správa výletů', + 'perm.cat.members': 'Správa členů', + 'perm.cat.files': 'Soubory', + 'perm.cat.content': 'Obsah a plán', + 'perm.cat.extras': 'Rozpočet, balení a spolupráce', + 'perm.action.trip_create': 'Vytvářet výlety', + 'perm.action.trip_edit': 'Upravit detaily výletu', + 'perm.action.trip_delete': 'Smazat výlety', + 'perm.action.trip_archive': 'Archivovat / odarchivovat výlety', + 'perm.action.trip_cover_upload': 'Nahrát titulní obrázek', + 'perm.action.member_manage': 'Přidat / odebrat členy', + 'perm.action.file_upload': 'Nahrát soubory', + 'perm.action.file_edit': 'Upravit metadata souborů', + 'perm.action.file_delete': 'Smazat soubory', + 'perm.action.place_edit': 'Přidat / upravit / smazat místa', + 'perm.action.day_edit': 'Upravit dny, poznámky a přiřazení', + 'perm.action.reservation_edit': 'Spravovat rezervace', + 'perm.action.budget_edit': 'Spravovat rozpočet', + 'perm.action.packing_edit': 'Spravovat seznamy balení', + 'perm.action.collab_edit': 'Spolupráce (poznámky, hlasování, chat)', + 'perm.action.share_manage': 'Spravovat odkazy ke sdílení', + 'perm.actionHint.trip_create': 'Kdo může vytvářet nové výlety', + 'perm.actionHint.trip_edit': + 'Kdo může měnit název, data, popis a měnu výletu', + 'perm.actionHint.trip_delete': 'Kdo může trvale smazat výlet', + 'perm.actionHint.trip_archive': 'Kdo může archivovat nebo odarchivovat výlet', + 'perm.actionHint.trip_cover_upload': + 'Kdo může nahrát nebo změnit titulní obrázek', + 'perm.actionHint.member_manage': 'Kdo může pozvat nebo odebrat členy výletu', + 'perm.actionHint.file_upload': 'Kdo může nahrávat soubory k výletu', + 'perm.actionHint.file_edit': 'Kdo může upravovat popisy a odkazy souborů', + 'perm.actionHint.file_delete': + 'Kdo může přesunout soubory do koše nebo je trvale smazat', + 'perm.actionHint.place_edit': 'Kdo může přidávat, upravovat nebo mazat místa', + 'perm.actionHint.day_edit': + 'Kdo může upravovat dny, poznámky ke dnům a přiřazení míst', + 'perm.actionHint.reservation_edit': + 'Kdo může vytvářet, upravovat nebo mazat rezervace', + 'perm.actionHint.budget_edit': + 'Kdo může vytvářet, upravovat nebo mazat položky rozpočtu', + 'perm.actionHint.packing_edit': 'Kdo může spravovat položky balení a tašky', + 'perm.actionHint.collab_edit': + 'Kdo může vytvářet poznámky, hlasování a posílat zprávy', + 'perm.actionHint.share_manage': + 'Kdo může vytvářet nebo mazat veřejné odkazy ke sdílení', +}; +export default perm; diff --git a/shared/src/i18n/cs/photos.ts b/shared/src/i18n/cs/photos.ts new file mode 100644 index 00000000..6acfdc35 --- /dev/null +++ b/shared/src/i18n/cs/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Fotografie', + 'photos.subtitle': '{count} fotek pro {trip}', + 'photos.dropHere': 'Přetáhněte fotografie sem...', + 'photos.dropHereActive': 'Přetáhněte fotografie sem', + 'photos.captionForAll': 'Popisek (pro všechny)', + 'photos.captionPlaceholder': 'Volitelný popisek...', + 'photos.addCaption': 'Přidat popisek...', + 'photos.allDays': 'Všechny dny', + 'photos.noPhotos': 'Zatím žádné fotky', + 'photos.uploadHint': 'Nahrajte své cestovní fotky', + 'photos.clickToSelect': 'nebo klikněte pro výběr', + 'photos.linkPlace': 'Propojit s místem', + 'photos.noPlace': 'Žádné místo', + 'photos.uploadN': 'Nahrát {n} fotek', + 'photos.linkDay': 'Propojit den', + 'photos.noDay': 'Žádný den', + 'photos.dayLabel': 'Den {number}', + 'photos.photoSelected': 'Fotografie vybrána', + 'photos.photosSelected': 'Fotografie vybrány', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · až 30 fotografií', +}; +export default photos; diff --git a/shared/src/i18n/cs/places.ts b/shared/src/i18n/cs/places.ts new file mode 100644 index 00000000..93c227d3 --- /dev/null +++ b/shared/src/i18n/cs/places.ts @@ -0,0 +1,91 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Přidat místo/aktivitu', + 'places.importFile': 'Importovat soubor', + 'places.sidebarDrop': 'Pusťte pro import', + 'places.importFileHint': + 'Importujte soubory .gpx, .kml nebo .kmz z nástrojů jako Google My Maps, Google Earth nebo GPS tracker.', + 'places.importFileDropHere': + 'Klikněte pro výběr souboru nebo jej přetáhněte sem', + 'places.importFileDropActive': 'Přetáhněte soubor pro výběr', + 'places.importFileUnsupported': + 'Nepodporovaný typ souboru. Použijte .gpx, .kml nebo .kmz.', + 'places.importFileTooLarge': + 'Soubor je příliš velký. Maximální velikost nahrání je {maxMb} MB.', + 'places.importFileError': 'Import se nezdařil', + 'places.importAllSkipped': 'Všechna místa již byla v cestě.', + 'places.gpxImported': '{count} míst importováno z GPX', + 'places.gpxImportTypes': 'Co chcete importovat?', + 'places.gpxImportWaypoints': 'Trasové body', + 'places.gpxImportRoutes': 'Trasy', + 'places.gpxImportTracks': 'Trasy GPS (s geometrií)', + 'places.gpxImportNoneSelected': 'Vyberte alespoň jeden typ k importu.', + 'places.kmlImportTypes': 'Co chcete importovat?', + 'places.kmlImportPoints': 'Body (Placemarks)', + 'places.kmlImportPaths': 'Trasy (LineStrings)', + 'places.kmlImportNoneSelected': 'Vyberte alespoň jeden typ.', + 'places.selectionCount': '{count} vybráno', + 'places.deleteSelected': 'Smazat vybrané', + 'places.kmlKmzImported': 'Importováno {count} míst z KMZ/KML', + 'places.urlResolved': 'Místo importováno z URL', + 'places.importList': 'Import seznamu', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importováno: {created} • Přeskočeno: {skipped}', + 'places.importGoogleList': 'Google Seznam', + 'places.importNaverList': 'Naver Seznam', + 'places.googleListHint': + 'Vložte sdílený odkaz na seznam Google Maps pro import všech míst.', + 'places.googleListImported': '{count} míst importováno ze seznamu "{list}"', + 'places.googleListError': 'Import seznamu Google Maps se nezdařil', + 'places.naverListHint': + 'Vložte sdílený odkaz na seznam Naver Maps pro import všech míst.', + 'places.naverListImported': '{count} míst importováno ze seznamu "{list}"', + 'places.naverListError': 'Import seznamu Naver Maps se nezdařil', + 'places.viewDetails': 'Zobrazit detaily', + 'places.assignToDay': 'Přidat do kterého dne?', + 'places.all': 'Vše', + 'places.unplanned': 'Nezařazené', + 'places.filterTracks': 'Trasy', + 'places.search': 'Hledat místa...', + 'places.allCategories': 'Všechny kategorie', + 'places.categoriesSelected': 'kategorií', + 'places.clearFilter': 'Vymazat filtr', + 'places.count': '{count} míst', + 'places.countSingular': '1 místo', + 'places.allPlanned': 'Všechna místa jsou naplánována', + 'places.noneFound': 'Žádná místa nebyla nalezena', + 'places.editPlace': 'Upravit místo', + 'places.formName': 'Název', + 'places.formNamePlaceholder': 'např. Eiffelova věž', + 'places.formDescription': 'Popis', + 'places.formDescriptionPlaceholder': 'Krátký popis...', + 'places.formAddress': 'Adresa', + 'places.formAddressPlaceholder': 'Ulice, město, země', + 'places.formLat': 'Zeměpisná šířka', + 'places.formLng': 'Zeměpisná délka', + 'places.formCategory': 'Kategorie', + 'places.noCategory': 'Bez kategorie', + 'places.categoryNamePlaceholder': 'Název kategorie', + 'places.formTime': 'Čas', + 'places.startTime': 'Od', + 'places.endTime': 'Do', + 'places.endTimeBeforeStart': 'Čas konce je před časem začátku', + 'places.timeCollision': 'Časový překryv s:', + 'places.formWebsite': 'Webové stránky', + 'places.formNotes': 'Poznámky', + 'places.formNotesPlaceholder': 'Osobní poznámky...', + 'places.formReservation': 'Rezervace', + 'places.reservationNotesPlaceholder': + 'Poznámky k rezervaci, potvrzovací kód...', + 'places.mapsSearchPlaceholder': 'Hledat místa...', + 'places.mapsSearchError': 'Hledání místa se nezdařilo.', + 'places.loadingDetails': 'Načítání podrobností místa…', + 'places.osmHint': + 'Používáte hledání přes OpenStreetMap (bez fotek a hodnocení). Pro plné detaily přidejte Google API klíč v nastavení.', + 'places.osmActive': 'Hledání přes OpenStreetMap.', + 'places.categoryCreateError': 'Nepodařilo se vytvořit kategorii', + 'places.nameRequired': 'Prosím zadejte název', + 'places.saveError': 'Uložení se nezdařilo', +}; +export default places; diff --git a/shared/src/i18n/cs/planner.ts b/shared/src/i18n/cs/planner.ts new file mode 100644 index 00000000..a822557d --- /dev/null +++ b/shared/src/i18n/cs/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Místa', + 'planner.bookings': 'Rezervace', + 'planner.packingList': 'Seznam věcí', + 'planner.documents': 'Dokumenty', + 'planner.dayPlan': 'Denní plán', + 'planner.reservations': 'Rezervace', + 'planner.minTwoPlaces': 'Potřebujete alespoň 2 místa se souřadnicemi', + 'planner.noGeoPlaces': 'Žádná místa se souřadnicemi nejsou k dispozici', + 'planner.routeCalculated': 'Trasa vypočtena', + 'planner.routeCalcFailed': 'Trasu se nepodařilo vypočítat', + 'planner.routeError': 'Chyba při výpočtu trasy', + 'planner.icsExportFailed': 'Export ICS se nezdařil', + 'planner.routeOptimized': 'Trasa optimalizována', + 'planner.reservationUpdated': 'Rezervace aktualizována', + 'planner.reservationAdded': 'Rezervace přidána', + 'planner.confirmDeleteReservation': 'Smazat rezervaci?', + 'planner.reservationDeleted': 'Rezervace smazána', + 'planner.days': 'Dny', + 'planner.allPlaces': 'Všechna místa', + 'planner.totalPlaces': 'Celkem {n} míst', + 'planner.noDaysPlanned': 'Zatím nejsou naplánovány žádné dny', + 'planner.editTrip': 'Upravit cestu →', + 'planner.placeOne': '1 místo', + 'planner.placeN': '{n} míst', + 'planner.addNote': 'Přidat poznámku', + 'planner.noEntries': 'Pro tento den nejsou žádné záznamy', + 'planner.addPlace': 'Přidat místo/aktivitu', + 'planner.addPlaceShort': '+ Přidat místo/aktivitu', + 'planner.resPending': 'Rezervace čeká · ', + 'planner.resConfirmed': 'Rezervace potvrzena · ', + 'planner.notePlaceholder': 'Poznámka…', + 'planner.noteTimePlaceholder': 'Čas (volitelné)', + 'planner.noteExamplePlaceholder': + 'např. S3 ve 14:30 z hlavního nádraží, trajekt z přístaviště 7, přestávka na oběd…', + 'planner.totalCost': 'Celkové náklady', + 'planner.searchPlaces': 'Hledat místa…', + 'planner.allCategories': 'Všechny kategorie', + 'planner.noPlacesFound': 'Žádná místa nenalezena', + 'planner.addFirstPlace': 'Přidat první místo', + 'planner.noReservations': 'Žádné rezervace', + 'planner.addFirstReservation': 'Přidat první rezervaci', + 'planner.new': 'Nový', + 'planner.addToDay': '+ Den', + 'planner.calculating': 'Počítání…', + 'planner.route': 'Trasa', + 'planner.optimize': 'Optimalizovat', + 'planner.openGoogleMaps': 'Otevřít v Google Mapách', + 'planner.selectDayHint': + 'Vyberte den ze seznamu vlevo pro zobrazení denního plánu', + 'planner.noPlacesForDay': 'Zatím žádná místa pro tento den', + 'planner.addPlacesLink': 'Přidat místa →', + 'planner.minTotal': 'min. celkem', + 'planner.noReservation': 'Žádná rezervace', + 'planner.removeFromDay': 'Odebrat ze dne', + 'planner.addToThisDay': 'Přidat ke dni', + 'planner.overview': 'Přehled', + 'planner.noDays': 'Zatím žádné dny', + 'planner.editTripToAddDays': 'Upravte cestu pro přidání dnů', + 'planner.dayCount': '{n} dní', + 'planner.clickToUnlock': 'Klikněte pro odemčení', + 'planner.keepPosition': 'Zachovat pozici při optimalizaci trasy', + 'planner.dayDetails': 'Podrobnosti dne', + 'planner.dayN': 'Den {n}', +}; +export default planner; diff --git a/shared/src/i18n/cs/register.ts b/shared/src/i18n/cs/register.ts new file mode 100644 index 00000000..dbb18e5b --- /dev/null +++ b/shared/src/i18n/cs/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Hesla se neshodují', + 'register.passwordTooShort': 'Heslo musí mít alespoň 8 znaků', + 'register.failed': 'Registrace se nezdařila', + 'register.getStarted': 'Začínáme', + 'register.subtitle': + 'Vytvořte si účet a začněte plánovat svou vysněnou cestu.', + 'register.feature1': 'Neomezené plány cest', + 'register.feature2': 'Zobrazení na interaktivní mapě', + 'register.feature3': 'Správa míst a kategorií', + 'register.feature4': 'Sledování rezervací', + 'register.feature5': 'Vytváření seznamů věcí', + 'register.feature6': 'Ukládání fotek a souborů', + 'register.createAccount': 'Vytvořit účet', + 'register.startPlanning': 'Začít plánovat', + 'register.minChars': 'Min. 6 znaků', + 'register.confirmPassword': 'Potvrdit heslo', + 'register.repeatPassword': 'Heslo znovu', + 'register.registering': 'Registrace...', + 'register.register': 'Registrovat se', + 'register.hasAccount': 'Již máte účet?', + 'register.signIn': 'Přihlásit se', +}; +export default register; diff --git a/shared/src/i18n/cs/reservations.ts b/shared/src/i18n/cs/reservations.ts new file mode 100644 index 00000000..b4cf36a7 --- /dev/null +++ b/shared/src/i18n/cs/reservations.ts @@ -0,0 +1,117 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Rezervace', + 'reservations.empty': 'Zatím žádné rezervace', + 'reservations.emptyHint': 'Přidejte rezervace letů, hotelů a dalších', + 'reservations.add': 'Přidat rezervaci', + 'reservations.addManual': 'Ruční rezervace', + 'reservations.placeHint': + 'Tip: Rezervace je nejlepší vytvářet přímo z místa – propojí se tak s denním plánem.', + 'reservations.confirmed': 'Potvrzeno', + 'reservations.pending': 'Čeká na potvrzení', + 'reservations.summary': '{confirmed} potvrzených, {pending} čekajících', + 'reservations.fromPlan': 'Z plánu', + 'reservations.showFiles': 'Zobrazit soubory', + 'reservations.editTitle': 'Upravit rezervaci', + 'reservations.status': 'Stav', + 'reservations.datetime': 'Datum a čas', + 'reservations.startTime': 'Čas začátku', + 'reservations.endTime': 'Čas konce', + 'reservations.date': 'Datum', + 'reservations.time': 'Čas', + 'reservations.timeAlt': 'Čas (alternativní, např. 19:30)', + 'reservations.notes': 'Poznámky', + 'reservations.notesPlaceholder': 'Další poznámky...', + 'reservations.meta.airline': 'Letecká společnost', + 'reservations.meta.flightNumber': 'Číslo letu', + 'reservations.meta.from': 'Z', + 'reservations.meta.to': 'Do', + 'reservations.needsReview': 'Zkontrolovat', + 'reservations.needsReviewHint': + 'Letiště nebylo možné automaticky rozpoznat — potvrďte prosím místo.', + 'reservations.searchLocation': 'Hledat stanici, přístav, adresu...', + 'reservations.meta.trainNumber': 'Číslo vlaku', + 'reservations.meta.platform': 'Nástupiště', + 'reservations.meta.seat': 'Sedadlo', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in do', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Ubytování', + 'reservations.meta.pickAccommodation': 'Propojit s ubytováním', + 'reservations.meta.noAccommodation': 'Nic', + 'reservations.meta.hotelPlace': 'Ubytování', + 'reservations.meta.pickHotel': 'Vybrat ubytování', + 'reservations.meta.fromDay': 'Od dne', + 'reservations.meta.toDay': 'Do dne', + 'reservations.meta.selectDay': 'Vyberte den', + 'reservations.type.flight': 'Let', + 'reservations.type.hotel': 'Ubytování', + 'reservations.type.restaurant': 'Restaurace', + 'reservations.type.train': 'Vlak', + 'reservations.type.car': 'Auto', + 'reservations.type.cruise': 'Plavba', + 'reservations.type.event': 'Událost', + 'reservations.type.tour': 'Prohlídka', + 'reservations.type.other': 'Jiné', + 'reservations.confirm.delete': 'Opravdu chcete smazat rezervaci „{name}”?', + 'reservations.confirm.deleteTitle': 'Smazat rezervaci?', + 'reservations.confirm.deleteBody': '„{name}” bude trvale smazána.', + 'reservations.toast.updated': 'Rezervace aktualizována', + 'reservations.toast.removed': 'Rezervace smazána', + 'reservations.toast.fileUploaded': 'Soubor byl nahrán', + 'reservations.toast.uploadError': 'Nahrávání se nezdařilo', + 'reservations.newTitle': 'Nová rezervace', + 'reservations.bookingType': 'Typ rezervace', + 'reservations.titleLabel': 'Název', + 'reservations.titlePlaceholder': 'např. Let LH123, Hotel Adlon...', + 'reservations.locationAddress': 'Místo / Adresa', + 'reservations.locationPlaceholder': 'Adresa, letiště, hotel...', + 'reservations.confirmationCode': 'Rezervační kód', + 'reservations.confirmationPlaceholder': 'např. ABC12345', + 'reservations.day': 'Den', + 'reservations.noDay': 'Žádný den', + 'reservations.place': 'Místo', + 'reservations.noPlace': 'Žádné místo', + 'reservations.pendingSave': 'bude uloženo…', + 'reservations.uploading': 'Nahrávání...', + 'reservations.attachFile': 'Přiložit soubor', + 'reservations.linkExisting': 'Propojit stávající soubor', + 'reservations.toast.saveError': 'Uložení se nezdařilo', + 'reservations.toast.updateError': 'Aktualizace se nezdařila', + 'reservations.toast.deleteError': 'Smazání se nezdařilo', + 'reservations.confirm.remove': 'Odstranit rezervaci pro „{name}”?', + 'reservations.linkAssignment': 'Propojit s přiřazením dne', + 'reservations.pickAssignment': 'Vyberte přiřazení z vašeho plánu...', + 'reservations.noAssignment': 'Bez propojení (samostatné)', + 'reservations.price': 'Cena', + 'reservations.budgetCategory': 'Kategorie rozpočtu', + 'reservations.budgetCategoryPlaceholder': 'např. Doprava, Ubytování', + 'reservations.budgetCategoryAuto': 'Auto (podle typu rezervace)', + 'reservations.budgetHint': + 'Při ukládání bude automaticky vytvořena položka rozpočtu.', + 'reservations.departureDate': 'Odlet', + 'reservations.arrivalDate': 'Přílet', + 'reservations.departureTime': 'Čas odletu', + 'reservations.arrivalTime': 'Čas příletu', + 'reservations.pickupDate': 'Vyzvednutí', + 'reservations.returnDate': 'Vrácení', + 'reservations.pickupTime': 'Čas vyzvednutí', + 'reservations.returnTime': 'Čas vrácení', + 'reservations.endDate': 'Datum konce', + 'reservations.meta.departureTimezone': 'TZ odletu', + 'reservations.meta.arrivalTimezone': 'TZ příletu', + 'reservations.span.departure': 'Odlet', + 'reservations.span.arrival': 'Přílet', + 'reservations.span.inTransit': 'Na cestě', + 'reservations.span.pickup': 'Vyzvednutí', + 'reservations.span.return': 'Vrácení', + 'reservations.span.active': 'Aktivní', + 'reservations.span.start': 'Začátek', + 'reservations.span.end': 'Konec', + 'reservations.span.ongoing': 'Probíhá', + 'reservations.validation.endBeforeStart': + 'Datum/čas konce musí být po datu/čase začátku', + 'reservations.addBooking': 'Přidat rezervaci', +}; +export default reservations; diff --git a/shared/src/i18n/cs/settings.ts b/shared/src/i18n/cs/settings.ts new file mode 100644 index 00000000..f7b9deae --- /dev/null +++ b/shared/src/i18n/cs/settings.ts @@ -0,0 +1,299 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Nastavení', + 'settings.subtitle': 'Upravte své osobní nastavení', + 'settings.tabs.display': 'Zobrazení', + 'settings.tabs.map': 'Mapa', + 'settings.tabs.notifications': 'Oznámení', + 'settings.tabs.integrations': 'Integrace', + 'settings.tabs.account': 'Účet', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'O aplikaci', + 'settings.map': 'Mapy', + 'settings.mapTemplate': 'Šablona mapy', + 'settings.mapTemplatePlaceholder.select': 'Vyberte šablonu...', + 'settings.mapDefaultHint': 'Ponechte prázdné pro OpenStreetMap (výchozí)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL šablony pro mapové dlaždice', + 'settings.mapProvider': 'Poskytovatel mapy', + 'settings.mapProviderHint': + 'Ovlivňuje mapy v Trip Planneru a Journey. Atlas vždy používá Leaflet.', + 'settings.mapLeafletSubtitle': 'Klasické 2D, libovolné rastrové dlaždice', + 'settings.mapMapboxSubtitle': 'Vektorové dlaždice, 3D budovy a terén', + 'settings.mapExperimental': 'Experimentální', + 'settings.mapMapboxToken': 'Mapbox přístupový token', + 'settings.mapMapboxTokenHint': 'Veřejný token (pk.*) z', + 'settings.mapMapboxTokenLink': 'mapbox.com → Přístupové tokeny', + 'settings.mapStyle': 'Styl mapy', + 'settings.mapStylePlaceholder': 'Vyberte styl Mapbox', + 'settings.mapStyleHint': + 'Preset nebo vaše vlastní URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': '3D budovy a terén', + 'settings.map3dHint': + 'Náklon + skutečné 3D vyvýšení budov — funguje s každým stylem, včetně satelitu.', + 'settings.mapHighQuality': 'Režim vysoké kvality', + 'settings.mapHighQualityHint': + 'Antialiasing + zobrazení glóbu pro ostřejší hrany a realistický pohled na svět.', + 'settings.mapHighQualityWarning': + 'Může ovlivnit výkon na slabších zařízeních.', + 'settings.mapTipLabel': 'Tip:', + 'settings.mapTip': + 'Pravé tlačítko myši a táhněte pro rotaci/náklon mapy. Prostřední tlačítko pro přidání místa (pravé tlačítko je vyhrazeno pro rotaci).', + 'settings.latitude': 'Zeměpisná šířka', + 'settings.longitude': 'Zeměpisná délka', + 'settings.saveMap': 'Uložit nastavení mapy', + 'settings.apiKeys': 'API klíče', + 'settings.mapsKey': 'Google Maps API klíč', + 'settings.mapsKeyHint': + 'Pro vyhledávání míst. Vyžaduje Places API (New). Získáte na console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API klíč', + 'settings.weatherKeyHint': + 'Pro předpověď počasí. Zdarma na openweathermap.org/api', + 'settings.keyPlaceholder': 'Vložte klíč...', + 'settings.configured': 'Nastaveno', + 'settings.saveKeys': 'Uložit klíče', + 'settings.display': 'Zobrazení', + 'settings.colorMode': 'Barevné schéma', + 'settings.light': 'Světlé', + 'settings.dark': 'Tmavé', + 'settings.auto': 'Automatické', + 'settings.language': 'Jazyk', + 'settings.temperature': 'Jednotky teploty', + 'settings.timeFormat': 'Formát času', + 'settings.blurBookingCodes': 'Skrýt rezervační kódy', + 'settings.notifications': 'Oznámení', + 'settings.notifyTripInvite': 'Pozvánky na cesty', + 'settings.notifyBookingChange': 'Změny rezervací', + 'settings.notifyTripReminder': 'Připomínky cest', + 'settings.notifyTodoDue': 'Úkol se blíží', + 'settings.notifyVacayInvite': 'Pozvánky k propojení Vacay', + 'settings.notifyPhotosShared': 'Sdílené fotky (Immich)', + 'settings.notifyCollabMessage': 'Zprávy v chatu (Collab)', + 'settings.notifyPackingTagged': 'Seznam balení: přiřazení', + 'settings.notifyWebhook': 'Webhook oznámení', + 'settings.notificationsDisabled': + 'Oznámení nejsou nakonfigurována. Požádejte správce o aktivaci e-mailových nebo webhookových oznámení.', + 'settings.notificationsActive': 'Aktivní kanál', + 'settings.notificationsManagedByAdmin': + 'Události oznámení jsou konfigurovány administrátorem.', + 'settings.on': 'Zapnuto', + 'settings.off': 'Vypnuto', + 'settings.mcp.title': 'Konfigurace MCP', + 'settings.mcp.endpoint': 'MCP endpoint', + 'settings.mcp.clientConfig': 'Konfigurace klienta', + 'settings.mcp.clientConfigHint': + 'Nahraďte API tokenem ze seznamu níže. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Nahraďte a přihlašovacími údaji ze klienta OAuth 2.1, který jste vytvořili výše. mcp-remote při prvním připojení otevře prohlížeč pro dokončení autorizace. Cestu k npx může být nutné upravit pro váš systém (např. C:\\PROGRA~1\\nodejs\\npx.cmd ve Windows).', + 'settings.mcp.copy': 'Kopírovat', + 'settings.mcp.copied': 'Zkopírováno!', + 'settings.mcp.apiTokens': 'API tokeny', + 'settings.mcp.createToken': 'Vytvořit nový token', + 'settings.mcp.noTokens': + 'Zatím žádné tokeny. Vytvořte jeden pro připojení MCP klientů.', + 'settings.mcp.tokenCreatedAt': 'Vytvořen', + 'settings.mcp.tokenUsedAt': 'Použit', + 'settings.mcp.deleteTokenTitle': 'Smazat token', + 'settings.mcp.deleteTokenMessage': + 'Tento token přestane okamžitě fungovat. Všichni MCP klienti, kteří ho používají, ztratí přístup.', + 'settings.mcp.modal.createTitle': 'Vytvořit API token', + 'settings.mcp.modal.tokenName': 'Název tokenu', + 'settings.mcp.modal.tokenNamePlaceholder': + 'např. Claude Desktop, Pracovní notebook', + 'settings.mcp.modal.creating': 'Vytváření…', + 'settings.mcp.modal.create': 'Vytvořit token', + 'settings.mcp.modal.createdTitle': 'Token vytvořen', + 'settings.mcp.modal.createdWarning': + 'Tento token bude zobrazen pouze jednou. Zkopírujte a uložte ho nyní — nelze ho obnovit.', + 'settings.mcp.modal.done': 'Hotovo', + 'settings.mcp.toast.created': 'Token vytvořen', + 'settings.mcp.toast.createError': 'Nepodařilo se vytvořit token', + 'settings.mcp.toast.deleted': 'Token smazán', + 'settings.mcp.toast.deleteError': 'Nepodařilo se smazat token', + 'settings.mcp.apiTokensDeprecated': + 'API tokeny jsou zastaralé a budou odstraněny v budoucí verzi. Místo toho použijte klienty OAuth 2.1.', + 'settings.oauth.clients': 'Klienti OAuth 2.1', + 'settings.oauth.clientsHint': + 'Zaregistrujte klienty OAuth 2.1, aby se aplikace MCP třetích stran (Claude Web, Cursor atd.) mohly připojit bez statických tokenů.', + 'settings.oauth.createClient': 'Nový klient', + 'settings.oauth.noClients': 'Žádní klienti OAuth nejsou zaregistrováni.', + 'settings.oauth.clientId': 'ID klienta', + 'settings.oauth.clientSecret': 'Tajný klíč klienta', + 'settings.oauth.deleteClient': 'Smazat klienta', + 'settings.oauth.deleteClientMessage': + 'Tento klient a všechny aktivní relace budou trvale odstraněny. Jakákoliv aplikace, která ho používá, okamžitě ztratí přístup.', + 'settings.oauth.rotateSecret': 'Obnovit tajný klíč', + 'settings.oauth.rotateSecretMessage': + 'Bude vygenerován nový tajný klíč klienta a všechny stávající relace budou okamžitě zneplatněny. Aktualizujte aplikaci před zavřením tohoto dialogu.', + 'settings.oauth.rotateSecretConfirm': 'Obnovit', + 'settings.oauth.rotateSecretConfirming': 'Obnovování…', + 'settings.oauth.rotateSecretDoneTitle': 'Nový tajný klíč vygenerován', + 'settings.oauth.rotateSecretDoneWarning': + 'Tento tajný klíč se zobrazí pouze jednou. Zkopírujte ho nyní a aktualizujte aplikaci — všechny předchozí relace byly zneplatněny.', + 'settings.oauth.activeSessions': 'Aktivní relace OAuth', + 'settings.oauth.sessionScopes': 'Oprávnění', + 'settings.oauth.sessionExpires': 'Vyprší', + 'settings.oauth.revoke': 'Odvolat', + 'settings.oauth.revokeSession': 'Odvolat relaci', + 'settings.oauth.revokeSessionMessage': + 'Tím se okamžitě odvolá přístup pro tuto relaci OAuth.', + 'settings.oauth.modal.createTitle': 'Zaregistrovat klienta OAuth', + 'settings.oauth.modal.presets': 'Rychlá nastavení', + 'settings.oauth.modal.clientName': 'Název aplikace', + 'settings.oauth.modal.clientNamePlaceholder': + 'např. Claude Web, Moje MCP aplikace', + 'settings.oauth.modal.redirectUris': 'Přesměrovací URI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Jedno URI na řádek. Vyžadováno HTTPS (localhost vyjmuto). Vyžadována přesná shoda.', + 'settings.oauth.modal.scopes': 'Povolená oprávnění', + 'settings.oauth.modal.scopesHint': + 'list_trips a get_trip_summary jsou vždy dostupné — bez požadovaného oprávnění. Umožňují AI zjistit potřebná ID výletů.', + 'settings.oauth.modal.selectAll': 'Vybrat vše', + 'settings.oauth.modal.deselectAll': 'Zrušit výběr', + 'settings.oauth.modal.creating': 'Registrování…', + 'settings.oauth.modal.create': 'Zaregistrovat klienta', + 'settings.oauth.modal.createdTitle': 'Klient zaregistrován', + 'settings.oauth.modal.createdWarning': + 'Tajný klíč klienta se zobrazí pouze jednou. Zkopírujte ho nyní — nelze ho obnovit.', + 'settings.oauth.toast.createError': 'Registrace klienta OAuth se nezdařila', + 'settings.oauth.toast.deleted': 'Klient OAuth smazán', + 'settings.oauth.toast.deleteError': 'Smazání klienta OAuth se nezdařilo', + 'settings.oauth.toast.revoked': 'Relace odvolána', + 'settings.oauth.toast.revokeError': 'Odvolání relace se nezdařilo', + 'settings.oauth.toast.rotateError': + 'Obnovení tajného klíče klienta se nezdařilo', + 'settings.oauth.modal.machineClient': + 'Strojový klient (bez přihlášení v prohlížeči)', + 'settings.oauth.modal.machineClientHint': + 'Používá grant client_credentials — bez URI pro přesměrování. Token je vydán přímo přes client_id + client_secret a funguje jako vy v rámci vybraných oborů.', + 'settings.oauth.modal.machineClientUsage': + 'Získat token: POST /oauth/token s grant_type=client_credentials, client_id a client_secret. Bez prohlížeče, bez obnovovacího tokenu.', + 'settings.oauth.badge.machine': 'strojový', + 'settings.account': 'Účet', + 'settings.about': 'O aplikaci', + 'settings.about.reportBug': 'Nahlásit chybu', + 'settings.about.reportBugHint': 'Našli jste problém? Dejte nám vědět', + 'settings.about.featureRequest': 'Navrhnout funkci', + 'settings.about.featureRequestHint': 'Navrhněte novou funkci', + 'settings.about.wikiHint': 'Dokumentace a návody', + 'settings.about.supporters.badge': 'Měsíční podporovatelé', + 'settings.about.supporters.title': 'Společníci na cestě s TREK', + 'settings.about.supporters.subtitle': + 'Zatímco plánuješ další trasu, tihle lidé plánují společně se mnou budoucnost TREK. Jejich měsíční příspěvek jde přímo na vývoj a reálně strávené hodiny — aby TREK zůstal Open Source.', + 'settings.about.supporters.since': 'podporovatel od {date}', + 'settings.about.supporters.tierEmpty': 'Buď první', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK je samohostovaný plánovač cest, který vám pomůže organizovat výlety od prvního nápadu po poslední vzpomínku. Denní plánování, rozpočet, balicí seznamy, fotky a mnoho dalšího — vše na jednom místě, na vašem vlastním serveru.', + 'settings.about.madeWith': 'Vytvořeno s', + 'settings.about.madeBy': 'Mauricem a rostoucí open-source komunitou.', + 'settings.username': 'Uživatelské jméno', + 'settings.email': 'E-mail', + 'settings.role': 'Role', + 'settings.roleAdmin': 'Administrátor', + 'settings.oidcLinked': 'Propojeno přes', + 'settings.changePassword': 'Změnit heslo', + 'settings.currentPassword': 'Současné heslo', + 'settings.currentPasswordRequired': 'Současné heslo je vyžadováno', + 'settings.newPassword': 'Nové heslo', + 'settings.confirmPassword': 'Potvrdit nové heslo', + 'settings.updatePassword': 'Aktualizovat heslo', + 'settings.passwordRequired': 'Zadejte prosím současné i nové heslo', + 'settings.passwordTooShort': 'Heslo musí mít alespoň 8 znaků', + 'settings.passwordMismatch': 'Hesla se neshodují', + 'settings.passwordWeak': + 'Heslo musí obsahovat velké a malé písmeno, číslici a speciální znak', + 'settings.passwordChanged': 'Heslo bylo úspěšně změněno', + 'settings.deleteAccount': 'Smazat účet', + 'settings.deleteAccountTitle': 'Smazat váš účet?', + 'settings.deleteAccountWarning': + 'Váš účet a všechny vaše cesty, místa a soubory budou trvale smazány. Tuto akci nelze vrátit.', + 'settings.deleteAccountConfirm': 'Smazat natrvalo', + 'settings.deleteBlockedTitle': 'Účet nelze smazat', + 'settings.deleteBlockedMessage': + 'Jste jediným administrátorem. Před smazáním svého účtu předejte roli administrátora jinému uživateli.', + 'settings.roleUser': 'Uživatel', + 'settings.saveProfile': 'Uložit profil', + 'settings.toast.mapSaved': 'Nastavení map uloženo', + 'settings.toast.keysSaved': 'API klíče uloženy', + 'settings.toast.displaySaved': 'Nastavení zobrazení uloženo', + 'settings.toast.profileSaved': 'Profil byl uložen', + 'settings.uploadAvatar': 'Nahrát profilový obrázek', + 'settings.removeAvatar': 'Odebrat profilový obrázek', + 'settings.avatarUploaded': 'Profilový obrázek byl aktualizován', + 'settings.avatarRemoved': 'Profilový obrázek byl odstraněn', + 'settings.avatarError': 'Nahrávání se nezdařilo', + 'settings.mfa.title': 'Dvoufaktorové ověření (2FA)', + 'settings.mfa.description': + 'Přidá druhý stupeň zabezpečení při přihlašování e-mailem a heslem. Použijte aplikaci (Google Authenticator, Authy apod.).', + 'settings.mfa.requiredByPolicy': + 'Správce vyžaduje dvoufázové ověření. Nejdřív níže nastavte aplikaci autentikátoru.', + 'settings.mfa.backupTitle': 'Záložní kódy', + 'settings.mfa.backupDescription': + 'Použijte tyto jednorázové kódy, pokud ztratíte přístup k autentizační aplikaci.', + 'settings.mfa.backupWarning': + 'Uložte si je hned. Každý kód lze použít pouze jednou.', + 'settings.mfa.backupCopy': 'Kopírovat kódy', + 'settings.mfa.backupDownload': 'Stáhnout TXT', + 'settings.mfa.backupPrint': 'Tisk / PDF', + 'settings.mfa.backupCopied': 'Záložní kódy zkopírovány', + 'settings.mfa.enabled': '2FA je pro váš účet aktivní.', + 'settings.mfa.disabled': '2FA není aktivní.', + 'settings.mfa.setup': 'Nastavit autentizační aplikaci', + 'settings.mfa.scanQr': + 'Naskenujte tento QR kód ve vaší aplikaci nebo zadejte kód ručně.', + 'settings.mfa.secretLabel': 'Tajný klíč (pro ruční zadání)', + 'settings.mfa.codePlaceholder': '6místný kód', + 'settings.mfa.enable': 'Zapnout 2FA', + 'settings.mfa.cancelSetup': 'Zrušit', + 'settings.mfa.disableTitle': 'Vypnout 2FA', + 'settings.mfa.disableHint': + 'Zadejte své heslo k účtu a aktuální kód z aplikace.', + 'settings.mfa.disable': 'Vypnout 2FA', + 'settings.mfa.toastEnabled': 'Dvoufaktorové ověření bylo zapnuto', + 'settings.mfa.toastDisabled': 'Dvoufaktorové ověření bylo vypnuto', + 'settings.mfa.demoBlocked': 'Není k dispozici v demo režimu', + 'settings.bookingLabels': 'Popisky tras rezervací', + 'settings.bookingLabelsHint': + 'Zobrazuje názvy stanic / letišť na mapě. Pokud je vypnuto, zobrazí se pouze ikona.', + 'settings.mustChangePassword': 'Před pokračováním musíte změnit heslo.', + 'settings.notifyVersionAvailable': 'Nová verze k dispozici', + 'settings.notificationPreferences.noChannels': + 'Nejsou nakonfigurovány žádné kanály oznámení. Požádejte správce o nastavení e-mailových nebo webhook oznámení.', + 'settings.webhookUrl.label': 'URL webhooku', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Zadejte URL vašeho Discord, Slack nebo vlastního webhooku pro příjem oznámení.', + 'settings.webhookUrl.saved': 'URL webhooku uložena', + 'settings.webhookUrl.test': 'Otestovat', + 'settings.webhookUrl.testSuccess': 'Testovací webhook byl úspěšně odeslán', + 'settings.webhookUrl.testFailed': 'Testovací webhook selhal', + 'settings.ntfyUrl.topicLabel': 'Téma Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL serveru Ntfy (volitelné)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Zadejte své téma Ntfy pro příjem push notifikací. Pole serveru ponechte prázdné pro použití výchozího nastavení správce.', + 'settings.ntfyUrl.tokenLabel': 'Přístupový token (volitelné)', + 'settings.ntfyUrl.tokenHint': 'Vyžadováno pro témata chráněná heslem.', + 'settings.ntfyUrl.saved': 'Nastavení Ntfy uloženo', + 'settings.ntfyUrl.test': 'Otestovat', + 'settings.ntfyUrl.testSuccess': + 'Testovací notifikace Ntfy byla úspěšně odeslána', + 'settings.ntfyUrl.testFailed': 'Testovací notifikace Ntfy selhala', + 'settings.ntfyUrl.tokenCleared': 'Přístupový token byl vymazán', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/cs/share.ts b/shared/src/i18n/cs/share.ts new file mode 100644 index 00000000..c7194e76 --- /dev/null +++ b/shared/src/i18n/cs/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Veřejný odkaz', + 'share.linkHint': + 'Vytvořte odkaz, kterým si může kdokoli prohlédnout tuto cestu bez přihlášení. Pouze pro čtení — úpravy nejsou možné.', + 'share.createLink': 'Vytvořit odkaz', + 'share.deleteLink': 'Smazat odkaz', + 'share.createError': 'Nepodařilo se vytvořit odkaz', + 'share.permMap': 'Mapa a plán', + 'share.permBookings': 'Rezervace', + 'share.permPacking': 'Balení', + 'share.permBudget': 'Rozpočet', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/cs/shared.ts b/shared/src/i18n/cs/shared.ts new file mode 100644 index 00000000..f80c09c5 --- /dev/null +++ b/shared/src/i18n/cs/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Odkaz vypršel nebo je neplatný', + 'shared.expiredHint': 'Tento sdílený odkaz na cestu již není aktivní.', + 'shared.readOnly': 'Sdílené zobrazení – pouze pro čtení', + 'shared.tabPlan': 'Plán', + 'shared.tabBookings': 'Rezervace', + 'shared.tabPacking': 'Balení', + 'shared.tabBudget': 'Rozpočet', + 'shared.tabChat': 'Chat', + 'shared.days': 'dní', + 'shared.places': 'míst', + 'shared.other': 'Ostatní', + 'shared.totalBudget': 'Celkový rozpočet', + 'shared.messages': 'zpráv', + 'shared.sharedVia': 'Sdíleno přes', + 'shared.confirmed': 'Potvrzeno', + 'shared.pending': 'Čeká na potvrzení', +}; +export default shared; diff --git a/shared/src/i18n/cs/stats.ts b/shared/src/i18n/cs/stats.ts new file mode 100644 index 00000000..54e68a43 --- /dev/null +++ b/shared/src/i18n/cs/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Země', + 'stats.cities': 'Města', + 'stats.trips': 'Cesty', + 'stats.places': 'Místa', + 'stats.worldProgress': 'Průzkum světa', + 'stats.visited': 'navštíveno', + 'stats.remaining': 'zbývá', + 'stats.visitedCountries': 'Navštívené země', +}; +export default stats; diff --git a/shared/src/i18n/cs/system_notice.ts b/shared/src/i18n/cs/system_notice.ts new file mode 100644 index 00000000..c80b584a --- /dev/null +++ b/shared/src/i18n/cs/system_notice.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Vítejte v TREK', + 'system_notice.welcome_v1.body': + 'Váš kompletní plánovač cest. Vytvářejte itineráře, sdílejte výlety s přáteli a zůstaňte organizovaní — online i offline.', + 'system_notice.welcome_v1.cta_label': 'Naplánovat cestu', + 'system_notice.welcome_v1.hero_alt': + 'Malebné cestovní místo s rozhraním TREK', + 'system_notice.welcome_v1.highlight_plan': 'Denní itineráře pro každou cestu', + 'system_notice.welcome_v1.highlight_share': + 'Spolupráce s cestovními partnery', + 'system_notice.welcome_v1.highlight_offline': 'Funguje offline na mobilu', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Předchozí oznámení', + 'system_notice.pager.next': 'Další oznámení', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Přejít na oznámení {n}', + 'system_notice.pager.position': 'Oznámení {current} z {total}', + 'system_notice.v3_photos.title': 'Fotografie přesunuty ve verzi 3.0', + 'system_notice.v3_photos.body': + '**Fotografie** v Plánovacím nástroji byly odebrány. Vaše fotografie jsou v bezpečí — TREK nikdy neupravoval vaši knihovnu Immich nebo Synology.\n\nFotografie jsou nyní dostupné v doplňku **Journey**. Journey je volitelný — pokud ještě není k dispozici, požádejte svého správce, aby ho aktivoval v Admin → Doplňky.', + 'system_notice.v3_journey.title': 'Poznejte Journey — cest. denník', + 'system_notice.v3_journey.body': + 'Dokumentujte své cesty jako bohaté příběhy s časovnicemi, galeriemi fotek a interaktivními mapami.', + 'system_notice.v3_journey.cta_label': 'Otevřít Journey', + 'system_notice.v3_journey.highlight_timeline': 'Denní časovnice a galerie', + 'system_notice.v3_journey.highlight_photos': 'Import z Immich nebo Synology', + 'system_notice.v3_journey.highlight_share': + 'Sdílet veřejně — bez přihlašování', + 'system_notice.v3_journey.highlight_export': 'Export jako PDF fotokniha', + 'system_notice.v3_features.title': 'Další novinky ve verzi 3.0', + 'system_notice.v3_features.body': + 'Několik dalších změn, které stojí za pozornost.', + 'system_notice.v3_features.highlight_dashboard': + 'Předesign dashboardu mobile-first', + 'system_notice.v3_features.highlight_offline': 'Plný offline režim jako PWA', + 'system_notice.v3_features.highlight_search': + 'Autodoplňování vyhledávání míst', + 'system_notice.v3_features.highlight_import': + 'Import míst ze souborů KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: aktualizace OAuth 2.1', + 'system_notice.v3_mcp.body': + 'Integrace MCP byla kompletně přepracována. OAuth 2.1 je nyní doporučenou metodou ověřování. Statické tokeny (trek_…) jsou zastaralé a budou v budoucí verzi odstraněny.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 doporučeno (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 jemnozrnných oprávnění', + 'system_notice.v3_mcp.highlight_deprecated': + 'Statické tokeny trek_ zastaralé', + 'system_notice.v3_mcp.highlight_tools': 'Rozšířená sada nástrojů a promptů', + 'system_notice.v3_thankyou.title': 'Osobní slovo ode mě', + 'system_notice.v3_thankyou.body': + 'Než budete pokračovat — chci se na chvíli zastavit.\n\nTREK začal jako vedlejší projekt, který jsem vytvořil pro své vlastní cesty. Nikdy jsem si nepředstavoval, že vyroste v něco, čemu 4 000 z vás důvěřuje při plánování svých dobrodružství. Každou hvězdičku, každý issue, každý požadavek na funkci — všechny čtu a právě ony mě drží při životě během pozdních nocí mezi prací na plný úvazek a univerzitou.\n\nChci, abyste věděli: TREK bude vždy open source, vždy self-hosted, vždy váš. Žádné sledování, žádná předplatná, žádné háčky. Jen nástroj vytvořený někým, kdo miluje cestování stejně jako vy.\n\nZvláštní poděkování patří [jubnl](https://github.com/jubnl) — stal ses neuvěřitelným spolupracovníkem. Tolik z toho, co dělá verzi 3.0 skvělou, nese tvůj rukopis. Děkuji, že jsi věřil tomuto projektu, když byl ještě v plenkách.\n\nA každému z vás, kdo nahlásil chybu, přeložil řetězec, sdílel TREK s přítelem nebo ho jednoduše použil k plánování cesty — **děkuji**. Vy jste důvod, proč tohle existuje.\n\nNa mnoho dalších dobrodružství společně.\n\n— Maurice\n\n---\n\n[Přidej se ke komunitě na Discordu](https://discord.gg/7Q6M6jDwzf)\n\nPokud ti TREK zlepšuje cestování, [malá káva](https://ko-fi.com/mauriceboe) vždy pomůže udržet světla rozsvícená.', + 'system_notice.v3014_whitespace_collision.title': + 'Vyžadována akce: konflikt uživatelského účtu', + 'system_notice.v3014_whitespace_collision.body': + 'Aktualizace 3.0.14 zjistila jeden nebo více konfliktů uživatelského jména nebo e-mailu způsobených mezerami na začátku nebo konci uložených hodnot. Dotčené účty byly automaticky přejmenovány. Zkontrolujte protokoly serveru na řádky začínající **[migration] WHITESPACE COLLISION** a zjistěte, které účty vyžadují kontrolu.', +}; +export default system_notice; diff --git a/shared/src/i18n/cs/todo.ts b/shared/src/i18n/cs/todo.ts new file mode 100644 index 00000000..da07c648 --- /dev/null +++ b/shared/src/i18n/cs/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Balicí seznam', + 'todo.subtab.todo': 'Úkoly', + 'todo.completed': 'dokončeno', + 'todo.filter.all': 'Vše', + 'todo.filter.open': 'Otevřené', + 'todo.filter.done': 'Hotové', + 'todo.uncategorized': 'Bez kategorie', + 'todo.namePlaceholder': 'Název úkolu', + 'todo.descriptionPlaceholder': 'Popis (volitelné)', + 'todo.unassigned': 'Nepřiřazeno', + 'todo.noCategory': 'Bez kategorie', + 'todo.hasDescription': 'Má popis', + 'todo.addItem': 'Přidat nový úkol', + 'todo.sidebar.sortBy': 'Řadit podle', + 'todo.priority': 'Priorita', + 'todo.newCategoryLabel': 'nová', + 'todo.newCategory': 'Název kategorie', + 'todo.addCategory': 'Přidat kategorii', + 'todo.newItem': 'Nový úkol', + 'todo.empty': 'Zatím žádné úkoly. Přidejte úkol a začněte!', + 'todo.filter.my': 'Moje úkoly', + 'todo.filter.overdue': 'Po termínu', + 'todo.sidebar.tasks': 'Úkoly', + 'todo.sidebar.categories': 'Kategorie', + 'todo.detail.title': 'Úkol', + 'todo.detail.description': 'Popis', + 'todo.detail.category': 'Kategorie', + 'todo.detail.dueDate': 'Termín splnění', + 'todo.detail.assignedTo': 'Přiřazeno', + 'todo.detail.delete': 'Smazat', + 'todo.detail.save': 'Uložit změny', + 'todo.detail.create': 'Vytvořit úkol', + 'todo.detail.priority': 'Priorita', + 'todo.detail.noPriority': 'Žádná', + 'todo.sortByPrio': 'Priorita', +}; +export default todo; diff --git a/shared/src/i18n/cs/transport.ts b/shared/src/i18n/cs/transport.ts new file mode 100644 index 00000000..38b69d7a --- /dev/null +++ b/shared/src/i18n/cs/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Přidat dopravu', + 'transport.modalTitle.create': 'Přidat dopravu', + 'transport.modalTitle.edit': 'Upravit dopravu', + 'transport.title': 'Doprava', + 'transport.addManual': 'Ruční doprava', +}; +export default transport; diff --git a/shared/src/i18n/cs/trip.ts b/shared/src/i18n/cs/trip.ts new file mode 100644 index 00000000..24307285 --- /dev/null +++ b/shared/src/i18n/cs/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plán', + 'trip.tabs.transports': 'Doprava', + 'trip.tabs.reservations': 'Rezervace', + 'trip.tabs.reservationsShort': 'Rez.', + 'trip.tabs.packing': 'Seznam věcí', + 'trip.tabs.packingShort': 'Balení', + 'trip.tabs.lists': 'Seznamy', + 'trip.tabs.listsShort': 'Seznamy', + 'trip.tabs.budget': 'Rozpočet', + 'trip.tabs.files': 'Soubory', + 'trip.loading': 'Načítání cesty...', + 'trip.loadingPhotos': 'Načítání fotek míst...', + 'trip.mobilePlan': 'Plán', + 'trip.mobilePlaces': 'Místa', + 'trip.toast.placeUpdated': 'Místo bylo aktualizováno', + 'trip.toast.placeAdded': 'Místo bylo přidáno', + 'trip.toast.placeDeleted': 'Místo bylo smazáno', + 'trip.toast.selectDay': 'Prosím nejdříve vyberte den', + 'trip.toast.assignedToDay': 'Místo bylo přiřazeno ke dni', + 'trip.toast.reorderError': 'Nepodařilo se změnit pořadí', + 'trip.toast.reservationUpdated': 'Rezervace aktualizována', + 'trip.toast.reservationAdded': 'Rezervace přidána', + 'trip.toast.deleted': 'Smazáno', + 'trip.confirm.deletePlace': 'Opravdu chcete toto místo smazat?', + 'trip.confirm.deletePlaces': 'Smazat {count} míst?', + 'trip.toast.placesDeleted': '{count} míst smazáno', +}; +export default trip; diff --git a/shared/src/i18n/cs/trips.ts b/shared/src/i18n/cs/trips.ts new file mode 100644 index 00000000..4410757d --- /dev/null +++ b/shared/src/i18n/cs/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} odebrán', + 'trips.memberRemoveError': 'Odebrání se nezdařilo', + 'trips.memberAdded': '{username} přidán', + 'trips.memberAddError': 'Přidání se nezdařilo', + 'trips.reminder': 'Připomínka', + 'trips.reminderNone': 'Žádná', + 'trips.reminderDay': 'den', + 'trips.reminderDays': 'dní', + 'trips.reminderCustom': 'Vlastní', + 'trips.reminderDaysBefore': 'dní před odjezdem', + 'trips.reminderDisabledHint': + 'Připomínky výletů jsou zakázány. Povolte je v Správa > Nastavení > Oznámení.', +}; +export default trips; diff --git a/shared/src/i18n/cs/undo.ts b/shared/src/i18n/cs/undo.ts new file mode 100644 index 00000000..5aba0b31 --- /dev/null +++ b/shared/src/i18n/cs/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Zpět', + 'undo.tooltip': 'Zpět: {action}', + 'undo.assignPlace': 'Místo přiřazeno ke dni', + 'undo.removeAssignment': 'Místo odebráno ze dne', + 'undo.reorder': 'Místa přeseřazena', + 'undo.optimize': 'Trasa optimalizována', + 'undo.deletePlace': 'Místo smazáno', + 'undo.deletePlaces': 'Místa smazána', + 'undo.moveDay': 'Místo přesunuto na jiný den', + 'undo.lock': 'Zámek místa přepnut', + 'undo.importGpx': 'Import GPX', + 'undo.importKeyholeMarkup': 'Import KMZ/KML', + 'undo.importGoogleList': 'Import z Google Maps', + 'undo.importNaverList': 'Import z Naver Maps', + 'undo.addPlace': 'Místo přidáno', + 'undo.done': 'Vráceno zpět: {action}', +}; +export default undo; diff --git a/shared/src/i18n/cs/vacay.ts b/shared/src/i18n/cs/vacay.ts new file mode 100644 index 00000000..2041d0d1 --- /dev/null +++ b/shared/src/i18n/cs/vacay.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Plánování a správa dovolené', + 'vacay.settings': 'Nastavení', + 'vacay.year': 'Rok', + 'vacay.addYear': 'Přidat následující rok', + 'vacay.addPrevYear': 'Přidat předchozí rok', + 'vacay.removeYear': 'Odebrat rok', + 'vacay.removeYearConfirm': 'Odebrat rok {year}?', + 'vacay.removeYearHint': + 'Všechny záznamy o dovolené a firemní svátky pro tento rok budou trvale smazány.', + 'vacay.remove': 'Odebrat', + 'vacay.persons': 'Osoby', + 'vacay.noPersons': 'Žádné osoby nebyly přidány', + 'vacay.addPerson': 'Přidat osobu', + 'vacay.editPerson': 'Upravit osobu', + 'vacay.removePerson': 'Odebrat osobu', + 'vacay.removePersonConfirm': 'Odebrat osobu {name}?', + 'vacay.removePersonHint': + 'Všechny záznamy dovolené pro tuto osobu budou trvale smazány.', + 'vacay.personName': 'Jméno', + 'vacay.personNamePlaceholder': 'Zadejte jméno', + 'vacay.color': 'Barva', + 'vacay.add': 'Přidat', + 'vacay.legend': 'Legenda', + 'vacay.publicHoliday': 'Státní svátek', + 'vacay.companyHoliday': 'Firemní volno', + 'vacay.weekend': 'Víkend', + 'vacay.modeVacation': 'Dovolená', + 'vacay.modeCompany': 'Firemní volno', + 'vacay.entitlement': 'Nárok', + 'vacay.entitlementDays': 'Dní', + 'vacay.used': 'Vyčerpáno', + 'vacay.remaining': 'Zbývá', + 'vacay.carriedOver': 'z roku {year}', + 'vacay.blockWeekends': 'Blokovat víkendy', + 'vacay.blockWeekendsHint': 'Zamezit zadávání dovolené na víkendové dny', + 'vacay.mon': 'Po', + 'vacay.tue': 'Út', + 'vacay.wed': 'St', + 'vacay.thu': 'Čt', + 'vacay.fri': 'Pá', + 'vacay.sat': 'So', + 'vacay.sun': 'Ne', + 'vacay.weekendDays': 'Víkendové dny', + 'vacay.publicHolidays': 'Státní svátky', + 'vacay.publicHolidaysHint': 'Zobrazit státní svátky v kalendáři', + 'vacay.selectCountry': 'Vyberte zemi', + 'vacay.selectRegion': 'Vyberte region (volitelné)', + 'vacay.addCalendar': 'Přidat kalendář', + 'vacay.calendarLabel': 'Popisek (volitelné)', + 'vacay.calendarColor': 'Barva', + 'vacay.noCalendars': 'Zatím nebyly přidány žádné svátkové kalendáře', + 'vacay.companyHolidays': 'Firemní volno', + 'vacay.companyHolidaysHint': 'Povolit označování dnů celofiremního volna', + 'vacay.companyHolidaysNoDeduct': + 'Firemní volno se nezapočítává do nároku na dovolenou.', + 'vacay.weekStart': 'Týden začíná', + 'vacay.weekStartHint': 'Zvolte, zda týden začíná v pondělí nebo v neděli', + 'vacay.carryOver': 'Převod dovolené', + 'vacay.carryOverHint': 'Automaticky převádět zbývající dny do dalšího roku', + 'vacay.sharing': 'Sdílení', + 'vacay.sharingHint': 'Sdílejte svůj plán dovolené s ostatními uživateli TREK', + 'vacay.owner': 'Vlastník', + 'vacay.shareEmailPlaceholder': 'E-mail uživatele TREK', + 'vacay.shareSuccess': 'Plán byl úspěšně sdílen', + 'vacay.shareError': 'Nepodařilo se sdílet plán', + 'vacay.dissolve': 'Zrušit propojení', + 'vacay.dissolveHint': + 'Znovu oddělit kalendáře. Vaše záznamy zůstanou zachovány.', + 'vacay.dissolveAction': 'Oddělit', + 'vacay.dissolved': 'Kalendáře byly odděleny', + 'vacay.fusedWith': 'Propojeno s', + 'vacay.you': 'vy', + 'vacay.noData': 'Žádná data', + 'vacay.changeColor': 'Změnit barvu', + 'vacay.inviteUser': 'Pozvat uživatele', + 'vacay.inviteHint': + 'Pozvěte jiného uživatele TREK ke sdílení společného kalendáře dovolených.', + 'vacay.selectUser': 'Vyberte uživatele', + 'vacay.sendInvite': 'Odeslat pozvánku', + 'vacay.inviteSent': 'Pozvánka odeslána', + 'vacay.inviteError': 'Nepodařilo se odeslat pozvánku', + 'vacay.pending': 'čeká na vyřízení', + 'vacay.noUsersAvailable': 'Žádní uživatelé nejsou k dispozici', + 'vacay.accept': 'Přijmout', + 'vacay.decline': 'Odmítnout', + 'vacay.acceptFusion': 'Přijmout a propojit', + 'vacay.inviteTitle': 'Žádost o propojení', + 'vacay.inviteWantsToFuse': 'vás zve ke sdílení kalendáře dovolených.', + 'vacay.fuseInfo1': 'Oba uvidíte všechny záznamy v jednom sdíleném kalendáři.', + 'vacay.fuseInfo2': + 'Obě strany mohou vytvářet a upravovat záznamy tomu druhému.', + 'vacay.fuseInfo3': 'Obě strany mohou měnit nároky na dovolenou.', + 'vacay.fuseInfo4': 'Nastavení (svátky, firemní volno) jsou sdílená.', + 'vacay.fuseInfo5': 'Propojení lze kdykoli zrušit bez ztráty dat.', +}; +export default vacay; diff --git a/shared/src/i18n/de/admin.ts b/shared/src/i18n/de/admin.ts new file mode 100644 index 00000000..f56921aa --- /dev/null +++ b/shared/src/i18n/de/admin.ts @@ -0,0 +1,365 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Benachrichtigungen', + 'admin.notifications.hint': + 'Wählen Sie einen Benachrichtigungskanal. Es kann nur einer gleichzeitig aktiv sein.', + 'admin.notifications.none': 'Deaktiviert', + 'admin.notifications.email': 'E-Mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Benachrichtigungseinstellungen speichern', + 'admin.notifications.saved': 'Benachrichtigungseinstellungen gespeichert', + 'admin.notifications.testWebhook': 'Test-Webhook senden', + 'admin.notifications.testWebhookSuccess': 'Test-Webhook erfolgreich gesendet', + 'admin.notifications.testWebhookFailed': 'Test-Webhook fehlgeschlagen', + 'admin.smtp.title': 'E-Mail & Benachrichtigungen', + 'admin.smtp.hint': + 'SMTP-Konfiguration zum Versenden von E-Mail-Benachrichtigungen.', + 'admin.smtp.testButton': 'Test-E-Mail senden', + 'admin.webhook.hint': + 'Benachrichtigungen an einen externen Webhook senden (Discord, Slack usw.).', + 'admin.smtp.testSuccess': 'Test-E-Mail erfolgreich gesendet', + 'admin.smtp.testFailed': 'Test-E-Mail fehlgeschlagen', + 'admin.title': 'Administration', + 'admin.subtitle': 'Benutzerverwaltung und Systemeinstellungen', + 'admin.tabs.users': 'Benutzer', + 'admin.tabs.categories': 'Kategorien', + 'admin.tabs.backup': 'Backup', + 'admin.tabs.audit': 'Audit', + 'admin.stats.users': 'Benutzer', + 'admin.stats.trips': 'Reisen', + 'admin.stats.places': 'Orte', + 'admin.stats.photos': 'Fotos', + 'admin.stats.files': 'Dateien', + 'admin.table.user': 'Benutzer', + 'admin.table.email': 'E-Mail', + 'admin.table.role': 'Rolle', + 'admin.table.created': 'Erstellt', + 'admin.table.lastLogin': 'Letzter Login', + 'admin.table.actions': 'Aktionen', + 'admin.you': '(Du)', + 'admin.editUser': 'Benutzer bearbeiten', + 'admin.newPassword': 'Neues Passwort', + 'admin.newPasswordHint': 'Leer lassen, um das Passwort nicht zu ändern', + 'admin.deleteUser': + 'Benutzer "{name}" löschen? Alle Reisen werden unwiderruflich gelöscht.', + 'admin.deleteUserTitle': 'Benutzer löschen', + 'admin.newPasswordPlaceholder': 'Neues Passwort eingeben…', + 'admin.toast.loadError': 'Fehler beim Laden der Admin-Daten', + 'admin.toast.userUpdated': 'Benutzer aktualisiert', + 'admin.toast.updateError': 'Fehler beim Aktualisieren', + 'admin.toast.userDeleted': 'Benutzer gelöscht', + 'admin.toast.deleteError': 'Fehler beim Löschen', + 'admin.toast.cannotDeleteSelf': 'Eigenes Konto kann nicht gelöscht werden', + 'admin.toast.userCreated': 'Benutzer erstellt', + 'admin.toast.createError': 'Fehler beim Erstellen des Benutzers', + 'admin.toast.fieldsRequired': + 'Benutzername, E-Mail und Passwort sind erforderlich', + 'admin.createUser': 'Benutzer anlegen', + 'admin.invite.title': 'Einladungslinks', + 'admin.invite.subtitle': 'Einmal-Links für die Registrierung erstellen', + 'admin.invite.create': 'Link erstellen', + 'admin.invite.createAndCopy': 'Erstellen & kopieren', + 'admin.invite.empty': 'Noch keine Einladungslinks erstellt', + 'admin.invite.maxUses': 'Max. Nutzungen', + 'admin.invite.expiry': 'Gültig für', + 'admin.invite.uses': 'genutzt', + 'admin.invite.expiresAt': 'läuft ab am', + 'admin.invite.createdBy': 'von', + 'admin.invite.active': 'Aktiv', + 'admin.invite.expired': 'Abgelaufen', + 'admin.invite.usedUp': 'Aufgebraucht', + 'admin.invite.copied': 'Einladungslink in Zwischenablage kopiert', + 'admin.invite.copyLink': 'Link kopieren', + 'admin.invite.deleted': 'Einladungslink gelöscht', + 'admin.invite.createError': 'Fehler beim Erstellen des Einladungslinks', + 'admin.invite.deleteError': 'Fehler beim Löschen des Einladungslinks', + 'admin.tabs.settings': 'Einstellungen', + 'admin.allowRegistration': 'Registrierung erlauben', + 'admin.allowRegistrationHint': + 'Neue Benutzer können sich selbst registrieren', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Zwei-Faktor-Authentifizierung (2FA) für alle verlangen', + 'admin.requireMfaHint': + 'Benutzer ohne 2FA müssen die Einrichtung unter Einstellungen abschließen, bevor sie die App nutzen können.', + 'admin.apiKeys': 'API-Schlüssel', + 'admin.apiKeysHint': + 'Optional. Aktiviert erweiterte Ortsdaten wie Fotos und Wetter.', + 'admin.mapsKey': 'Google Maps API-Schlüssel', + 'admin.mapsKeyHint': + 'Für Ortsuche benötigt. Erstellen unter console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Ohne API Key wird OpenStreetMap für die Ortssuche genutzt. Mit Google API Key können zusätzlich Bilder, Bewertungen und Öffnungszeiten geladen werden. Erstellen unter console.cloud.google.com.', + 'admin.recommended': 'Empfohlen', + 'admin.weatherKey': 'OpenWeatherMap API-Schlüssel', + 'admin.weatherKeyHint': 'Für Wetterdaten. Kostenlos unter openweathermap.org', + 'admin.validateKey': 'Test', + 'admin.keyValid': 'Verbunden', + 'admin.keyInvalid': 'Ungültig', + 'admin.keySaved': 'API-Schlüssel gespeichert', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Anmeldung über externe Anbieter wie Google, Apple, Authentik oder Keycloak.', + 'admin.oidcDisplayName': 'Anzeigename', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'Die OpenID Connect Issuer URL des Anbieters. z.B. https://accounts.google.com', + 'admin.oidcSaved': 'OIDC-Konfiguration gespeichert', + 'admin.oidcOnlyMode': 'Passwort-Authentifizierung deaktivieren', + 'admin.oidcOnlyModeHint': + 'Wenn aktiviert, ist nur SSO-Login erlaubt. Passwort-Login und Registrierung werden blockiert.', + 'admin.fileTypes': 'Erlaubte Dateitypen', + 'admin.fileTypesHint': + 'Konfiguriere welche Dateitypen hochgeladen werden dürfen.', + 'admin.fileTypesFormat': + 'Kommagetrennte Endungen (z.B. jpg,png,pdf,doc). Verwende * um alle Typen zu erlauben.', + 'admin.fileTypesSaved': 'Dateityp-Einstellungen gespeichert', + 'admin.placesPhotos.title': 'Ortsfotos', + 'admin.placesPhotos.subtitle': + 'Fotos von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen. Wikimedia-Fotos sind davon nicht betroffen.', + 'admin.placesAutocomplete.title': 'Orts-Autovervollständigung', + 'admin.placesAutocomplete.subtitle': + 'Google Places API für Suchvorschläge nutzen. Deaktivieren, um API-Kontingent zu sparen.', + 'admin.placesDetails.title': 'Ortsdetails', + 'admin.placesDetails.subtitle': + 'Detaillierte Ortsinformationen (Öffnungszeiten, Bewertung, Website) von der Google Places API laden. Deaktivieren, um API-Kontingent zu sparen.', + 'admin.bagTracking.title': 'Gepäck-Tracking', + 'admin.bagTracking.subtitle': + 'Gewicht und Gepäckstück-Zuordnung für Packlisteneinträge aktivieren', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Echtzeit-Nachrichten für die Reiseplanung', + 'admin.collab.notes.title': 'Notizen', + 'admin.collab.notes.subtitle': 'Gemeinsame Notizen und Dokumente', + 'admin.collab.polls.title': 'Umfragen', + 'admin.collab.polls.subtitle': 'Gruppen-Umfragen und Abstimmungen', + 'admin.collab.whatsnext.title': 'Was kommt als Nächstes', + 'admin.collab.whatsnext.subtitle': + 'Aktivitätsvorschläge und nächste Schritte', + 'admin.tabs.config': 'Personalisierung', + 'admin.tabs.defaults': 'Benutzer-Standards', + 'admin.defaultSettings.title': 'Standard-Benutzereinstellungen', + 'admin.defaultSettings.description': + 'Instanzweite Standards festlegen. Benutzer, die eine Einstellung nicht geändert haben, sehen diese Werte. Eigene Änderungen haben immer Vorrang.', + 'admin.defaultSettings.saved': 'Standard gespeichert', + 'admin.defaultSettings.reset': 'Auf eingebauten Standard zurücksetzen', + 'admin.defaultSettings.resetToBuiltIn': 'zurücksetzen', + 'admin.tabs.templates': 'Packvorlagen', + 'admin.packingTemplates.title': 'Packvorlagen', + 'admin.packingTemplates.subtitle': + 'Wiederverwendbare Packlisten für deine Reisen erstellen', + 'admin.packingTemplates.create': 'Neue Vorlage', + 'admin.packingTemplates.namePlaceholder': 'Vorlagenname (z.B. Strandurlaub)', + 'admin.packingTemplates.empty': 'Noch keine Vorlagen erstellt', + 'admin.packingTemplates.items': 'Einträge', + 'admin.packingTemplates.categories': 'Kategorien', + 'admin.packingTemplates.itemName': 'Artikelname', + 'admin.packingTemplates.itemCategory': 'Kategorie', + 'admin.packingTemplates.categoryName': 'Kategoriename (z.B. Kleidung)', + 'admin.packingTemplates.addCategory': 'Kategorie hinzufügen', + 'admin.packingTemplates.created': 'Vorlage erstellt', + 'admin.packingTemplates.deleted': 'Vorlage gelöscht', + 'admin.packingTemplates.loadError': 'Vorlagen konnten nicht geladen werden', + 'admin.packingTemplates.createError': 'Vorlage konnte nicht erstellt werden', + 'admin.packingTemplates.deleteError': 'Vorlage konnte nicht gelöscht werden', + 'admin.packingTemplates.saveError': 'Fehler beim Speichern', + 'admin.tabs.addons': 'Addons', + 'admin.addons.title': 'Addons', + 'admin.addons.subtitle': + 'Aktiviere oder deaktiviere Funktionen, um TREK nach deinen Wünschen anzupassen.', + 'admin.addons.catalog.packing.name': 'Listen', + 'admin.addons.catalog.packing.description': + 'Packlisten und To-Do-Aufgaben für deine Reisen', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Ausgaben verfolgen und Reisebudget planen', + 'admin.addons.catalog.documents.name': 'Dokumente', + 'admin.addons.catalog.documents.description': + 'Reisedokumente speichern und verwalten', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Persönlicher Urlaubsplaner mit Kalenderansicht', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Weltkarte mit besuchten Ländern und Reisestatistiken', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Echtzeit-Notizen, Umfragen und Chat für die Reiseplanung', + 'admin.addons.catalog.memories.name': 'Fotos (Immich)', + 'admin.addons.catalog.memories.description': + 'Reisefotos über deine Immich-Instanz teilen', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol für die KI-Assistenten-Integration', + 'admin.addons.subtitleBefore': 'Aktiviere oder deaktiviere Funktionen, um ', + 'admin.addons.subtitleAfter': ' nach deinen Wünschen anzupassen.', + 'admin.addons.enabled': 'Aktiviert', + 'admin.addons.disabled': 'Deaktiviert', + 'admin.addons.type.trip': 'Reise', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integration', + 'admin.addons.tripHint': 'Verfügbar als Tab innerhalb jedes Trips', + 'admin.addons.globalHint': + 'Verfügbar als eigenständiger Bereich in der Navigation', + 'admin.addons.integrationHint': + 'Backend-Dienste und API-Integrationen ohne eigene Seite', + 'admin.addons.toast.updated': 'Addon aktualisiert', + 'admin.addons.toast.error': 'Addon konnte nicht aktualisiert werden', + 'admin.addons.noAddons': 'Keine Addons verfügbar', + 'admin.weather.title': 'Wetterdaten', + 'admin.weather.badge': 'Seit 24. März 2026', + 'admin.weather.description': + 'TREK nutzt Open-Meteo als Wetterdatenquelle. Open-Meteo ist ein kostenloser, quelloffener Wetterdienst — es wird kein API-Schlüssel benötigt.', + 'admin.weather.forecast': '16-Tage-Vorhersage', + 'admin.weather.forecastDesc': 'Statt bisher 5 Tage (OpenWeatherMap)', + 'admin.weather.climate': 'Historische Klimadaten', + 'admin.weather.climateDesc': + 'Durchschnittswerte der letzten 85 Jahre für Tage jenseits der 16-Tage-Vorhersage', + 'admin.weather.requests': '10.000 Anfragen / Tag', + 'admin.weather.requestsDesc': 'Kostenlos, kein API-Schlüssel erforderlich', + 'admin.weather.locationHint': + 'Das Wetter wird anhand des ersten Ortes mit Koordinaten im jeweiligen Tag berechnet. Ist kein Ort am Tag eingeplant, wird ein beliebiger Ort aus der Ortsliste als Referenz verwendet.', + 'admin.tabs.mcpTokens': 'MCP-Zugang', + 'admin.mcpTokens.title': 'MCP-Zugang', + 'admin.mcpTokens.subtitle': + 'OAuth-Sitzungen und API-Tokens aller Benutzer verwalten', + 'admin.mcpTokens.sectionTitle': 'API-Tokens', + 'admin.mcpTokens.owner': 'Besitzer', + 'admin.mcpTokens.tokenName': 'Token-Name', + 'admin.mcpTokens.created': 'Erstellt', + 'admin.mcpTokens.lastUsed': 'Zuletzt verwendet', + 'admin.mcpTokens.never': 'Nie', + 'admin.mcpTokens.empty': 'Es wurden noch keine MCP-Tokens erstellt', + 'admin.mcpTokens.deleteTitle': 'Token löschen', + 'admin.mcpTokens.deleteMessage': + 'Dieser Token wird sofort widerrufen. Der Benutzer verliert den MCP-Zugang über diesen Token.', + 'admin.mcpTokens.deleteSuccess': 'Token gelöscht', + 'admin.mcpTokens.deleteError': 'Token konnte nicht gelöscht werden', + 'admin.mcpTokens.loadError': 'Tokens konnten nicht geladen werden', + 'admin.oauthSessions.sectionTitle': 'OAuth-Sitzungen', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Besitzer', + 'admin.oauthSessions.scopes': 'Berechtigungen', + 'admin.oauthSessions.created': 'Erstellt', + 'admin.oauthSessions.empty': 'Keine aktiven OAuth-Sitzungen', + 'admin.oauthSessions.revokeTitle': 'Sitzung widerrufen', + 'admin.oauthSessions.revokeMessage': + 'Diese OAuth-Sitzung wird sofort widerrufen. Der Client verliert den MCP-Zugang.', + 'admin.oauthSessions.revokeSuccess': 'Sitzung widerrufen', + 'admin.oauthSessions.revokeError': 'Sitzung konnte nicht widerrufen werden', + 'admin.oauthSessions.loadError': + 'OAuth-Sitzungen konnten nicht geladen werden', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Sicherheitsrelevante und administrative Ereignisse (Backups, Benutzer, MFA, Einstellungen).', + 'admin.audit.empty': 'Noch keine Audit-Einträge.', + 'admin.audit.refresh': 'Aktualisieren', + 'admin.audit.loadMore': 'Mehr laden', + 'admin.audit.showing': '{count} geladen · {total} gesamt', + 'admin.audit.col.time': 'Zeit', + 'admin.audit.col.user': 'Benutzer', + 'admin.audit.col.action': 'Aktion', + 'admin.audit.col.resource': 'Ressource', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Details', + 'admin.github.title': 'Update-Verlauf', + 'admin.github.subtitle': 'Neueste Updates von {repo}', + 'admin.github.latest': 'Aktuell', + 'admin.github.prerelease': 'Vorabversion', + 'admin.github.showDetails': 'Details anzeigen', + 'admin.github.hideDetails': 'Details ausblenden', + 'admin.github.loadMore': 'Mehr laden', + 'admin.github.loading': 'Wird geladen...', + 'admin.github.error': 'Releases konnten nicht geladen werden', + 'admin.github.by': 'von', + 'admin.github.support': 'Hilft mir, TREK weiterzuentwickeln', + 'admin.update.available': 'Update verfügbar', + 'admin.update.text': 'TREK {version} ist verfügbar. Du verwendest {current}.', + 'admin.update.button': 'Auf GitHub ansehen', + 'admin.update.install': 'Update installieren', + 'admin.update.confirmTitle': 'Update installieren?', + 'admin.update.confirmText': + 'TREK wird von {current} auf {version} aktualisiert. Der Server startet danach automatisch neu.', + 'admin.update.dataInfo': + 'Alle Daten (Reisen, Benutzer, API-Schlüssel, Uploads, Vacay, Atlas, Budgets) bleiben erhalten.', + 'admin.update.warning': + 'Die App ist während des Neustarts kurz nicht erreichbar.', + 'admin.update.confirm': 'Jetzt aktualisieren', + 'admin.update.installing': 'Wird aktualisiert…', + 'admin.update.success': 'Update installiert! Server startet neu…', + 'admin.update.failed': 'Update fehlgeschlagen', + 'admin.update.backupHint': + 'Wir empfehlen, vor dem Update ein Backup zu erstellen und herunterzuladen.', + 'admin.update.backupLink': 'Zum Backup', + 'admin.update.howTo': 'Update-Anleitung', + 'admin.update.dockerText': + 'Deine TREK-Instanz läuft in Docker. Um auf {version} zu aktualisieren, führe folgende Befehle auf deinem Server aus:', + 'admin.update.reloadHint': 'Bitte lade die Seite in wenigen Sekunden neu.', + 'admin.tabs.permissions': 'Berechtigungen', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'In-App-Benachrichtigungen sind immer aktiv und können nicht global deaktiviert werden.', + 'admin.notifications.adminWebhookPanel.title': 'Admin-Webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'Dieser Webhook wird ausschließlich für Admin-Benachrichtigungen verwendet (z. B. Versions-Updates). Er ist unabhängig von den Benutzer-Webhooks und sendet automatisch, wenn eine URL konfiguriert ist.', + 'admin.notifications.adminWebhookPanel.saved': + 'Admin-Webhook-URL gespeichert', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Test-Webhook erfolgreich gesendet', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Test-Webhook fehlgeschlagen', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin-Webhook sendet automatisch, wenn eine URL konfiguriert ist', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Erlaubt Benutzern, eigene ntfy-Themen für Push-Benachrichtigungen zu konfigurieren. Legen Sie unten den Standardserver fest, um die Benutzereinstellungen vorauszufüllen.', + 'admin.notifications.testNtfy': 'Test-Ntfy senden', + 'admin.notifications.testNtfySuccess': 'Test-Ntfy erfolgreich gesendet', + 'admin.notifications.testNtfyFailed': 'Test-Ntfy fehlgeschlagen', + 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Dieses Ntfy-Thema wird ausschließlich für Admin-Benachrichtigungen verwendet (z. B. Versions-Updates). Es ist unabhängig von Benutzer-Themen und sendet immer, wenn es konfiguriert ist.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-Server-URL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Wird auch als Standardserver für Benutzer-ntfy-Benachrichtigungen verwendet. Leer lassen für ntfy.sh. Benutzer können dies in ihren eigenen Einstellungen überschreiben.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-Thema', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Zugriffstoken (optional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Admin-Zugriffstoken gelöscht', + 'admin.notifications.adminNtfyPanel.saved': + 'Admin-Ntfy-Einstellungen gespeichert', + 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy senden', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Test-Ntfy erfolgreich gesendet', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy fehlgeschlagen', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin-Ntfy sendet immer, wenn ein Thema konfiguriert ist', + 'admin.notifications.adminNotificationsHint': + 'Konfiguriere, welche Kanäle Admin-Benachrichtigungen liefern (z. B. Versions-Updates). Der Webhook sendet automatisch, wenn eine Admin-Webhook-URL gesetzt ist.', + 'admin.notifications.tripReminders.title': 'Reiseerinnerungen', + 'admin.notifications.tripReminders.hint': + 'Sendet eine Erinnerungsbenachrichtigung vor Reisebeginn (erfordert gesetzte Erinnerungstage bei der Reise).', + 'admin.notifications.tripReminders.enabled': 'Reiseerinnerungen aktiviert', + 'admin.notifications.tripReminders.disabled': 'Reiseerinnerungen deaktiviert', + 'admin.tabs.notifications': 'Benachrichtigungen', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + 'Reise-Tracking & Tagebuch mit Check-ins, Fotos und Tagesberichten', +}; +export default admin; diff --git a/shared/src/i18n/de/airport.ts b/shared/src/i18n/de/airport.ts new file mode 100644 index 00000000..98007036 --- /dev/null +++ b/shared/src/i18n/de/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Flughafencode oder Stadt (z. B. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/de/atlas.ts b/shared/src/i18n/de/atlas.ts new file mode 100644 index 00000000..5dc2526e --- /dev/null +++ b/shared/src/i18n/de/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Dein Reise-Fußabdruck auf der Welt', + 'atlas.countries': 'Länder', + 'atlas.trips': 'Reisen', + 'atlas.places': 'Orte', + 'atlas.unmark': 'Entfernen', + 'atlas.confirmMark': 'Dieses Land als besucht markieren?', + 'atlas.confirmUnmark': 'Dieses Land von der Liste entfernen?', + 'atlas.confirmUnmarkRegion': 'Diese Region von der Liste entfernen?', + 'atlas.markVisited': 'Als besucht markieren', + 'atlas.markVisitedHint': 'Dieses Land zur besuchten Liste hinzufügen', + 'atlas.markRegionVisitedHint': 'Diese Region zur besuchten Liste hinzufügen', + 'atlas.addToBucket': 'Zur Bucket List', + 'atlas.addPoi': 'Ort hinzufügen', + 'atlas.searchCountry': 'Land suchen...', + 'atlas.bucketNamePlaceholder': 'Name (Land, Stadt, Ort...)', + 'atlas.month': 'Monat', + 'atlas.year': 'Jahr', + 'atlas.addToBucketHint': 'Als Wunschziel speichern', + 'atlas.bucketWhen': 'Wann möchtest du dorthin reisen?', + 'atlas.statsTab': 'Statistik', + 'atlas.bucketTab': 'Wunschliste', + 'atlas.addBucket': 'Zur Bucket List hinzufügen', + 'atlas.bucketNotesPlaceholder': 'Notizen (optional)', + 'atlas.bucketEmpty': 'Deine Bucket List ist leer', + 'atlas.bucketEmptyHint': 'Füge Orte hinzu, die du besuchen möchtest', + 'atlas.days': 'Tage', + 'atlas.visitedCountries': 'Besuchte Länder', + 'atlas.cities': 'Städte', + 'atlas.noData': 'Noch keine Reisedaten', + 'atlas.noDataHint': 'Erstelle einen Trip und füge Orte hinzu', + 'atlas.lastTrip': 'Letzter Trip', + 'atlas.nextTrip': 'Nächster Trip', + 'atlas.daysLeft': 'Tage', + 'atlas.streak': 'Serie', + 'atlas.years': 'Jahre', + 'atlas.yearInRow': 'Jahr in Folge', + 'atlas.yearsInRow': 'Jahre in Folge', + 'atlas.tripIn': 'Reise in', + 'atlas.tripsIn': 'Reisen in', + 'atlas.since': 'seit', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Asien', + 'atlas.northAmerica': 'N-Amerika', + 'atlas.southAmerica': 'S-Amerika', + 'atlas.africa': 'Afrika', + 'atlas.oceania': 'Ozeanien', + 'atlas.other': 'Andere', + 'atlas.firstVisit': 'Erste Reise', + 'atlas.lastVisitLabel': 'Letzte Reise', + 'atlas.tripSingular': 'Reise', + 'atlas.tripPlural': 'Reisen', + 'atlas.placeVisited': 'Ort besucht', + 'atlas.placesVisited': 'Orte besucht', +}; +export default atlas; diff --git a/shared/src/i18n/de/backup.ts b/shared/src/i18n/de/backup.ts new file mode 100644 index 00000000..3f30ab2f --- /dev/null +++ b/shared/src/i18n/de/backup.ts @@ -0,0 +1,78 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Datensicherung', + 'backup.subtitle': 'Datenbank und alle hochgeladenen Dateien', + 'backup.refresh': 'Aktualisieren', + 'backup.upload': 'Backup hochladen', + 'backup.uploading': 'Wird hochgeladen…', + 'backup.create': 'Backup erstellen', + 'backup.creating': 'Erstelle…', + 'backup.empty': 'Noch keine Backups vorhanden', + 'backup.createFirst': 'Erstes Backup erstellen', + 'backup.download': 'Herunterladen', + 'backup.restore': 'Wiederherstellen', + 'backup.confirm.restore': + 'Backup "{name}" wiederherstellen?\n\nAlle aktuellen Daten werden durch den Backup-Stand ersetzt.', + 'backup.confirm.uploadRestore': + 'Backup-Datei "{name}" hochladen und wiederherstellen?\n\nAlle aktuellen Daten werden überschrieben.', + 'backup.confirm.delete': 'Backup "{name}" löschen?', + 'backup.toast.loadError': 'Fehler beim Laden der Backups', + 'backup.toast.created': 'Backup erfolgreich erstellt', + 'backup.toast.createError': 'Fehler beim Erstellen des Backups', + 'backup.toast.restored': 'Backup wiederhergestellt. Seite wird neu geladen…', + 'backup.toast.restoreError': 'Fehler beim Wiederherstellen', + 'backup.toast.uploadError': 'Fehler beim Hochladen', + 'backup.toast.deleted': 'Backup gelöscht', + 'backup.toast.deleteError': 'Fehler beim Löschen', + 'backup.toast.downloadError': 'Download fehlgeschlagen', + 'backup.toast.settingsSaved': 'Auto-Backup Einstellungen gespeichert', + 'backup.toast.settingsError': 'Fehler beim Speichern der Einstellungen', + 'backup.auto.title': 'Auto-Backup', + 'backup.auto.subtitle': 'Automatische Sicherung nach Zeitplan', + 'backup.auto.enable': 'Auto-Backup aktivieren', + 'backup.auto.enableHint': + 'Backups werden automatisch nach dem gewählten Zeitplan erstellt', + 'backup.auto.interval': 'Intervall', + 'backup.auto.hour': 'Ausführung um', + 'backup.auto.hourHint': 'Lokale Serverzeit ({format}-Format)', + 'backup.auto.dayOfWeek': 'Wochentag', + 'backup.auto.dayOfMonth': 'Tag des Monats', + 'backup.auto.dayOfMonthHint': + 'Auf 1–28 beschränkt, um mit allen Monaten kompatibel zu sein', + 'backup.auto.scheduleSummary': 'Zeitplan', + 'backup.auto.summaryDaily': 'Täglich um {hour}:00', + 'backup.auto.summaryWeekly': 'Jeden {day} um {hour}:00', + 'backup.auto.summaryMonthly': 'Am {day}. jedes Monats um {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Auto-Backup wird über Docker-Umgebungsvariablen konfiguriert. Ändern Sie Ihre docker-compose.yml und starten Sie den Container neu.', + 'backup.auto.copyEnv': 'Docker-Umgebungsvariablen kopieren', + 'backup.auto.envCopied': + 'Docker-Umgebungsvariablen in die Zwischenablage kopiert', + 'backup.auto.keepLabel': 'Alte Backups löschen nach', + 'backup.dow.sunday': 'So', + 'backup.dow.monday': 'Mo', + 'backup.dow.tuesday': 'Di', + 'backup.dow.wednesday': 'Mi', + 'backup.dow.thursday': 'Do', + 'backup.dow.friday': 'Fr', + 'backup.dow.saturday': 'Sa', + 'backup.interval.hourly': 'Stündlich', + 'backup.interval.daily': 'Täglich', + 'backup.interval.weekly': 'Wöchentlich', + 'backup.interval.monthly': 'Monatlich', + 'backup.keep.1day': '1 Tag', + 'backup.keep.3days': '3 Tage', + 'backup.keep.7days': '7 Tage', + 'backup.keep.14days': '14 Tage', + 'backup.keep.30days': '30 Tage', + 'backup.keep.forever': 'Immer behalten', + 'backup.restoreConfirmTitle': 'Backup wiederherstellen?', + 'backup.restoreWarning': + 'Alle aktuellen Daten (Reisen, Orte, Benutzer, Uploads) werden unwiderruflich durch das Backup ersetzt. Dieser Vorgang kann nicht rückgängig gemacht werden.', + 'backup.restoreTip': + 'Tipp: Erstelle zuerst ein Backup des aktuellen Stands, bevor du wiederherstellst.', + 'backup.restoreConfirm': 'Ja, wiederherstellen', +}; +export default backup; diff --git a/shared/src/i18n/de/budget.ts b/shared/src/i18n/de/budget.ts new file mode 100644 index 00000000..feae9cff --- /dev/null +++ b/shared/src/i18n/de/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'CSV exportieren', + 'budget.emptyTitle': 'Noch kein Budget erstellt', + 'budget.emptyText': + 'Erstelle Kategorien und Einträge, um dein Reisebudget zu planen', + 'budget.emptyPlaceholder': 'Kategoriename eingeben...', + 'budget.createCategory': 'Kategorie erstellen', + 'budget.category': 'Kategorie', + 'budget.categoryName': 'Kategoriename', + 'budget.table.name': 'Name', + 'budget.table.total': 'Gesamt', + 'budget.table.persons': 'Personen', + 'budget.table.days': 'Tage', + 'budget.table.perPerson': 'Pro Person', + 'budget.table.perDay': 'Pro Tag', + 'budget.table.perPersonDay': 'P. p / Tag', + 'budget.table.note': 'Notiz', + 'budget.table.date': 'Datum', + 'budget.newEntry': 'Neuer Eintrag', + 'budget.defaultEntry': 'Neuer Eintrag', + 'budget.defaultCategory': 'Neue Kategorie', + 'budget.total': 'Gesamt', + 'budget.totalBudget': 'Gesamtbudget', + 'budget.byCategory': 'Nach Kategorie', + 'budget.editTooltip': 'Klicken zum Bearbeiten', + 'budget.linkedToReservation': + 'Verknüpft mit einer Buchung — Name dort bearbeiten', + 'budget.confirm.deleteCategory': + 'Möchtest du die Kategorie "{name}" mit {count} Einträgen wirklich löschen?', + 'budget.deleteCategory': 'Kategorie löschen', + 'budget.perPerson': 'Pro Person', + 'budget.paid': 'Bezahlt', + 'budget.open': 'Offen', + 'budget.noMembers': 'Keine Teilnehmer zugewiesen', + 'budget.settlement': 'Ausgleich', + 'budget.settlementInfo': + 'Klicke auf ein Mitglied-Bild bei einem Eintrag, um es grün zu markieren — das bedeutet, diese Person hat bezahlt. Der Ausgleich zeigt dann, wer wem wie viel schuldet.', + 'budget.netBalances': 'Netto-Salden', + 'budget.categoriesLabel': 'Kategorien', +}; +export default budget; diff --git a/shared/src/i18n/de/categories.ts b/shared/src/i18n/de/categories.ts new file mode 100644 index 00000000..79976085 --- /dev/null +++ b/shared/src/i18n/de/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Kategorien', + 'categories.subtitle': 'Kategorien für Orte verwalten', + 'categories.new': 'Neue Kategorie', + 'categories.empty': 'Keine Kategorien vorhanden', + 'categories.namePlaceholder': 'Kategoriename', + 'categories.icon': 'Symbol', + 'categories.color': 'Farbe', + 'categories.customColor': 'Eigene Farbe wählen', + 'categories.preview': 'Vorschau', + 'categories.defaultName': 'Kategorie', + 'categories.update': 'Aktualisieren', + 'categories.create': 'Erstellen', + 'categories.confirm.delete': + 'Kategorie löschen? Orte dieser Kategorie werden nicht gelöscht.', + 'categories.toast.loadError': 'Fehler beim Laden der Kategorien', + 'categories.toast.nameRequired': 'Bitte einen Namen eingeben', + 'categories.toast.updated': 'Kategorie aktualisiert', + 'categories.toast.created': 'Kategorie erstellt', + 'categories.toast.saveError': 'Fehler beim Speichern', + 'categories.toast.deleted': 'Kategorie gelöscht', + 'categories.toast.deleteError': 'Fehler beim Löschen', +}; +export default categories; diff --git a/shared/src/i18n/de/collab.ts b/shared/src/i18n/de/collab.ts new file mode 100644 index 00000000..3fe9e737 --- /dev/null +++ b/shared/src/i18n/de/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Notizen', + 'collab.tabs.polls': 'Umfragen', + 'collab.whatsNext.title': 'Nächste', + 'collab.whatsNext.today': 'Heute', + 'collab.whatsNext.tomorrow': 'Morgen', + 'collab.whatsNext.empty': 'Keine anstehenden Aktivitäten', + 'collab.whatsNext.until': 'bis', + 'collab.whatsNext.emptyHint': 'Aktivitäten mit Uhrzeit erscheinen hier', + 'collab.chat.send': 'Senden', + 'collab.chat.placeholder': 'Nachricht eingeben...', + 'collab.chat.empty': 'Starte die Unterhaltung', + 'collab.chat.emptyHint': + 'Nachrichten werden mit allen Reiseteilnehmern geteilt', + 'collab.chat.emptyDesc': + 'Teile Ideen, Pläne und Updates mit deiner Reisegruppe', + 'collab.chat.today': 'Heute', + 'collab.chat.yesterday': 'Gestern', + 'collab.chat.deletedMessage': 'hat eine Nachricht gelöscht', + 'collab.chat.reply': 'Antworten', + 'collab.chat.loadMore': 'Ältere Nachrichten laden', + 'collab.chat.justNow': 'gerade eben', + 'collab.chat.minutesAgo': 'vor {n} Min.', + 'collab.chat.hoursAgo': 'vor {n} Std.', + 'collab.notes.title': 'Notizen', + 'collab.notes.new': 'Neue Notiz', + 'collab.notes.empty': 'Noch keine Notizen', + 'collab.notes.emptyHint': 'Halte Ideen und Pläne fest', + 'collab.notes.all': 'Alle', + 'collab.notes.titlePlaceholder': 'Notiztitel', + 'collab.notes.contentPlaceholder': 'Schreibe etwas...', + 'collab.notes.categoryPlaceholder': 'Kategorie', + 'collab.notes.newCategory': 'Neue Kategorie...', + 'collab.notes.category': 'Kategorie', + 'collab.notes.noCategory': 'Keine Kategorie', + 'collab.notes.color': 'Farbe', + 'collab.notes.save': 'Speichern', + 'collab.notes.cancel': 'Abbrechen', + 'collab.notes.edit': 'Bearbeiten', + 'collab.notes.delete': 'Löschen', + 'collab.notes.pin': 'Anheften', + 'collab.notes.unpin': 'Loslösen', + 'collab.notes.daysAgo': 'vor {n} T.', + 'collab.notes.categorySettings': 'Kategorien verwalten', + 'collab.notes.create': 'Erstellen', + 'collab.notes.website': 'Website', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Dateien anhängen', + 'collab.notes.noCategoriesYet': 'Noch keine Kategorien', + 'collab.notes.emptyDesc': 'Erstelle eine Notiz um loszulegen', + 'collab.polls.title': 'Umfragen', + 'collab.polls.new': 'Neue Umfrage', + 'collab.polls.empty': 'Noch keine Umfragen', + 'collab.polls.emptyHint': 'Frage die Gruppe und stimmt gemeinsam ab', + 'collab.polls.question': 'Frage', + 'collab.polls.questionPlaceholder': 'Was sollen wir machen?', + 'collab.polls.addOption': '+ Option hinzufügen', + 'collab.polls.optionPlaceholder': 'Option {n}', + 'collab.polls.create': 'Umfrage erstellen', + 'collab.polls.close': 'Schließen', + 'collab.polls.closed': 'Geschlossen', + 'collab.polls.votes': '{n} Stimmen', + 'collab.polls.vote': '{n} Stimme', + 'collab.polls.multipleChoice': 'Mehrfachauswahl', + 'collab.polls.multiChoice': 'Mehrfachauswahl', + 'collab.polls.deadline': 'Frist', + 'collab.polls.option': 'Option', + 'collab.polls.options': 'Optionen', + 'collab.polls.delete': 'Löschen', + 'collab.polls.closedSection': 'Geschlossen', +}; +export default collab; diff --git a/shared/src/i18n/de/common.ts b/shared/src/i18n/de/common.ts new file mode 100644 index 00000000..508c6b57 --- /dev/null +++ b/shared/src/i18n/de/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Speichern', + 'common.showMore': 'Mehr anzeigen', + 'common.showLess': 'Weniger anzeigen', + 'common.cancel': 'Abbrechen', + 'common.clear': 'Löschen', + 'common.delete': 'Löschen', + 'common.edit': 'Bearbeiten', + 'common.add': 'Hinzufügen', + 'common.loading': 'Laden...', + 'common.import': 'Importieren', + 'common.select': 'Auswählen', + 'common.selectAll': 'Alle auswählen', + 'common.deselectAll': 'Alle abwählen', + 'common.error': 'Fehler', + 'common.unknownError': 'Unbekannter Fehler', + 'common.tooManyAttempts': + 'Zu viele Versuche. Bitte versuchen Sie es später erneut.', + 'common.back': 'Zurück', + 'common.all': 'Alle', + 'common.close': 'Schließen', + 'common.open': 'Öffnen', + 'common.upload': 'Hochladen', + 'common.search': 'Suchen', + 'common.confirm': 'Bestätigen', + 'common.ok': 'OK', + 'common.yes': 'Ja', + 'common.no': 'Nein', + 'common.or': 'oder', + 'common.none': 'Keine', + 'common.date': 'Datum', + 'common.rename': 'Umbenennen', + 'common.discardChanges': 'Änderungen verwerfen', + 'common.discard': 'Verwerfen', + 'common.name': 'Name', + 'common.email': 'E-Mail', + 'common.password': 'Passwort', + 'common.saving': 'Speichern...', + 'common.expand': 'Erweitern', + 'common.collapse': 'Einklappen', + 'common.justNow': 'gerade eben', + 'common.hoursAgo': 'vor {count}h', + 'common.daysAgo': 'vor {count}T', + 'common.saved': 'Gespeichert', + 'common.update': 'Aktualisieren', + 'common.change': 'Ändern', + 'common.uploading': 'Hochladen…', + 'common.backToPlanning': 'Zurück zur Planung', + 'common.reset': 'Zurücksetzen', + 'common.copy': 'Kopieren', + 'common.copied': 'Kopiert', +}; +export default common; diff --git a/shared/src/i18n/de/dashboard.ts b/shared/src/i18n/de/dashboard.ts new file mode 100644 index 00000000..aeb572b3 --- /dev/null +++ b/shared/src/i18n/de/dashboard.ts @@ -0,0 +1,108 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Meine Reisen', + 'dashboard.subtitle.loading': 'Reisen werden geladen...', + 'dashboard.subtitle.trips': '{count} Reisen ({archived} archiviert)', + 'dashboard.subtitle.empty': 'Starte deine erste Reise', + 'dashboard.subtitle.activeOne': '{count} aktive Reise', + 'dashboard.subtitle.activeMany': '{count} aktive Reisen', + 'dashboard.subtitle.archivedSuffix': ' · {count} archiviert', + 'dashboard.newTrip': 'Neue Reise', + 'dashboard.gridView': 'Kachelansicht', + 'dashboard.listView': 'Listenansicht', + 'dashboard.currency': 'Währung', + 'dashboard.timezone': 'Zeitzonen', + 'dashboard.localTime': 'Lokal', + 'dashboard.timezoneCustomTitle': 'Eigene Zeitzone', + 'dashboard.timezoneCustomLabelPlaceholder': 'Bezeichnung (optional)', + 'dashboard.timezoneCustomTzPlaceholder': 'z.B. America/New_York', + 'dashboard.timezoneCustomAdd': 'Hinzufügen', + 'dashboard.timezoneCustomErrorEmpty': 'Zeitzone eingeben', + 'dashboard.timezoneCustomErrorInvalid': + 'Ungültige Zeitzone. Format: Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Bereits hinzugefügt', + 'dashboard.emptyTitle': 'Noch keine Reisen', + 'dashboard.emptyText': + 'Erstelle deine erste Reise und beginne mit der Planung von Orten, Tagesabläufen und Packlisten.', + 'dashboard.emptyButton': 'Erste Reise erstellen', + 'dashboard.nextTrip': 'Nächste Reise', + 'dashboard.shared': 'Geteilt', + 'dashboard.sharedBy': 'Geteilt von {name}', + 'dashboard.days': 'Tage', + 'dashboard.places': 'Orte', + 'dashboard.members': 'Reise-Buddies', + 'dashboard.archive': 'Archivieren', + 'dashboard.copyTrip': 'Kopieren', + 'dashboard.copySuffix': 'Kopie', + 'dashboard.restore': 'Wiederherstellen', + 'dashboard.archived': 'Archiviert', + 'dashboard.status.ongoing': 'Laufend', + 'dashboard.status.today': 'Heute', + 'dashboard.status.tomorrow': 'Morgen', + 'dashboard.status.past': 'Vergangen', + 'dashboard.status.daysLeft': 'Noch {count} Tage', + 'dashboard.toast.loadError': 'Fehler beim Laden der Reisen', + 'dashboard.toast.created': 'Reise erfolgreich erstellt!', + 'dashboard.toast.createError': 'Fehler beim Erstellen', + 'dashboard.toast.updated': 'Reise aktualisiert!', + 'dashboard.toast.updateError': 'Fehler beim Aktualisieren', + 'dashboard.toast.deleted': 'Reise gelöscht', + 'dashboard.toast.deleteError': 'Fehler beim Löschen', + 'dashboard.toast.archived': 'Reise archiviert', + 'dashboard.toast.archiveError': 'Fehler beim Archivieren', + 'dashboard.toast.restored': 'Reise wiederhergestellt', + 'dashboard.toast.restoreError': 'Fehler beim Wiederherstellen', + 'dashboard.toast.copied': 'Reise kopiert!', + 'dashboard.toast.copyError': 'Fehler beim Kopieren der Reise', + 'dashboard.confirm.delete': + 'Reise "{title}" löschen? Alle Orte und Pläne werden unwiderruflich gelöscht.', + 'dashboard.editTrip': 'Reise bearbeiten', + 'dashboard.createTrip': 'Neue Reise erstellen', + 'dashboard.tripTitle': 'Titel', + 'dashboard.tripTitlePlaceholder': 'z.B. Sommer in Japan', + 'dashboard.tripDescription': 'Beschreibung', + 'dashboard.tripDescriptionPlaceholder': 'Worum geht es bei dieser Reise?', + 'dashboard.startDate': 'Startdatum', + 'dashboard.endDate': 'Enddatum', + 'dashboard.dayCount': 'Anzahl Tage', + 'dashboard.dayCountHint': + 'Wie viele Tage geplant werden sollen, wenn kein Reisezeitraum gesetzt ist.', + 'dashboard.noDateHint': + 'Kein Datum gesetzt — es werden 7 Standardtage erstellt. Du kannst das jederzeit ändern.', + 'dashboard.coverImage': 'Titelbild', + 'dashboard.addCoverImage': 'Titelbild hinzufügen (oder per Drag & Drop)', + 'dashboard.addMembers': 'Reisebegleiter', + 'dashboard.addMember': 'Mitglied hinzufügen', + 'dashboard.coverSaved': 'Titelbild gespeichert', + 'dashboard.coverUploadError': 'Fehler beim Hochladen', + 'dashboard.coverRemoveError': 'Fehler beim Entfernen', + 'dashboard.titleRequired': 'Titel ist erforderlich', + 'dashboard.endDateError': 'Enddatum muss nach dem Startdatum liegen', + 'dashboard.greeting.morning': 'Guten Morgen,', + 'dashboard.greeting.afternoon': 'Guten Tag,', + 'dashboard.greeting.evening': 'Guten Abend,', + 'dashboard.mobile.liveNow': 'Jetzt live', + 'dashboard.mobile.tripProgress': 'Reisefortschritt', + 'dashboard.mobile.daysLeft': '{count} Tage übrig', + 'dashboard.mobile.places': 'Orte', + 'dashboard.mobile.buddies': 'Freunde', + 'dashboard.mobile.newTrip': 'Neuer Trip', + 'dashboard.mobile.currency': 'Währung', + 'dashboard.mobile.timezone': 'Zeitzone', + 'dashboard.mobile.upcomingTrips': 'Anstehende Trips', + 'dashboard.mobile.yourTrips': 'Deine Trips', + 'dashboard.mobile.trips': 'Trips', + 'dashboard.mobile.starts': 'Beginn', + 'dashboard.mobile.duration': 'Dauer', + 'dashboard.mobile.day': 'Tag', + 'dashboard.mobile.days': 'Tage', + 'dashboard.mobile.ongoing': 'Laufend', + 'dashboard.mobile.startsToday': 'Beginnt heute', + 'dashboard.mobile.tomorrow': 'Morgen', + 'dashboard.mobile.inDays': 'In {count} Tagen', + 'dashboard.mobile.inMonths': 'In {count} Monaten', + 'dashboard.mobile.completed': 'Abgeschlossen', + 'dashboard.mobile.currencyConverter': 'Währungsrechner', +}; +export default dashboard; diff --git a/shared/src/i18n/de/day.ts b/shared/src/i18n/de/day.ts new file mode 100644 index 00000000..bf2e43e3 --- /dev/null +++ b/shared/src/i18n/de/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Regenwahrscheinlichkeit', + 'day.precipitation': 'Niederschlag', + 'day.wind': 'Wind', + 'day.sunrise': 'Sonnenaufgang', + 'day.sunset': 'Sonnenuntergang', + 'day.hourlyForecast': 'Stündliche Vorhersage', + 'day.climateHint': + 'Historische Durchschnittswerte — echte Vorhersage verfügbar innerhalb von 16 Tagen vor diesem Datum.', + 'day.noWeather': + 'Keine Wetterdaten verfügbar. Füge einen Ort mit Koordinaten hinzu.', + 'day.overview': 'Tagesübersicht', + 'day.accommodation': 'Unterkunft', + 'day.addAccommodation': 'Unterkunft hinzufügen', + 'day.hotelDayRange': 'Auf Tage anwenden', + 'day.noPlacesForHotel': 'Füge zuerst Orte zu deiner Reise hinzu', + 'day.allDays': 'Alle', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Bis', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Bestätigung', + 'day.editAccommodation': 'Unterkunft bearbeiten', + 'day.reservations': 'Reservierungen', +}; +export default day; diff --git a/shared/src/i18n/de/dayplan.ts b/shared/src/i18n/de/dayplan.ts new file mode 100644 index 00000000..1ad23de0 --- /dev/null +++ b/shared/src/i18n/de/dayplan.ts @@ -0,0 +1,48 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Kalender exportieren (ICS)', + 'dayplan.emptyDay': 'Keine Orte für diesen Tag geplant', + 'dayplan.cannotReorderTransport': + 'Buchungen mit fester Uhrzeit können nicht verschoben werden', + 'dayplan.confirmRemoveTimeTitle': 'Uhrzeit entfernen?', + 'dayplan.confirmRemoveTimeBody': + 'Dieser Ort hat eine feste Uhrzeit ({time}). Durch das Verschieben wird die Uhrzeit entfernt und der Ort kann frei sortiert werden.', + 'dayplan.confirmRemoveTimeAction': 'Uhrzeit entfernen & verschieben', + 'dayplan.cannotDropOnTimed': + 'Orte können nicht zwischen zeitgebundene Einträge geschoben werden', + 'dayplan.cannotBreakChronology': + 'Die zeitliche Reihenfolge von Uhrzeiten und Buchungen darf nicht verletzt werden', + 'dayplan.addNote': 'Notiz hinzufügen', + 'dayplan.expandAll': 'Alle Tage ausklappen', + 'dayplan.collapseAll': 'Alle Tage einklappen', + 'dayplan.editNote': 'Notiz bearbeiten', + 'dayplan.noteAdd': 'Notiz hinzufügen', + 'dayplan.noteEdit': 'Notiz bearbeiten', + 'dayplan.noteTitle': 'Notiz', + 'dayplan.noteSubtitle': 'Tagesnotiz', + 'dayplan.totalCost': 'Gesamtkosten', + 'dayplan.days': 'Tage', + 'dayplan.dayN': 'Tag {n}', + 'dayplan.calculating': 'Berechne...', + 'dayplan.route': 'Route', + 'dayplan.optimize': 'Optimieren', + 'dayplan.optimized': 'Route optimiert', + 'dayplan.routeError': 'Fehler bei der Routenberechnung', + 'dayplan.toast.needTwoPlaces': + 'Mindestens zwei Orte für Routenoptimierung nötig', + 'dayplan.toast.routeOptimized': 'Route optimiert', + 'dayplan.toast.noGeoPlaces': + 'Keine Orte mit Koordinaten für Routenberechnung gefunden', + 'dayplan.confirmed': 'Bestätigt', + 'dayplan.pendingRes': 'Ausstehend', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Tagesplan als PDF exportieren', + 'dayplan.pdfError': 'Fehler beim PDF-Export', + 'dayplan.mobile.addPlace': 'Ort hinzufügen', + 'dayplan.mobile.searchPlaces': 'Orte suchen...', + 'dayplan.mobile.allAssigned': 'Alle Orte zugeordnet', + 'dayplan.mobile.noMatch': 'Kein Treffer', + 'dayplan.mobile.createNew': 'Neuen Ort erstellen', +}; +export default dayplan; diff --git a/shared/src/i18n/de/externalNotifications.ts b/shared/src/i18n/de/externalNotifications.ts new file mode 100644 index 00000000..4f9446bb --- /dev/null +++ b/shared/src/i18n/de/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const de: NotificationLocale = { + email: { + footer: + 'Du erhältst diese E-Mail, weil du Benachrichtigungen in TREK aktiviert hast.', + manage: 'Einstellungen verwalten', + madeWith: 'Made with', + openTrek: 'TREK öffnen', + }, + events: { + trip_invite: (p) => ({ + title: `Einladung zu "${p.trip}"`, + body: `${p.actor} hat ${p.invitee || 'ein Mitglied'} zur Reise "${p.trip}" eingeladen.`, + }), + booking_change: (p) => ({ + title: `Neue Buchung: ${p.booking}`, + body: `${p.actor} hat eine neue Buchung "${p.booking}" (${p.type}) zu "${p.trip}" hinzugefügt.`, + }), + trip_reminder: (p) => ({ + title: `Reiseerinnerung: ${p.trip}`, + body: `Deine Reise "${p.trip}" steht bald an!`, + }), + todo_due: (p) => ({ + title: `Aufgabe fällig: ${p.todo}`, + body: `"${p.todo}" in "${p.trip}" ist am ${p.due} fällig.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion-Einladung', + body: `${p.actor} hat dich eingeladen, Urlaubspläne zu fusionieren. Öffne TREK um anzunehmen oder abzulehnen.`, + }), + photos_shared: (p) => ({ + title: `${p.count} Fotos geteilt`, + body: `${p.actor} hat ${p.count} Foto(s) in "${p.trip}" geteilt.`, + }), + collab_message: (p) => ({ + title: `Neue Nachricht in "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Packliste: ${p.category}`, + body: `${p.actor} hat dich der Kategorie "${p.category}" in der Packliste von "${p.trip}" zugewiesen.`, + }), + version_available: (p) => ({ + title: 'Neue TREK-Version verfügbar', + body: `TREK ${p.version} ist jetzt verfügbar. Besuche das Admin-Panel zum Aktualisieren.`, + }), + synology_session_cleared: () => ({ + title: 'Synology-Sitzung beendet', + body: 'Dein Synology-Konto oder die URL hat sich geändert. Du wurdest von Synology Photos abgemeldet.', + }), + }, + passwordReset: { + subject: 'Passwort zurücksetzen', + greeting: 'Hallo', + body: 'Wir haben eine Anfrage erhalten, das Passwort für dein TREK-Konto zurückzusetzen. Klicke auf den Button unten, um ein neues Passwort festzulegen.', + ctaIntro: 'Passwort zurücksetzen', + expiry: 'Dieser Link ist 60 Minuten gültig.', + ignore: + 'Wenn du das nicht warst, ignoriere diese E-Mail — dein Passwort bleibt unverändert.', + }, +}; + +export default de; diff --git a/shared/src/i18n/de/files.ts b/shared/src/i18n/de/files.ts new file mode 100644 index 00000000..47326d01 --- /dev/null +++ b/shared/src/i18n/de/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Dateien', + 'files.pageTitle': 'Dateien & Dokumente', + 'files.subtitle': '{count} Dateien für {trip}', + 'files.download': 'Herunterladen', + 'files.openError': 'Datei konnte nicht geöffnet werden', + 'files.downloadPdf': 'PDF herunterladen', + 'files.count': '{count} Dateien', + 'files.countSingular': '1 Datei', + 'files.uploaded': '{count} hochgeladen', + 'files.uploadError': 'Fehler beim Hochladen', + 'files.dropzone': 'Dateien hier ablegen', + 'files.dropzoneHint': 'oder klicken zum Auswählen', + 'files.allowedTypes': + 'Bilder, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Wird hochgeladen...', + 'files.filterAll': 'Alle', + 'files.filterPdf': 'PDFs', + 'files.filterImages': 'Bilder', + 'files.filterDocs': 'Dokumente', + 'files.filterCollab': 'Collab Notizen', + 'files.sourceCollab': 'Aus Collab Notizen', + 'files.empty': 'Keine Dateien vorhanden', + 'files.emptyHint': 'Lade Dateien hoch, um sie mit deiner Reise zu verknüpfen', + 'files.openTab': 'In neuem Tab öffnen', + 'files.confirm.delete': 'Möchtest du diese Datei wirklich löschen?', + 'files.toast.deleted': 'Datei gelöscht', + 'files.toast.deleteError': 'Fehler beim Löschen der Datei', + 'files.sourcePlan': 'Tagesplan', + 'files.sourceBooking': 'Buchung', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Anhängen', + 'files.pasteHint': + 'Du kannst auch Bilder aus der Zwischenablage einfügen (Strg+V)', + 'files.trash': 'Papierkorb', + 'files.trashEmpty': 'Papierkorb ist leer', + 'files.emptyTrash': 'Papierkorb leeren', + 'files.restore': 'Wiederherstellen', + 'files.star': 'Markieren', + 'files.unstar': 'Markierung entfernen', + 'files.assign': 'Zuweisen', + 'files.assignTitle': 'Datei zuweisen', + 'files.assignPlace': 'Ort', + 'files.assignBooking': 'Buchung', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Nicht zugewiesen', + 'files.unlink': 'Verknüpfung entfernen', + 'files.toast.trashed': 'In den Papierkorb verschoben', + 'files.toast.restored': 'Datei wiederhergestellt', + 'files.toast.trashEmptied': 'Papierkorb geleert', + 'files.toast.assigned': 'Datei zugewiesen', + 'files.toast.assignError': 'Zuweisung fehlgeschlagen', + 'files.toast.restoreError': 'Wiederherstellung fehlgeschlagen', + 'files.confirm.permanentDelete': + 'Diese Datei endgültig löschen? Das kann nicht rückgängig gemacht werden.', + 'files.confirm.emptyTrash': + 'Alle Dateien im Papierkorb endgültig löschen? Das kann nicht rückgängig gemacht werden.', + 'files.noteLabel': 'Notiz', + 'files.notePlaceholder': 'Notiz hinzufügen...', +}; +export default files; diff --git a/shared/src/i18n/de/index.ts b/shared/src/i18n/de/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/de/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/de/inspector.ts b/shared/src/i18n/de/inspector.ts new file mode 100644 index 00000000..27955f03 --- /dev/null +++ b/shared/src/i18n/de/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Geöffnet', + 'inspector.closed': 'Geschlossen', + 'inspector.openingHours': 'Öffnungszeiten', + 'inspector.showHours': 'Öffnungszeiten anzeigen', + 'inspector.files': 'Dateien', + 'inspector.filesCount': '{count} Dateien', + 'inspector.removeFromDay': 'Vom Tag entfernen', + 'inspector.remove': 'Entfernen', + 'inspector.addToDay': 'Zum Tag hinzufügen', + 'inspector.confirmedRes': 'Bestätigte Reservierung', + 'inspector.pendingRes': 'Ausstehende Reservierung', + 'inspector.google': 'In Google Maps öffnen', + 'inspector.website': 'Webseite öffnen', + 'inspector.addRes': 'Reservierung', + 'inspector.editRes': 'Reservierung bearbeiten', + 'inspector.participants': 'Teilnehmer', + 'inspector.trackStats': 'Streckendaten', +}; +export default inspector; diff --git a/shared/src/i18n/de/journey.ts b/shared/src/i18n/de/journey.ts new file mode 100644 index 00000000..f2161f37 --- /dev/null +++ b/shared/src/i18n/de/journey.ts @@ -0,0 +1,246 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Reisen suchen…', + 'journey.search.noResults': 'Keine Reisen passen zu „{query}"', + 'journey.title': 'Journey', + 'journey.subtitle': 'Dokumentiere deine Reisen unterwegs', + 'journey.new': 'Neue Journey', + 'journey.create': 'Erstellen', + 'journey.titlePlaceholder': 'Wohin geht die Reise?', + 'journey.empty': 'Noch keine Journeys', + 'journey.emptyHint': 'Starte die Dokumentation deiner naechsten Reise', + 'journey.deleted': 'Journey geloescht', + 'journey.createError': 'Journey konnte nicht erstellt werden', + 'journey.deleteError': 'Journey konnte nicht geloescht werden', + 'journey.deleteConfirmTitle': 'Loeschen', + 'journey.deleteConfirmMessage': + '"{title}" loeschen? Das kann nicht rueckgaengig gemacht werden.', + 'journey.deleteConfirmGeneric': + 'Bist du sicher, dass du das loeschen moechtest?', + 'journey.notFound': 'Journey nicht gefunden', + 'journey.photos': 'Fotos', + 'journey.timelineEmpty': 'Noch keine Stationen', + 'journey.timelineEmptyHint': + 'Fuege einen Check-in hinzu oder schreibe einen Tagebucheintrag', + 'journey.status.draft': 'Entwurf', + 'journey.status.active': 'Aktiv', + 'journey.status.completed': 'Abgeschlossen', + 'journey.status.upcoming': 'Anstehend', + 'journey.status.archived': 'Archiviert', + 'journey.checkin.add': 'Einchecken', + 'journey.checkin.namePlaceholder': 'Ortsname', + 'journey.checkin.notesPlaceholder': 'Notizen (optional)', + 'journey.checkin.save': 'Speichern', + 'journey.checkin.error': 'Check-in konnte nicht gespeichert werden', + 'journey.entry.add': 'Tagebuch', + 'journey.entry.edit': 'Eintrag bearbeiten', + 'journey.entry.titlePlaceholder': 'Titel (optional)', + 'journey.entry.bodyPlaceholder': 'Was ist heute passiert?', + 'journey.entry.save': 'Speichern', + 'journey.entry.error': 'Eintrag konnte nicht gespeichert werden', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Upload fehlgeschlagen', + 'journey.share.share': 'Teilen', + 'journey.share.public': 'Oeffentlich', + 'journey.share.linkCopied': 'Oeffentlicher Link kopiert', + 'journey.share.disabled': 'Oeffentliches Teilen deaktiviert', + 'journey.editor.titlePlaceholder': 'Gib diesem Moment einen Namen...', + 'journey.editor.bodyPlaceholder': 'Erzaehl die Geschichte dieses Tages...', + 'journey.editor.placePlaceholder': 'Ort (optional)', + 'journey.editor.tagsPlaceholder': + 'Tags: Geheimtipp, bestes Essen, nochmal hin...', + 'journey.visibility.private': 'Privat', + 'journey.visibility.shared': 'Geteilt', + 'journey.visibility.public': 'Oeffentlich', + 'journey.emptyState.title': 'Deine Geschichte beginnt hier', + 'journey.emptyState.subtitle': + 'Checke an einem Ort ein oder schreibe deinen ersten Tagebucheintrag', + 'journey.frontpage.subtitle': + 'Verwandle deine Reisen in Geschichten, die du nie vergisst', + 'journey.frontpage.createJourney': 'Journey erstellen', + 'journey.frontpage.activeJourney': 'Aktive Journey', + 'journey.frontpage.allJourneys': 'Alle Journeys', + 'journey.frontpage.journeys': 'Journeys', + 'journey.frontpage.createNew': 'Neue Journey erstellen', + 'journey.frontpage.createNewSub': + 'Trips auswählen, Geschichten schreiben, Abenteuer teilen', + 'journey.frontpage.live': 'Live', + 'journey.frontpage.synced': 'Synchronisiert', + 'journey.frontpage.continueWriting': 'Weiterschreiben', + 'journey.frontpage.updated': 'Aktualisiert {time}', + 'journey.frontpage.suggestionLabel': 'Trip gerade beendet', + 'journey.frontpage.suggestionText': + 'Verwandle {title} in eine Journey', + 'journey.frontpage.dismiss': 'Schließen', + 'journey.frontpage.journeyName': 'Journey-Name', + 'journey.frontpage.namePlaceholder': 'z.B. Südostasien 2026', + 'journey.frontpage.selectTrips': 'Trips auswählen', + 'journey.frontpage.tripsSelected': 'Trips ausgewählt', + 'journey.frontpage.trips': 'Trips', + 'journey.frontpage.placesImported': 'Orte werden importiert', + 'journey.frontpage.places': 'Orte', + 'journey.detail.backToJourney': 'Zurück zur Journey', + 'journey.detail.syncedWithTrips': 'Mit Trips synchronisiert', + 'journey.detail.addEntry': 'Eintrag hinzufügen', + 'journey.detail.newEntry': 'Neuer Eintrag', + 'journey.detail.editEntry': 'Eintrag bearbeiten', + 'journey.detail.noEntries': 'Noch keine Einträge', + 'journey.detail.noEntriesHint': + 'Füge einen Trip hinzu, um mit Skelett-Einträgen zu starten', + 'journey.detail.noPhotos': 'Noch keine Fotos', + 'journey.detail.noPhotosHint': + 'Lade Fotos hoch oder durchsuche deine Immich/Synology-Bibliothek', + 'journey.detail.journeyStats': 'Journey-Statistiken', + 'journey.detail.syncedTrips': 'Verknüpfte Trips', + 'journey.detail.noTripsLinked': 'Noch keine Trips verknüpft', + 'journey.detail.contributors': 'Mitwirkende', + 'journey.detail.readMore': 'Mehr lesen', + 'journey.detail.prosCons': 'Pro & Contra', + 'journey.detail.photos': 'Fotos', + 'journey.detail.day': 'Tag {number}', + 'journey.detail.places': 'Orte', + 'journey.stats.days': 'Tage', + 'journey.stats.cities': 'Städte', + 'journey.stats.entries': 'Einträge', + 'journey.stats.photos': 'Fotos', + 'journey.stats.places': 'Orte', + 'journey.skeletons.show': 'Vorschläge anzeigen', + 'journey.skeletons.hide': 'Vorschläge ausblenden', + 'journey.verdict.lovedIt': 'Toll', + 'journey.verdict.couldBeBetter': 'Verbesserungswürdig', + 'journey.synced.places': 'Orte', + 'journey.synced.synced': 'synchronisiert', + 'journey.editor.discardChangesConfirm': + 'Du hast ungespeicherte Änderungen. Verwerfen?', + 'journey.editor.uploadFailed': 'Foto-Upload fehlgeschlagen', + 'journey.editor.uploadPhotos': 'Fotos hochladen', + 'journey.editor.uploading': 'Hochladen...', + 'journey.editor.uploadingProgress': 'Hochladen {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} von {total} Fotos fehlgeschlagen — erneut speichern zum Wiederholen', + 'journey.editor.fromGallery': 'Aus Galerie', + 'journey.editor.allPhotosAdded': 'Alle Fotos bereits hinzugefügt', + 'journey.editor.writeStory': 'Erzähle deine Geschichte...', + 'journey.editor.prosCons': 'Pro & Contra', + 'journey.editor.pros': 'Pro', + 'journey.editor.cons': 'Contra', + 'journey.editor.proPlaceholder': 'Etwas Positives...', + 'journey.editor.conPlaceholder': 'Nicht so toll...', + 'journey.editor.addAnother': 'Hinzufügen', + 'journey.editor.date': 'Datum', + 'journey.editor.location': 'Ort', + 'journey.editor.searchLocation': 'Ort suchen...', + 'journey.editor.mood': 'Stimmung', + 'journey.editor.weather': 'Wetter', + 'journey.editor.photoFirst': '1.', + 'journey.editor.makeFirst': 'Als 1. setzen', + 'journey.editor.searching': 'Suche...', + 'journey.mood.amazing': 'Großartig', + 'journey.mood.good': 'Gut', + 'journey.mood.neutral': 'Neutral', + 'journey.mood.rough': 'Schwierig', + 'journey.weather.sunny': 'Sonnig', + 'journey.weather.partly': 'Teilweise bewölkt', + 'journey.weather.cloudy': 'Bewölkt', + 'journey.weather.rainy': 'Regnerisch', + 'journey.weather.stormy': 'Stürmisch', + 'journey.weather.cold': 'Schnee', + 'journey.trips.linkTrip': 'Trip verknüpfen', + 'journey.trips.searchTrip': 'Trip suchen', + 'journey.trips.searchPlaceholder': 'Tripname oder Reiseziel...', + 'journey.trips.noTripsAvailable': 'Keine Trips verfügbar', + 'journey.trips.link': 'Verknüpfen', + 'journey.trips.tripLinked': 'Trip verknüpft', + 'journey.trips.linkFailed': 'Verknüpfung fehlgeschlagen', + 'journey.trips.addTrip': 'Trip hinzufügen', + 'journey.trips.unlinkTrip': 'Trip trennen', + 'journey.trips.unlinkMessage': + '"{title}" trennen? Alle synchronisierten Einträge und Fotos dieses Trips werden unwiderruflich gelöscht.', + 'journey.trips.unlink': 'Trennen', + 'journey.trips.tripUnlinked': 'Trip getrennt', + 'journey.trips.unlinkFailed': 'Trennung fehlgeschlagen', + 'journey.trips.noTripsLinkedSettings': 'Keine Trips verknüpft', + 'journey.contributors.invite': 'Mitwirkenden einladen', + 'journey.contributors.searchUser': 'Benutzer suchen', + 'journey.contributors.searchPlaceholder': 'Benutzername oder E-Mail...', + 'journey.contributors.noUsers': 'Keine Benutzer gefunden', + 'journey.contributors.role': 'Rolle', + 'journey.contributors.added': 'Mitwirkender hinzugefügt', + 'journey.contributors.addFailed': 'Hinzufügen fehlgeschlagen', + 'journey.contributors.remove': 'Mitwirkenden entfernen', + 'journey.contributors.removeConfirm': + '{username} aus dieser Journey entfernen?', + 'journey.contributors.removed': 'Mitwirkender entfernt', + 'journey.contributors.removeFailed': 'Entfernen fehlgeschlagen', + 'journey.share.publicShare': 'Öffentlicher Link', + 'journey.share.createLink': 'Link erstellen', + 'journey.share.linkCreated': 'Link erstellt', + 'journey.share.createFailed': 'Link konnte nicht erstellt werden', + 'journey.share.copy': 'Kopieren', + 'journey.share.copied': 'Kopiert!', + 'journey.share.timeline': 'Zeitstrahl', + 'journey.share.gallery': 'Galerie', + 'journey.share.map': 'Karte', + 'journey.share.removeLink': 'Link entfernen', + 'journey.share.linkDeleted': 'Link entfernt', + 'journey.share.deleteFailed': 'Entfernen fehlgeschlagen', + 'journey.share.updateFailed': 'Aktualisierung fehlgeschlagen', + 'journey.invite.role': 'Rolle', + 'journey.invite.viewer': 'Betrachter', + 'journey.invite.editor': 'Bearbeiter', + 'journey.invite.invite': 'Einladen', + 'journey.invite.inviting': 'Wird eingeladen...', + 'journey.settings.title': 'Journey-Einstellungen', + 'journey.settings.coverImage': 'Titelbild', + 'journey.settings.changeCover': 'Titelbild ändern', + 'journey.settings.addCover': 'Titelbild hinzufügen', + 'journey.settings.name': 'Name', + 'journey.settings.subtitle': 'Untertitel', + 'journey.settings.subtitlePlaceholder': 'z.B. Thailand, Vietnam & Kambodscha', + 'journey.settings.endJourney': 'Reise archivieren', + 'journey.settings.reopenJourney': 'Reise wiederherstellen', + 'journey.settings.archived': 'Reise archiviert', + 'journey.settings.reopened': 'Reise erneut geöffnet', + 'journey.settings.endDescription': + 'Blendet das Live-Abzeichen aus. Sie können jederzeit wieder öffnen.', + 'journey.settings.delete': 'Löschen', + 'journey.settings.deleteJourney': 'Journey löschen', + 'journey.settings.deleteMessage': + '"{title}" löschen? Alle Einträge und Fotos gehen verloren.', + 'journey.settings.saved': 'Einstellungen gespeichert', + 'journey.settings.saveFailed': 'Speichern fehlgeschlagen', + 'journey.settings.coverUpdated': 'Titelbild aktualisiert', + 'journey.settings.coverFailed': 'Upload fehlgeschlagen', + 'journey.settings.failedToDelete': 'Löschen fehlgeschlagen', + 'journey.entries.deleteTitle': 'Eintrag löschen', + 'journey.photosUploaded': '{count} Fotos hochgeladen', + 'journey.photosUploadFailed': 'Einige Fotos konnten nicht hochgeladen werden', + 'journey.photosAdded': '{count} Fotos hinzugefügt', + 'journey.public.notFound': 'Nicht gefunden', + 'journey.public.notFoundMessage': + 'Diese Journey existiert nicht oder der Link ist abgelaufen.', + 'journey.public.readOnly': 'Nur lesen · Öffentliche Journey', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Geteilt über', + 'journey.public.madeWith': 'Erstellt mit', + 'journey.pdf.journeyBook': 'Reisebuch', + 'journey.pdf.madeWith': 'Erstellt mit TREK', + 'journey.pdf.day': 'Tag', + 'journey.pdf.theEnd': 'Ende', + 'journey.pdf.saveAsPdf': 'Als PDF speichern', + 'journey.pdf.pages': 'Seiten', + 'journey.picker.tripPeriod': 'Reisezeitraum', + 'journey.picker.dateRange': 'Zeitraum', + 'journey.picker.allPhotos': 'Alle Fotos', + 'journey.picker.albums': 'Alben', + 'journey.picker.selected': 'ausgewählt', + 'journey.picker.addTo': 'Hinzufügen zu', + 'journey.picker.newGallery': 'Neue Galerie', + 'journey.picker.selectAll': 'Alle auswählen', + 'journey.picker.deselectAll': 'Alle abwählen', + 'journey.picker.noAlbums': 'Keine Alben gefunden', + 'journey.picker.selectDate': 'Datum wählen', + 'journey.picker.search': 'Suchen', +}; +export default journey; diff --git a/shared/src/i18n/de/login.ts b/shared/src/i18n/de/login.ts new file mode 100644 index 00000000..3e701bf0 --- /dev/null +++ b/shared/src/i18n/de/login.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Anmeldung fehlgeschlagen. Bitte Zugangsdaten prüfen.', + 'login.tagline': 'Deine Reisen.\nDein Plan.', + 'login.description': + 'Plane Reisen gemeinsam mit interaktiven Karten, Budgets und Echtzeit-Sync.', + 'login.features.maps': 'Interaktive Karten', + 'login.features.mapsDesc': 'Google Places, Routen & Clustering', + 'login.features.realtime': 'Echtzeit-Sync', + 'login.features.realtimeDesc': 'Gemeinsam planen via WebSocket', + 'login.features.budget': 'Budget-Tracking', + 'login.features.budgetDesc': 'Kategorien, Diagramme & Pro-Kopf', + 'login.features.collab': 'Zusammenarbeit', + 'login.features.collabDesc': 'Multi-User mit geteilten Reisen', + 'login.features.packing': 'Packlisten', + 'login.features.packingDesc': 'Kategorien & Fortschritt', + 'login.features.bookings': 'Buchungen', + 'login.features.bookingsDesc': 'Flüge, Hotels, Restaurants & mehr', + 'login.features.files': 'Dokumente', + 'login.features.filesDesc': 'Dateien hochladen & verwalten', + 'login.features.routes': 'Routenoptimierung', + 'login.features.routesDesc': 'Auto-Optimierung & Google Maps Export', + 'login.selfHosted': 'Self-hosted · Open Source · Deine Daten bleiben bei dir', + 'login.title': 'Anmelden', + 'login.subtitle': 'Willkommen zurück', + 'login.signingIn': 'Anmelden…', + 'login.signIn': 'Anmelden', + 'login.createAdmin': 'Admin-Konto erstellen', + 'login.createAdminHint': 'Erstelle das erste Admin-Konto für TREK.', + 'login.setNewPassword': 'Neues Passwort festlegen', + 'login.setNewPasswordHint': + 'Sie müssen Ihr Passwort ändern, bevor Sie fortfahren können.', + 'login.createAccount': 'Konto erstellen', + 'login.createAccountHint': 'Neues Konto registrieren.', + 'login.creating': 'Erstelle…', + 'login.noAccount': 'Noch kein Konto?', + 'login.hasAccount': 'Bereits ein Konto?', + 'login.register': 'Registrieren', + 'login.emailPlaceholder': 'deine@email.de', + 'login.username': 'Benutzername', + 'login.oidc.registrationDisabled': + 'Registrierung ist deaktiviert. Kontaktiere den Administrator.', + 'login.oidc.noEmail': 'Keine E-Mail vom Provider erhalten.', + 'login.oidc.tokenFailed': 'Authentifizierung fehlgeschlagen.', + 'login.oidc.invalidState': 'Ungültige Sitzung. Bitte erneut versuchen.', + 'login.demoFailed': 'Demo-Login fehlgeschlagen', + 'login.oidcSignIn': 'Anmelden mit {name}', + 'login.oidcOnly': + 'Passwort-Authentifizierung ist deaktiviert. Bitte melde dich über deinen SSO-Anbieter an.', + 'login.oidcLoggedOut': + 'Du wurdest abgemeldet. Melde dich erneut über deinen SSO-Anbieter an.', + 'login.demoHint': 'Demo ausprobieren — ohne Registrierung', + 'login.mfaTitle': 'Zwei-Faktor-Authentifizierung', + 'login.mfaSubtitle': + 'Gib den 6-stelligen Code aus deiner Authenticator-App ein.', + 'login.mfaCodeLabel': 'Bestätigungscode', + 'login.mfaCodeRequired': 'Bitte den Code aus der Authenticator-App eingeben.', + 'login.mfaHint': + 'Google Authenticator, Authy oder eine andere TOTP-App öffnen.', + 'login.mfaBack': '← Zurück zur Anmeldung', + 'login.mfaVerify': 'Bestätigen', + 'login.invalidInviteLink': 'Ungültiger oder abgelaufener Einladungslink', + 'login.oidcFailed': 'OIDC-Anmeldung fehlgeschlagen', + 'login.usernameRequired': 'Benutzername ist erforderlich', + 'login.passwordMinLength': 'Das Passwort muss mindestens 8 Zeichen lang sein', + 'login.forgotPassword': 'Passwort vergessen?', + 'login.forgotPasswordTitle': 'Passwort zurücksetzen', + 'login.forgotPasswordBody': + 'Gib die E-Mail-Adresse deines Kontos ein. Falls ein Konto existiert, schicken wir dir einen Reset-Link.', + 'login.forgotPasswordSubmit': 'Reset-Link senden', + 'login.forgotPasswordSentTitle': 'Prüfe deine E-Mails', + 'login.forgotPasswordSentBody': + 'Falls ein Konto mit dieser Adresse existiert, ist ein Reset-Link unterwegs. Er läuft in 60 Minuten ab.', + 'login.forgotPasswordSmtpHintOff': + 'Hinweis: Der Administrator hat SMTP nicht konfiguriert. Der Reset-Link wird statt per E-Mail in die Server-Konsole geschrieben.', + 'login.backToLogin': 'Zurück zur Anmeldung', + 'login.newPassword': 'Neues Passwort', + 'login.confirmPassword': 'Neues Passwort bestätigen', + 'login.passwordsDontMatch': 'Passwörter stimmen nicht überein', + 'login.mfaCode': '2FA-Code', + 'login.resetPasswordTitle': 'Neues Passwort festlegen', + 'login.resetPasswordBody': + 'Wähle ein starkes Passwort, das du hier noch nicht verwendet hast. Mindestens 8 Zeichen.', + 'login.resetPasswordMfaBody': + 'Gib deinen 2FA-Code oder einen Backup-Code ein, um den Reset abzuschließen.', + 'login.resetPasswordSubmit': 'Passwort zurücksetzen', + 'login.resetPasswordVerify': 'Prüfen & zurücksetzen', + 'login.resetPasswordSuccessTitle': 'Passwort aktualisiert', + 'login.resetPasswordSuccessBody': + 'Du kannst dich jetzt mit deinem neuen Passwort anmelden.', + 'login.resetPasswordInvalidLink': 'Ungültiger Reset-Link', + 'login.resetPasswordInvalidLinkBody': + 'Dieser Link fehlt oder ist beschädigt. Fordere einen neuen an, um fortzufahren.', + 'login.resetPasswordFailed': + 'Zurücksetzen fehlgeschlagen. Der Link ist möglicherweise abgelaufen.', +}; +export default login; diff --git a/shared/src/i18n/de/map.ts b/shared/src/i18n/de/map.ts new file mode 100644 index 00000000..c763712d --- /dev/null +++ b/shared/src/i18n/de/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Verbindungen', + 'map.showConnections': 'Buchungsrouten anzeigen', + 'map.hideConnections': 'Buchungsrouten ausblenden', +}; +export default map; diff --git a/shared/src/i18n/de/members.ts b/shared/src/i18n/de/members.ts new file mode 100644 index 00000000..85aaa8bf --- /dev/null +++ b/shared/src/i18n/de/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Reise teilen', + 'members.inviteUser': 'Benutzer einladen', + 'members.selectUser': 'Benutzer auswählen…', + 'members.invite': 'Einladen', + 'members.allHaveAccess': 'Alle Benutzer haben bereits Zugriff.', + 'members.access': 'Zugriff', + 'members.person': 'Person', + 'members.persons': 'Personen', + 'members.you': 'du', + 'members.owner': 'Eigentümer', + 'members.leaveTrip': 'Reise verlassen', + 'members.removeAccess': 'Zugriff entfernen', + 'members.confirmLeave': 'Reise verlassen? Du verlierst den Zugriff.', + 'members.confirmRemove': 'Zugriff für diesen Benutzer entfernen?', + 'members.loadError': 'Fehler beim Laden der Mitglieder', + 'members.added': 'hinzugefügt', + 'members.addError': 'Fehler beim Hinzufügen', + 'members.removed': 'Mitglied entfernt', + 'members.removeError': 'Fehler beim Entfernen', +}; +export default members; diff --git a/shared/src/i18n/de/memories.ts b/shared/src/i18n/de/memories.ts new file mode 100644 index 00000000..3e0f2c11 --- /dev/null +++ b/shared/src/i18n/de/memories.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Fotos', + 'memories.notConnected': 'Immich nicht verbunden', + 'memories.notConnectedHint': + 'Verbinde deine Immich-Instanz in den Einstellungen, um deine Reisefotos hier zu sehen.', + 'memories.notConnectedMultipleHint': + 'Verbinde einen dieser Fotoanbieter: {provider_names} in den Einstellungen, um Fotos zu dieser Reise hinzufügen zu können.', + 'memories.noDates': 'Füge Daten zu deiner Reise hinzu, um Fotos zu laden.', + 'memories.noPhotos': 'Keine Fotos gefunden', + 'memories.noPhotosHint': + 'Keine Fotos in Immich für den Zeitraum dieser Reise gefunden.', + 'memories.photosFound': 'Fotos', + 'memories.fromOthers': 'von anderen', + 'memories.sharePhotos': 'Fotos teilen', + 'memories.sharing': 'Wird geteilt', + 'memories.reviewTitle': 'Deine Fotos prüfen', + 'memories.reviewHint': 'Klicke auf Fotos, um sie vom Teilen auszuschließen.', + 'memories.shareCount': '{count} Fotos teilen', + 'memories.providerUrl': 'Server-URL', + 'memories.providerApiKey': 'API-Schlüssel', + 'memories.providerUsername': 'Benutzername', + 'memories.providerPassword': 'Passwort', + 'memories.providerOTP': 'MFA-Code (falls aktiviert)', + 'memories.skipSSLVerification': 'SSL-Zertifikatsprüfung überspringen', + 'memories.immichAutoUpload': + 'Journey-Fotos beim Upload auch zu Immich spiegeln', + 'memories.providerUrlHintSynology': + 'Füge den Fotos-App-Pfad in die URL ein, z.B. https://nas:5001/photo', + 'memories.testConnection': 'Verbindung testen', + 'memories.testShort': 'Testen', + 'memories.testFirst': 'Verbindung zuerst testen', + 'memories.connected': 'Verbunden', + 'memories.disconnected': 'Nicht verbunden', + 'memories.connectionSuccess': 'Verbindung zu Immich hergestellt', + 'memories.connectionError': 'Verbindung zu Immich fehlgeschlagen', + 'memories.saved': '{provider_name}-Einstellungen gespeichert', + 'memories.providerDisconnectedBanner': + 'Deine {provider_name}-Verbindung wurde getrennt. Verbinde erneut in den Einstellungen, um Fotos anzuzeigen.', + 'memories.saveError': + '{provider_name}-Einstellungen konnten nicht gespeichert werden', + 'memories.saveRouteNotConfigured': + 'Speicherroute ist für diesen Anbieter nicht konfiguriert', + 'memories.testRouteNotConfigured': + 'Testroute ist für diesen Anbieter nicht konfiguriert', + 'memories.fillRequiredFields': 'Bitte füllen Sie alle Pflichtfelder aus', + 'memories.addPhotos': 'Fotos hinzufügen', + 'memories.linkAlbum': 'Album verknüpfen', + 'memories.selectAlbum': 'Immich-Album auswählen', + 'memories.selectAlbumMultiple': 'Album auswählen', + 'memories.noAlbums': 'Keine Alben gefunden', + 'memories.syncAlbum': 'Album synchronisieren', + 'memories.unlinkAlbum': 'Album trennen', + 'memories.photos': 'Fotos', + 'memories.selectPhotos': 'Fotos aus Immich auswählen', + 'memories.selectPhotosMultiple': 'Fotos auswählen', + 'memories.selectHint': 'Tippe auf Fotos um sie auszuwählen.', + 'memories.selected': 'ausgewählt', + 'memories.addSelected': '{count} Fotos hinzufügen', + 'memories.alreadyAdded': 'Hinzugefügt', + 'memories.private': 'Privat', + 'memories.stopSharing': 'Nicht mehr teilen', + 'memories.oldest': 'Älteste zuerst', + 'memories.newest': 'Neueste zuerst', + 'memories.allLocations': 'Alle Orte', + 'memories.tripDates': 'Trip-Zeitraum', + 'memories.allPhotos': 'Alle Fotos', + 'memories.confirmShareTitle': 'Mit Reisebegleitern teilen?', + 'memories.confirmShareHint': + '{count} Fotos werden für alle Mitglieder dieses Trips sichtbar. Du kannst einzelne Fotos nachträglich auf privat setzen.', + 'memories.confirmShareButton': 'Fotos teilen', + 'memories.error.loadAlbums': 'Alben konnten nicht geladen werden', + 'memories.error.linkAlbum': 'Album konnte nicht verknüpft werden', + 'memories.error.unlinkAlbum': 'Album konnte nicht getrennt werden', + 'memories.error.syncAlbum': 'Album konnte nicht synchronisiert werden', + 'memories.error.loadPhotos': 'Fotos konnten nicht geladen werden', + 'memories.error.addPhotos': 'Fotos konnten nicht hinzugefügt werden', + 'memories.error.removePhoto': 'Foto konnte nicht entfernt werden', + 'memories.error.toggleSharing': 'Freigabe konnte nicht aktualisiert werden', +}; +export default memories; diff --git a/shared/src/i18n/de/nav.ts b/shared/src/i18n/de/nav.ts new file mode 100644 index 00000000..c94f9e91 --- /dev/null +++ b/shared/src/i18n/de/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Reise', + 'nav.share': 'Teilen', + 'nav.settings': 'Einstellungen', + 'nav.admin': 'Admin', + 'nav.logout': 'Abmelden', + 'nav.lightMode': 'Heller Modus', + 'nav.darkMode': 'Dunkler Modus', + 'nav.autoMode': 'Automatischer Modus', + 'nav.administrator': 'Administrator', + 'nav.myTrips': 'Meine Trips', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Einstellungen', + 'nav.bottomAdmin': 'Admin-Einstellungen', + 'nav.bottomLogout': 'Abmelden', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/de/notif.ts b/shared/src/i18n/de/notif.ts new file mode 100644 index 00000000..b498226f --- /dev/null +++ b/shared/src/i18n/de/notif.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Benachrichtigung', + 'notif.test.simple.text': 'Dies ist eine einfache Testbenachrichtigung.', + 'notif.test.boolean.text': 'Akzeptierst du diese Testbenachrichtigung?', + 'notif.test.navigate.text': 'Klicke unten, um zum Dashboard zu navigieren.', + 'notif.trip_invite.title': 'Reiseeinladung', + 'notif.trip_invite.text': '{actor} hat dich zu {trip} eingeladen', + 'notif.booking_change.title': 'Buchung aktualisiert', + 'notif.booking_change.text': + '{actor} hat eine Buchung in {trip} aktualisiert', + 'notif.trip_reminder.title': 'Reiseerinnerung', + 'notif.trip_reminder.text': 'Deine Reise {trip} steht bald an!', + 'notif.todo_due.title': 'Aufgabe fällig', + 'notif.todo_due.text': '{todo} in {trip} ist am {due} fällig', + 'notif.vacay_invite.title': 'Vacay Fusion-Einladung', + 'notif.vacay_invite.text': + '{actor} hat dich zum Fusionieren von Urlaubsplänen eingeladen', + 'notif.photos_shared.title': 'Fotos geteilt', + 'notif.photos_shared.text': '{actor} hat {count} Foto(s) in {trip} geteilt', + 'notif.collab_message.title': 'Neue Nachricht', + 'notif.collab_message.text': '{actor} hat eine Nachricht in {trip} gesendet', + 'notif.packing_tagged.title': 'Packlistenzuweisung', + 'notif.packing_tagged.text': + '{actor} hat dich zu {category} in {trip} zugewiesen', + 'notif.version_available.title': 'Neue Version verfügbar', + 'notif.version_available.text': 'TREK {version} ist jetzt verfügbar', + 'notif.action.view_trip': 'Reise ansehen', + 'notif.action.view_collab': 'Nachrichten ansehen', + 'notif.action.view_packing': 'Packliste ansehen', + 'notif.action.view_photos': 'Fotos ansehen', + 'notif.action.view_vacay': 'Vacay ansehen', + 'notif.action.view_admin': 'Zum Admin', + 'notif.action.view': 'Ansehen', + 'notif.action.accept': 'Annehmen', + 'notif.action.decline': 'Ablehnen', + 'notif.generic.title': 'Benachrichtigung', + 'notif.generic.text': 'Du hast eine neue Benachrichtigung', + 'notif.dev.unknown_event.title': '[DEV] Unbekanntes Ereignis', + 'notif.dev.unknown_event.text': + 'Ereignistyp "{event}" ist nicht in EVENT_NOTIFICATION_CONFIG registriert', +}; +export default notif; diff --git a/shared/src/i18n/de/notifications.ts b/shared/src/i18n/de/notifications.ts new file mode 100644 index 00000000..60fc9384 --- /dev/null +++ b/shared/src/i18n/de/notifications.ts @@ -0,0 +1,39 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Benachrichtigungen', + 'notifications.markAllRead': 'Alle als gelesen markieren', + 'notifications.deleteAll': 'Alle löschen', + 'notifications.showAll': 'Alle Benachrichtigungen anzeigen', + 'notifications.empty': 'Keine Benachrichtigungen', + 'notifications.emptyDescription': 'Sie sind auf dem neuesten Stand!', + 'notifications.all': 'Alle', + 'notifications.unreadOnly': 'Ungelesen', + 'notifications.markRead': 'Als gelesen markieren', + 'notifications.markUnread': 'Als ungelesen markieren', + 'notifications.delete': 'Löschen', + 'notifications.system': 'System', + 'notifications.synologySessionCleared.title': 'Synology Photos getrennt', + 'notifications.synologySessionCleared.text': + 'Dein Server oder Konto hat sich geändert — gehe zu Einstellungen, um deine Verbindung erneut zu testen.', + 'notifications.test.title': 'Testbenachrichtigung von {actor}', + 'notifications.test.text': 'Dies ist eine einfache Testbenachrichtigung.', + 'notifications.test.booleanTitle': '{actor} bittet um Ihre Zustimmung', + 'notifications.test.booleanText': + 'Dies ist eine boolesche Testbenachrichtigung.', + 'notifications.test.accept': 'Genehmigen', + 'notifications.test.decline': 'Ablehnen', + 'notifications.test.navigateTitle': 'Etwas ansehen', + 'notifications.test.navigateText': + 'Dies ist eine Navigations-Testbenachrichtigung.', + 'notifications.test.goThere': 'Dorthin', + 'notifications.test.adminTitle': 'Admin-Broadcast', + 'notifications.test.adminText': + '{actor} hat eine Testbenachrichtigung an alle Admins gesendet.', + 'notifications.test.tripTitle': '{actor} hat in Ihrer Reise gepostet', + 'notifications.test.tripText': 'Testbenachrichtigung für Reise "{trip}".', + 'notifications.versionAvailable.title': 'Update verfügbar', + 'notifications.versionAvailable.text': 'TREK {version} ist jetzt verfügbar.', + 'notifications.versionAvailable.button': 'Details anzeigen', +}; +export default notifications; diff --git a/shared/src/i18n/de/oauth.ts b/shared/src/i18n/de/oauth.ts new file mode 100644 index 00000000..43a21e76 --- /dev/null +++ b/shared/src/i18n/de/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Reisen', + 'oauth.scope.group.places': 'Orte', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Packliste', + 'oauth.scope.group.todos': 'Aufgaben', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Buchungen', + 'oauth.scope.group.collab': 'Zusammenarbeit', + 'oauth.scope.group.notifications': 'Benachrichtigungen', + 'oauth.scope.group.vacay': 'Urlaub', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Wetter', + 'oauth.scope.group.journey': 'Journey', + 'oauth.scope.trips:read.label': 'Reisen und Reisepläne anzeigen', + 'oauth.scope.trips:read.description': + 'Reisen, Tage, Tagesnotizen und Mitglieder lesen', + 'oauth.scope.trips:write.label': 'Reisen und Reisepläne bearbeiten', + 'oauth.scope.trips:write.description': + 'Reisen, Tage und Notizen erstellen, aktualisieren und Mitglieder verwalten', + 'oauth.scope.trips:delete.label': 'Reisen löschen', + 'oauth.scope.trips:delete.description': + 'Reisen dauerhaft löschen — diese Aktion ist unwiderruflich', + 'oauth.scope.trips:share.label': 'Freigabelinks verwalten', + 'oauth.scope.trips:share.description': + 'Öffentliche Freigabelinks erstellen, aktualisieren und widerrufen', + 'oauth.scope.places:read.label': 'Orte und Kartendaten anzeigen', + 'oauth.scope.places:read.description': + 'Orte, Tageszuweisungen, Tags und Kategorien lesen', + 'oauth.scope.places:write.label': 'Orte verwalten', + 'oauth.scope.places:write.description': + 'Orte, Zuweisungen und Tags erstellen, aktualisieren und löschen', + 'oauth.scope.atlas:read.label': 'Atlas anzeigen', + 'oauth.scope.atlas:read.description': + 'Besuchte Länder, Regionen und Wunschliste lesen', + 'oauth.scope.atlas:write.label': 'Atlas verwalten', + 'oauth.scope.atlas:write.description': + 'Länder und Regionen als besucht markieren, Wunschliste verwalten', + 'oauth.scope.packing:read.label': 'Packlisten anzeigen', + 'oauth.scope.packing:read.description': + 'Packgegenstände, Taschen und Kategoriezuweisungen lesen', + 'oauth.scope.packing:write.label': 'Packlisten verwalten', + 'oauth.scope.packing:write.description': + 'Packgegenstände und Taschen hinzufügen, aktualisieren, löschen, abhaken und sortieren', + 'oauth.scope.todos:read.label': 'Aufgabenlisten anzeigen', + 'oauth.scope.todos:read.description': + 'Reiseaufgaben und Kategoriezuweisungen lesen', + 'oauth.scope.todos:write.label': 'Aufgabenlisten verwalten', + 'oauth.scope.todos:write.description': + 'Aufgaben erstellen, aktualisieren, abhaken, löschen und sortieren', + 'oauth.scope.budget:read.label': 'Budget anzeigen', + 'oauth.scope.budget:read.description': + 'Budgeteinträge und Ausgabenaufschlüsselung lesen', + 'oauth.scope.budget:write.label': 'Budget verwalten', + 'oauth.scope.budget:write.description': + 'Budgeteinträge erstellen, aktualisieren und löschen', + 'oauth.scope.reservations:read.label': 'Buchungen anzeigen', + 'oauth.scope.reservations:read.description': + 'Buchungen und Unterkunftsdetails lesen', + 'oauth.scope.reservations:write.label': 'Buchungen verwalten', + 'oauth.scope.reservations:write.description': + 'Buchungen erstellen, aktualisieren, löschen und sortieren', + 'oauth.scope.collab:read.label': 'Zusammenarbeit anzeigen', + 'oauth.scope.collab:read.description': + 'Kollaborationsnotizen, Umfragen und Nachrichten lesen', + 'oauth.scope.collab:write.label': 'Zusammenarbeit verwalten', + 'oauth.scope.collab:write.description': + 'Kollaborationsnotizen, Umfragen und Nachrichten erstellen, aktualisieren und löschen', + 'oauth.scope.notifications:read.label': 'Benachrichtigungen anzeigen', + 'oauth.scope.notifications:read.description': + 'In-App-Benachrichtigungen und ungelesene Zählungen lesen', + 'oauth.scope.notifications:write.label': 'Benachrichtigungen verwalten', + 'oauth.scope.notifications:write.description': + 'Benachrichtigungen als gelesen markieren und darauf reagieren', + 'oauth.scope.vacay:read.label': 'Urlaubspläne anzeigen', + 'oauth.scope.vacay:read.description': + 'Urlaubsplanungsdaten, Einträge und Statistiken lesen', + 'oauth.scope.vacay:write.label': 'Urlaubspläne verwalten', + 'oauth.scope.vacay:write.description': + 'Urlaubseinträge, Feiertage und Teampläne erstellen und verwalten', + 'oauth.scope.geo:read.label': 'Karten & Geocodierung', + 'oauth.scope.geo:read.description': + 'Orte suchen, Karten-URLs auflösen und Koordinaten rückwärts geokodieren', + 'oauth.scope.weather:read.label': 'Wettervorhersagen', + 'oauth.scope.weather:read.description': + 'Wettervorhersagen für Reiseorte und -daten abrufen', + 'oauth.scope.journey:read.label': 'Journeys ansehen', + 'oauth.scope.journey:read.description': + 'Journeys, Einträge und Mitarbeiterliste lesen', + 'oauth.scope.journey:write.label': 'Journeys verwalten', + 'oauth.scope.journey:write.description': + 'Journeys und deren Einträge erstellen, bearbeiten und löschen', + 'oauth.scope.journey:share.label': 'Journey-Links verwalten', + 'oauth.scope.journey:share.description': + 'Öffentliche Freigabelinks für Journeys erstellen, aktualisieren und widerrufen', +}; +export default oauth; diff --git a/shared/src/i18n/de/packing.ts b/shared/src/i18n/de/packing.ts new file mode 100644 index 00000000..3d80d511 --- /dev/null +++ b/shared/src/i18n/de/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Packliste', + 'packing.empty': 'Packliste ist leer', + 'packing.import': 'Importieren', + 'packing.importTitle': 'Packliste importieren', + 'packing.importHint': + 'Ein Eintrag pro Zeile. Format: Kategorie, Name, Gewicht in g (optional), Tasche (optional), checked/unchecked (optional)', + 'packing.importPlaceholder': + 'Hygiene, Zahnbürste\nKleidung, T-Shirts, 200\nDokumente, Reisepass, , Handgepäck\nElektronik, Ladekabel, 50, Koffer, checked', + 'packing.importCsv': 'CSV/TXT laden', + 'packing.importAction': '{count} importieren', + 'packing.importSuccess': '{count} Einträge importiert', + 'packing.importError': 'Import fehlgeschlagen', + 'packing.importEmpty': 'Keine Einträge zum Importieren', + 'packing.progress': '{packed} von {total} gepackt ({percent}%)', + 'packing.clearChecked': '{count} abgehakte entfernen', + 'packing.clearCheckedShort': '{count} entfernen', + 'packing.suggestions': 'Vorschläge', + 'packing.suggestionsTitle': 'Vorschläge hinzufügen', + 'packing.allSuggested': 'Alle Vorschläge hinzugefügt', + 'packing.allPacked': 'Alles gepackt!', + 'packing.addPlaceholder': 'Neuen Gegenstand hinzufügen...', + 'packing.categoryPlaceholder': 'Kategorie...', + 'packing.filterAll': 'Alle', + 'packing.filterOpen': 'Offen', + 'packing.filterDone': 'Erledigt', + 'packing.emptyTitle': 'Packliste ist leer', + 'packing.emptyHint': 'Füge Gegenstände hinzu oder nutze die Vorschläge', + 'packing.emptyFiltered': 'Keine Gegenstände in diesem Filter', + 'packing.menuRename': 'Umbenennen', + 'packing.menuCheckAll': 'Alle abhaken', + 'packing.menuUncheckAll': 'Alle Haken entfernen', + 'packing.menuDeleteCat': 'Kategorie löschen', + 'packing.noMembers': 'Keine Mitglieder', + 'packing.addItem': 'Eintrag hinzufügen', + 'packing.addItemPlaceholder': 'Artikelname...', + 'packing.addCategory': 'Kategorie hinzufügen', + 'packing.newCategoryPlaceholder': 'Kategoriename (z.B. Kleidung)', + 'packing.applyTemplate': 'Vorlage anwenden', + 'packing.template': 'Vorlage', + 'packing.templateApplied': '{count} Einträge aus Vorlage hinzugefügt', + 'packing.templateError': 'Vorlage konnte nicht angewendet werden', + 'packing.saveAsTemplate': 'Als Vorlage speichern', + 'packing.templateName': 'Vorlagenname', + 'packing.templateSaved': 'Packliste als Vorlage gespeichert', + 'packing.bags': 'Gepäck', + 'packing.noBag': 'Nicht zugeordnet', + 'packing.totalWeight': 'Gesamtgewicht', + 'packing.bagName': 'Name...', + 'packing.addBag': 'Gepäck hinzufügen', + 'packing.changeCategory': 'Kategorie ändern', + 'packing.confirm.clearChecked': + 'Möchtest du {count} abgehakte Gegenstände wirklich entfernen?', + 'packing.confirm.deleteCat': + 'Möchtest du die Kategorie "{name}" mit {count} Gegenständen wirklich löschen?', + 'packing.defaultCategory': 'Sonstiges', + 'packing.toast.saveError': 'Fehler beim Speichern', + 'packing.toast.deleteError': 'Fehler beim Löschen', + 'packing.toast.renameError': 'Fehler beim Umbenennen', + 'packing.toast.addError': 'Fehler beim Hinzufügen', + 'packing.suggestions.items': [ + { + name: 'Reisepass', + category: 'Dokumente', + }, + { + name: 'Personalausweis', + category: 'Dokumente', + }, + { + name: 'Reiseversicherung', + category: 'Dokumente', + }, + { + name: 'Flugtickets', + category: 'Dokumente', + }, + { + name: 'Kreditkarte', + category: 'Finanzen', + }, + { + name: 'Bargeld', + category: 'Finanzen', + }, + { + name: 'Visum', + category: 'Dokumente', + }, + { + name: 'T-Shirts', + category: 'Kleidung', + }, + { + name: 'Hosen', + category: 'Kleidung', + }, + { + name: 'Unterwäsche', + category: 'Kleidung', + }, + { + name: 'Socken', + category: 'Kleidung', + }, + { + name: 'Jacke', + category: 'Kleidung', + }, + { + name: 'Schlafkleidung', + category: 'Kleidung', + }, + { + name: 'Badekleidung', + category: 'Kleidung', + }, + { + name: 'Regenjacke', + category: 'Kleidung', + }, + { + name: 'Bequeme Schuhe', + category: 'Kleidung', + }, + { + name: 'Zahnbürste', + category: 'Hygiene', + }, + { + name: 'Zahnpasta', + category: 'Hygiene', + }, + { + name: 'Shampoo', + category: 'Hygiene', + }, + { + name: 'Deo', + category: 'Hygiene', + }, + { + name: 'Sonnencreme', + category: 'Hygiene', + }, + { + name: 'Rasierer', + category: 'Hygiene', + }, + { + name: 'Ladegerät', + category: 'Elektronik', + }, + { + name: 'Powerbank', + category: 'Elektronik', + }, + { + name: 'Kopfhörer', + category: 'Elektronik', + }, + { + name: 'Reiseadapter', + category: 'Elektronik', + }, + { + name: 'Kamera', + category: 'Elektronik', + }, + { + name: 'Schmerzmittel', + category: 'Gesundheit', + }, + { + name: 'Pflaster', + category: 'Gesundheit', + }, + { + name: 'Desinfektionsmittel', + category: 'Gesundheit', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/de/pdf.ts b/shared/src/i18n/de/pdf.ts new file mode 100644 index 00000000..003832ea --- /dev/null +++ b/shared/src/i18n/de/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Reiseplan', + 'pdf.planned': 'Eingeplant', + 'pdf.costLabel': 'Kosten EUR', + 'pdf.preview': 'PDF Vorschau', + 'pdf.saveAsPdf': 'Als PDF speichern', +}; +export default pdf; diff --git a/shared/src/i18n/de/perm.ts b/shared/src/i18n/de/perm.ts new file mode 100644 index 00000000..dbed9847 --- /dev/null +++ b/shared/src/i18n/de/perm.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Berechtigungseinstellungen', + 'perm.subtitle': 'Steuern Sie, wer Aktionen in der Anwendung ausführen kann', + 'perm.saved': 'Berechtigungseinstellungen gespeichert', + 'perm.resetDefaults': 'Auf Standard zurücksetzen', + 'perm.customized': 'angepasst', + 'perm.level.admin': 'Nur Administrator', + 'perm.level.tripOwner': 'Reise-Eigentümer', + 'perm.level.tripMember': 'Reise-Mitglieder', + 'perm.level.everybody': 'Alle', + 'perm.cat.trip': 'Reiseverwaltung', + 'perm.cat.members': 'Mitgliederverwaltung', + 'perm.cat.files': 'Dateien', + 'perm.cat.content': 'Inhalte & Zeitplan', + 'perm.cat.extras': 'Budget, Packlisten & Zusammenarbeit', + 'perm.action.trip_create': 'Reisen erstellen', + 'perm.action.trip_edit': 'Reisedetails bearbeiten', + 'perm.action.trip_delete': 'Reisen löschen', + 'perm.action.trip_archive': 'Reisen archivieren / dearchivieren', + 'perm.action.trip_cover_upload': 'Titelbild hochladen', + 'perm.action.member_manage': 'Mitglieder hinzufügen / entfernen', + 'perm.action.file_upload': 'Dateien hochladen', + 'perm.action.file_edit': 'Datei-Metadaten bearbeiten', + 'perm.action.file_delete': 'Dateien löschen', + 'perm.action.place_edit': 'Orte hinzufügen / bearbeiten / löschen', + 'perm.action.day_edit': 'Tage, Notizen & Zuweisungen bearbeiten', + 'perm.action.reservation_edit': 'Reservierungen verwalten', + 'perm.action.budget_edit': 'Budget verwalten', + 'perm.action.packing_edit': 'Packlisten verwalten', + 'perm.action.collab_edit': 'Zusammenarbeit (Notizen, Umfragen, Chat)', + 'perm.action.share_manage': 'Freigabelinks verwalten', + 'perm.actionHint.trip_create': 'Wer kann neue Reisen erstellen', + 'perm.actionHint.trip_edit': + 'Wer kann Reisename, Daten, Beschreibung und Währung ändern', + 'perm.actionHint.trip_delete': 'Wer kann eine Reise dauerhaft löschen', + 'perm.actionHint.trip_archive': + 'Wer kann eine Reise archivieren oder dearchivieren', + 'perm.actionHint.trip_cover_upload': + 'Wer kann das Titelbild hochladen oder ändern', + 'perm.actionHint.member_manage': + 'Wer kann Reise-Mitglieder einladen oder entfernen', + 'perm.actionHint.file_upload': 'Wer kann Dateien zu einer Reise hochladen', + 'perm.actionHint.file_edit': + 'Wer kann Dateibeschreibungen und Links bearbeiten', + 'perm.actionHint.file_delete': + 'Wer kann Dateien in den Papierkorb verschieben oder dauerhaft löschen', + 'perm.actionHint.place_edit': + 'Wer kann Orte hinzufügen, bearbeiten oder löschen', + 'perm.actionHint.day_edit': + 'Wer kann Tage, Tagesnotizen und Ort-Zuweisungen bearbeiten', + 'perm.actionHint.reservation_edit': + 'Wer kann Reservierungen erstellen, bearbeiten oder löschen', + 'perm.actionHint.budget_edit': + 'Wer kann Budgetposten erstellen, bearbeiten oder löschen', + 'perm.actionHint.packing_edit': 'Wer kann Packstücke und Taschen verwalten', + 'perm.actionHint.collab_edit': + 'Wer kann Notizen, Umfragen erstellen und Nachrichten senden', + 'perm.actionHint.share_manage': + 'Wer kann öffentliche Freigabelinks erstellen oder löschen', +}; +export default perm; diff --git a/shared/src/i18n/de/photos.ts b/shared/src/i18n/de/photos.ts new file mode 100644 index 00000000..ce667551 --- /dev/null +++ b/shared/src/i18n/de/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Fotos', + 'photos.subtitle': '{count} Fotos für {trip}', + 'photos.dropHere': 'Fotos hier ablegen...', + 'photos.dropHereActive': 'Fotos hier ablegen', + 'photos.captionForAll': 'Beschriftung (für alle)', + 'photos.captionPlaceholder': 'Optionale Beschriftung...', + 'photos.addCaption': 'Beschriftung hinzufügen...', + 'photos.allDays': 'Alle Tage', + 'photos.noPhotos': 'Noch keine Fotos', + 'photos.uploadHint': 'Lade deine Reisefotos hoch', + 'photos.clickToSelect': 'oder klicken zum Auswählen', + 'photos.linkPlace': 'Ort verknüpfen', + 'photos.noPlace': 'Kein Ort', + 'photos.uploadN': '{n} Foto(s) hochladen', + 'photos.linkDay': 'Tag verknüpfen', + 'photos.noDay': 'Kein Tag', + 'photos.dayLabel': 'Tag {number}', + 'photos.photoSelected': 'Foto ausgewählt', + 'photos.photosSelected': 'Fotos ausgewählt', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · bis zu 30 Fotos', +}; +export default photos; diff --git a/shared/src/i18n/de/places.ts b/shared/src/i18n/de/places.ts new file mode 100644 index 00000000..d67f9d3a --- /dev/null +++ b/shared/src/i18n/de/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Ort/Aktivität hinzufügen', + 'places.importFile': 'Dateimport', + 'places.sidebarDrop': 'Ablegen zum Importieren', + 'places.importFileHint': + '.gpx-, .kml- oder .kmz-Dateien aus Tools wie Google My Maps, Google Earth oder einem GPS-Tracker importieren.', + 'places.importFileDropHere': + 'Datei auswählen oder hierher ziehen und ablegen', + 'places.importFileDropActive': 'Datei ablegen zum Auswählen', + 'places.importFileUnsupported': + 'Nicht unterstützter Dateityp. Verwende .gpx, .kml oder .kmz.', + 'places.importFileTooLarge': + 'Datei ist zu groß. Maximale Upload-Größe ist {maxMb} MB.', + 'places.importFileError': 'Import fehlgeschlagen', + 'places.importAllSkipped': 'Alle Orte waren bereits in der Reise.', + 'places.gpxImported': '{count} Orte aus GPX importiert', + 'places.gpxImportTypes': 'Was soll importiert werden?', + 'places.gpxImportWaypoints': 'Wegpunkte', + 'places.gpxImportRoutes': 'Routen', + 'places.gpxImportTracks': 'Tracks (mit Streckenverlauf)', + 'places.gpxImportNoneSelected': 'Wähle mindestens einen Typ zum Importieren.', + 'places.kmlImportTypes': 'Was möchtest du importieren?', + 'places.kmlImportPoints': 'Punkte (Placemarks)', + 'places.kmlImportPaths': 'Pfade (LineStrings)', + 'places.kmlImportNoneSelected': 'Wähle mindestens einen Typ aus.', + 'places.selectionCount': '{count} ausgewählt', + 'places.deleteSelected': 'Auswahl löschen', + 'places.kmlKmzImported': '{count} Orte aus KMZ/KML importiert', + 'places.urlResolved': 'Ort aus URL importiert', + 'places.importList': 'Listenimport', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importiert: {created} • Übersprungen: {skipped}', + 'places.importGoogleList': 'Google Liste', + 'places.importNaverList': 'Naver Liste', + 'places.googleListHint': + 'Geteilten Google Maps Listen-Link einfügen, um alle Orte zu importieren.', + 'places.googleListImported': '{count} Orte aus "{list}" importiert', + 'places.googleListError': 'Google Maps Liste konnte nicht importiert werden', + 'places.naverListHint': + 'Geteilten Naver Maps Listen-Link einfügen, um alle Orte zu importieren.', + 'places.naverListImported': '{count} Orte aus "{list}" importiert', + 'places.naverListError': 'Naver Maps Liste konnte nicht importiert werden', + 'places.viewDetails': 'Details anzeigen', + 'places.assignToDay': 'Zu welchem Tag hinzufügen?', + 'places.all': 'Alle', + 'places.unplanned': 'Ungeplant', + 'places.filterTracks': 'Tracks', + 'places.search': 'Orte suchen...', + 'places.allCategories': 'Alle Kategorien', + 'places.categoriesSelected': 'Kategorien', + 'places.clearFilter': 'Filter zurücksetzen', + 'places.count': '{count} Orte', + 'places.countSingular': '1 Ort', + 'places.allPlanned': 'Alle Orte sind eingeplant', + 'places.noneFound': 'Keine Orte gefunden', + 'places.editPlace': 'Ort bearbeiten', + 'places.formName': 'Name', + 'places.formNamePlaceholder': 'z.B. Eiffelturm', + 'places.formDescription': 'Beschreibung', + 'places.formDescriptionPlaceholder': 'Kurze Beschreibung...', + 'places.formAddress': 'Adresse', + 'places.formAddressPlaceholder': 'Straße, Stadt, Land', + 'places.formLat': 'Breitengrad (z.B. 48.8566)', + 'places.formLng': 'Längengrad (z.B. 2.3522)', + 'places.formCategory': 'Kategorie', + 'places.noCategory': 'Keine Kategorie', + 'places.categoryNamePlaceholder': 'Kategoriename', + 'places.formTime': 'Uhrzeit', + 'places.startTime': 'Startzeit', + 'places.endTime': 'Ende', + 'places.endTimeBeforeStart': 'Endzeit liegt vor der Startzeit', + 'places.timeCollision': 'Zeitliche Überschneidung mit:', + 'places.formWebsite': 'Website', + 'places.formNotes': 'Notizen', + 'places.formNotesPlaceholder': 'Persönliche Notizen...', + 'places.formReservation': 'Reservierung', + 'places.reservationNotesPlaceholder': + 'Reservierungsnotizen, Bestätigungsnummer...', + 'places.mapsSearchPlaceholder': 'Ortssuche...', + 'places.mapsSearchError': 'Ortssuche fehlgeschlagen.', + 'places.loadingDetails': 'Ortsdetails werden geladen…', + 'places.osmHint': + 'OpenStreetMap-Suche aktiv (ohne Bilder, Öffnungszeiten, Bewertungen). Für erweiterte Daten Google API Key in den Einstellungen hinterlegen.', + 'places.osmActive': + 'Suche via OpenStreetMap (ohne Bilder, Bewertungen & Öffnungszeiten). Google API Key in den Einstellungen hinterlegen für erweiterte Daten.', + 'places.categoryCreateError': 'Fehler beim Erstellen der Kategorie', + 'places.nameRequired': 'Bitte einen Namen eingeben', + 'places.saveError': 'Fehler beim Speichern', +}; +export default places; diff --git a/shared/src/i18n/de/planner.ts b/shared/src/i18n/de/planner.ts new file mode 100644 index 00000000..7217c526 --- /dev/null +++ b/shared/src/i18n/de/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Orte', + 'planner.bookings': 'Buchungen', + 'planner.packingList': 'Packliste', + 'planner.documents': 'Dokumente', + 'planner.dayPlan': 'Tagesplan', + 'planner.reservations': 'Reservierungen', + 'planner.minTwoPlaces': 'Mindestens 2 Orte mit Koordinaten benötigt', + 'planner.noGeoPlaces': 'Keine Orte mit Koordinaten vorhanden', + 'planner.routeCalculated': 'Route berechnet', + 'planner.routeCalcFailed': 'Route konnte nicht berechnet werden', + 'planner.routeError': 'Fehler bei der Routenberechnung', + 'planner.icsExportFailed': 'ICS-Export fehlgeschlagen', + 'planner.routeOptimized': 'Route optimiert', + 'planner.reservationUpdated': 'Reservierung aktualisiert', + 'planner.reservationAdded': 'Reservierung hinzugefügt', + 'planner.confirmDeleteReservation': 'Reservierung löschen?', + 'planner.reservationDeleted': 'Reservierung gelöscht', + 'planner.days': 'Tage', + 'planner.allPlaces': 'Alle Orte', + 'planner.totalPlaces': '{n} Orte gesamt', + 'planner.noDaysPlanned': 'Noch keine Tage geplant', + 'planner.editTrip': 'Reise bearbeiten →', + 'planner.placeOne': '1 Ort', + 'planner.placeN': '{n} Orte', + 'planner.addNote': 'Notiz hinzufügen', + 'planner.noEntries': 'Keine Einträge für diesen Tag', + 'planner.addPlace': 'Ort/Aktivität hinzufügen', + 'planner.addPlaceShort': '+ Ort/Aktivität hinzufügen', + 'planner.resPending': 'Reservierung ausstehend · ', + 'planner.resConfirmed': 'Reservierung bestätigt · ', + 'planner.notePlaceholder': 'Notiz…', + 'planner.noteTimePlaceholder': 'Zeit (optional)', + 'planner.noteExamplePlaceholder': + 'z.B. S3 um 14:30 ab Hauptbahnhof, Fähre ab Pier 7, Mittagspause…', + 'planner.totalCost': 'Gesamtkosten', + 'planner.searchPlaces': 'Orte suchen…', + 'planner.allCategories': 'Alle Kategorien', + 'planner.noPlacesFound': 'Keine Orte gefunden', + 'planner.addFirstPlace': 'Ersten Ort hinzufügen', + 'planner.noReservations': 'Keine Reservierungen', + 'planner.addFirstReservation': 'Erste Reservierung hinzufügen', + 'planner.new': 'Neu', + 'planner.addToDay': '+ Tag', + 'planner.calculating': 'Berechne…', + 'planner.route': 'Route', + 'planner.optimize': 'Optimieren', + 'planner.openGoogleMaps': 'In Google Maps öffnen', + 'planner.selectDayHint': + 'Wähle einen Tag aus der linken Liste um den Tagesplan zu sehen', + 'planner.noPlacesForDay': 'Noch keine Orte für diesen Tag', + 'planner.addPlacesLink': 'Orte hinzufügen →', + 'planner.minTotal': 'Min. gesamt', + 'planner.noReservation': 'Keine Reservierung', + 'planner.removeFromDay': 'Aus Tag entfernen', + 'planner.addToThisDay': 'Zum Tag hinzufügen', + 'planner.overview': 'Gesamtübersicht', + 'planner.noDays': 'Noch keine Tage', + 'planner.editTripToAddDays': 'Reise bearbeiten um Tage hinzuzufügen', + 'planner.dayCount': '{n} Tage', + 'planner.clickToUnlock': 'Klicken zum Entsperren', + 'planner.keepPosition': 'Position bei Routenoptimierung beibehalten', + 'planner.dayDetails': 'Tagesdetails', + 'planner.dayN': 'Tag {n}', +}; +export default planner; diff --git a/shared/src/i18n/de/register.ts b/shared/src/i18n/de/register.ts new file mode 100644 index 00000000..1db6b61d --- /dev/null +++ b/shared/src/i18n/de/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Passwörter stimmen nicht überein', + 'register.passwordTooShort': 'Passwort muss mindestens 8 Zeichen lang sein', + 'register.failed': 'Registrierung fehlgeschlagen', + 'register.getStarted': 'Jetzt starten', + 'register.subtitle': + 'Erstellen Sie ein Konto und beginnen Sie, Ihre Traumreisen zu planen.', + 'register.feature1': 'Unbegrenzte Reisepläne', + 'register.feature2': 'Interaktive Kartenansicht', + 'register.feature3': 'Orte und Kategorien verwalten', + 'register.feature4': 'Reservierungen tracken', + 'register.feature5': 'Packlisten erstellen', + 'register.feature6': 'Fotos und Dateien speichern', + 'register.createAccount': 'Konto erstellen', + 'register.startPlanning': 'Beginnen Sie Ihre Reiseplanung', + 'register.minChars': 'Mind. 6 Zeichen', + 'register.confirmPassword': 'Passwort bestätigen', + 'register.repeatPassword': 'Passwort wiederholen', + 'register.registering': 'Registrieren...', + 'register.register': 'Registrieren', + 'register.hasAccount': 'Bereits ein Konto?', + 'register.signIn': 'Anmelden', +}; +export default register; diff --git a/shared/src/i18n/de/reservations.ts b/shared/src/i18n/de/reservations.ts new file mode 100644 index 00000000..4ef1fcb3 --- /dev/null +++ b/shared/src/i18n/de/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Buchungen', + 'reservations.empty': 'Keine Reservierungen vorhanden', + 'reservations.emptyHint': + 'Füge Reservierungen für Flüge, Hotels und mehr hinzu', + 'reservations.add': 'Reservierung hinzufügen', + 'reservations.addManual': 'Manuelle Buchung', + 'reservations.placeHint': + 'Tipp: Buchungen werden am besten direkt über einen angelegten Ort erstellt, um sie mit dem Tagesplan zu verknüpfen.', + 'reservations.confirmed': 'Bestätigt', + 'reservations.pending': 'Ausstehend', + 'reservations.summary': '{confirmed} bestätigt, {pending} ausstehend', + 'reservations.fromPlan': 'Aus Planung', + 'reservations.showFiles': 'Dateien anzeigen', + 'reservations.editTitle': 'Reservierung bearbeiten', + 'reservations.status': 'Status', + 'reservations.datetime': 'Datum & Uhrzeit', + 'reservations.startTime': 'Startzeit', + 'reservations.endTime': 'Endzeit', + 'reservations.date': 'Datum', + 'reservations.time': 'Uhrzeit', + 'reservations.timeAlt': 'Uhrzeit (alternativ, z.B. 19:30)', + 'reservations.notes': 'Notizen', + 'reservations.notesPlaceholder': 'Zusätzliche Notizen...', + 'reservations.meta.airline': 'Fluggesellschaft', + 'reservations.meta.flightNumber': 'Flugnr.', + 'reservations.meta.from': 'Von', + 'reservations.meta.to': 'Nach', + 'reservations.needsReview': 'Prüfen', + 'reservations.needsReviewHint': + 'Flughafen konnte nicht automatisch erkannt werden — bitte Ort bestätigen.', + 'reservations.searchLocation': 'Bahnhof, Hafen, Adresse suchen…', + 'reservations.meta.trainNumber': 'Zugnr.', + 'reservations.meta.platform': 'Gleis', + 'reservations.meta.seat': 'Sitzplatz', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in bis', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Unterkunft', + 'reservations.meta.pickAccommodation': 'Mit Unterkunft verknüpfen', + 'reservations.meta.noAccommodation': 'Keine', + 'reservations.meta.hotelPlace': 'Unterkunft', + 'reservations.meta.pickHotel': 'Unterkunft auswählen', + 'reservations.meta.fromDay': 'Von', + 'reservations.meta.toDay': 'Bis', + 'reservations.meta.selectDay': 'Tag wählen', + 'reservations.type.flight': 'Flug', + 'reservations.type.hotel': 'Unterkunft', + 'reservations.type.restaurant': 'Restaurant', + 'reservations.type.train': 'Zug', + 'reservations.type.car': 'Auto', + 'reservations.type.cruise': 'Kreuzfahrt', + 'reservations.type.event': 'Veranstaltung', + 'reservations.type.tour': 'Tour', + 'reservations.type.other': 'Sonstiges', + 'reservations.confirm.delete': + 'Möchtest du die Reservierung "{name}" wirklich löschen?', + 'reservations.confirm.deleteTitle': 'Buchung löschen?', + 'reservations.confirm.deleteBody': '"{name}" wird unwiderruflich gelöscht.', + 'reservations.toast.updated': 'Reservierung aktualisiert', + 'reservations.toast.removed': 'Reservierung gelöscht', + 'reservations.toast.saveError': 'Fehler beim Speichern', + 'reservations.toast.updateError': 'Fehler beim Aktualisieren', + 'reservations.toast.deleteError': 'Fehler beim Löschen', + 'reservations.confirm.remove': 'Reservierung für "{name}" entfernen?', + 'reservations.toast.fileUploaded': 'Datei hochgeladen', + 'reservations.toast.uploadError': 'Fehler beim Hochladen', + 'reservations.newTitle': 'Neue Buchung', + 'reservations.bookingType': 'Art der Buchung', + 'reservations.titleLabel': 'Titel', + 'reservations.titlePlaceholder': 'z.B. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Ort / Adresse', + 'reservations.locationPlaceholder': 'Adresse, Flughafen, Hotel...', + 'reservations.confirmationCode': 'Buchungscode', + 'reservations.confirmationPlaceholder': 'z.B. ABC12345', + 'reservations.day': 'Tag', + 'reservations.noDay': 'Kein Tag', + 'reservations.place': 'Ort', + 'reservations.noPlace': 'Kein Ort', + 'reservations.pendingSave': 'wird gespeichert…', + 'reservations.uploading': 'Wird hochgeladen...', + 'reservations.attachFile': 'Datei anhängen', + 'reservations.linkExisting': 'Vorhandene verknüpfen', + 'reservations.linkAssignment': 'Mit Tagesplanung verknüpfen', + 'reservations.pickAssignment': 'Zuordnung aus dem Plan wählen...', + 'reservations.noAssignment': 'Keine Verknüpfung', + 'reservations.price': 'Preis', + 'reservations.budgetCategory': 'Budgetkategorie', + 'reservations.budgetCategoryPlaceholder': 'z.B. Transport, Unterkunft', + 'reservations.budgetCategoryAuto': 'Auto (aus Buchungstyp)', + 'reservations.budgetHint': + 'Beim Speichern wird automatisch ein Budgeteintrag erstellt.', + 'reservations.departureDate': 'Abflug', + 'reservations.arrivalDate': 'Ankunft', + 'reservations.departureTime': 'Abflugzeit', + 'reservations.arrivalTime': 'Ankunftszeit', + 'reservations.pickupDate': 'Abholung', + 'reservations.returnDate': 'Rückgabe', + 'reservations.pickupTime': 'Abholzeit', + 'reservations.returnTime': 'Rückgabezeit', + 'reservations.endDate': 'Enddatum', + 'reservations.meta.departureTimezone': 'Abfl. TZ', + 'reservations.meta.arrivalTimezone': 'Ank. TZ', + 'reservations.span.departure': 'Abflug', + 'reservations.span.arrival': 'Ankunft', + 'reservations.span.inTransit': 'Unterwegs', + 'reservations.span.pickup': 'Abholung', + 'reservations.span.return': 'Rückgabe', + 'reservations.span.active': 'Aktiv', + 'reservations.span.start': 'Start', + 'reservations.span.end': 'Ende', + 'reservations.span.ongoing': 'Laufend', + 'reservations.validation.endBeforeStart': + 'Enddatum/-zeit muss nach dem Startdatum/-zeit liegen', + 'reservations.addBooking': 'Buchung hinzufügen', +}; +export default reservations; diff --git a/shared/src/i18n/de/settings.ts b/shared/src/i18n/de/settings.ts new file mode 100644 index 00000000..e62d9da7 --- /dev/null +++ b/shared/src/i18n/de/settings.ts @@ -0,0 +1,302 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Einstellungen', + 'settings.subtitle': 'Konfigurieren Sie Ihre persönlichen Einstellungen', + 'settings.tabs.display': 'Anzeige', + 'settings.tabs.map': 'Karte', + 'settings.tabs.notifications': 'Mitteilungen', + 'settings.tabs.integrations': 'Integrationen', + 'settings.tabs.account': 'Konto', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Über', + 'settings.map': 'Karte', + 'settings.mapTemplate': 'Karten-Vorlage', + 'settings.mapTemplatePlaceholder.select': 'Vorlage auswählen...', + 'settings.mapDefaultHint': 'Leer lassen für OpenStreetMap (Standard)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL-Template für die Kartenkacheln', + 'settings.mapProvider': 'Kartenanbieter', + 'settings.mapProviderHint': + 'Gilt für Trip Planner und Journey. Atlas nutzt immer Leaflet.', + 'settings.mapLeafletSubtitle': 'Klassisch 2D, beliebige Raster-Kacheln', + 'settings.mapMapboxSubtitle': 'Vektor-Kacheln, 3D-Gebäude & Terrain', + 'settings.mapExperimental': 'Experimentell', + 'settings.mapMapboxToken': 'Mapbox Access Token', + 'settings.mapMapboxTokenHint': 'Öffentliches Token (pk.*) von', + 'settings.mapMapboxTokenLink': 'mapbox.com → Access Tokens', + 'settings.mapStyle': 'Kartenstil', + 'settings.mapStylePlaceholder': 'Mapbox-Stil wählen', + 'settings.mapStyleHint': 'Preset oder eigene mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D-Gebäude & Terrain', + 'settings.map3dHint': + 'Neigung + echte 3D-Gebäude-Extrusionen — funktioniert mit jedem Stil, auch Satellit.', + 'settings.mapHighQuality': 'Hochqualitäts-Modus', + 'settings.mapHighQualityHint': + 'Antialiasing + Globus-Projektion für schärfere Kanten und eine realistische Weltsicht.', + 'settings.mapHighQualityWarning': + 'Kann die Performance auf schwächeren Geräten beeinträchtigen.', + 'settings.mapTipLabel': 'Tipp:', + 'settings.mapTip': + 'Rechtsklick und ziehen, um die Karte zu drehen/neigen. Mittelklick, um einen Ort hinzuzufügen (Rechtsklick ist für die Rotation reserviert).', + 'settings.latitude': 'Breitengrad', + 'settings.longitude': 'Längengrad', + 'settings.saveMap': 'Karte speichern', + 'settings.apiKeys': 'API-Schlüssel', + 'settings.mapsKey': 'Google Maps API-Schlüssel', + 'settings.mapsKeyHint': + 'Für Ortsuche. Benötigt Places API (New). Erhalten unter console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API-Schlüssel', + 'settings.weatherKeyHint': + 'Für Wetterdaten. Kostenlos unter openweathermap.org/api', + 'settings.keyPlaceholder': 'Schlüssel eingeben...', + 'settings.configured': 'Konfiguriert', + 'settings.saveKeys': 'Schlüssel speichern', + 'settings.display': 'Darstellung', + 'settings.colorMode': 'Farbmodus', + 'settings.light': 'Hell', + 'settings.dark': 'Dunkel', + 'settings.auto': 'Automatisch', + 'settings.language': 'Sprache', + 'settings.temperature': 'Temperatureinheit', + 'settings.timeFormat': 'Zeitformat', + 'settings.bookingLabels': 'Orts-Labels auf Buchungsrouten', + 'settings.bookingLabelsHint': + 'Zeigt Bahnhofs-/Flughafennamen auf der Karte. Wenn aus, wird nur das Icon angezeigt.', + 'settings.blurBookingCodes': 'Buchungscodes verbergen', + 'settings.notifications': 'Mitteilungen', + 'settings.notifyTripInvite': 'Trip-Einladungen', + 'settings.notifyBookingChange': 'Buchungsänderungen', + 'settings.notifyTripReminder': 'Trip-Erinnerungen', + 'settings.notifyTodoDue': 'Aufgabe bald fällig', + 'settings.notifyVacayInvite': 'Vacay Fusion-Einladungen', + 'settings.notifyPhotosShared': 'Geteilte Fotos (Immich)', + 'settings.notifyCollabMessage': 'Chat-Nachrichten (Collab)', + 'settings.notifyPackingTagged': 'Packliste: Zuweisungen', + 'settings.notifyWebhook': 'Webhook-Benachrichtigungen', + 'settings.notificationsDisabled': + 'Benachrichtigungen sind nicht konfiguriert. Bitten Sie einen Administrator, E-Mail- oder Webhook-Benachrichtungen zu aktivieren.', + 'settings.notificationsActive': 'Aktiver Kanal', + 'settings.notificationsManagedByAdmin': + 'Benachrichtigungsereignisse werden vom Administrator konfiguriert.', + 'settings.on': 'An', + 'settings.off': 'Aus', + 'settings.mcp.title': 'MCP-Konfiguration', + 'settings.mcp.endpoint': 'MCP-Endpunkt', + 'settings.mcp.clientConfig': 'Client-Konfiguration', + 'settings.mcp.clientConfigHint': + 'Ersetze durch ein API-Token aus der Liste unten. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Ersetze und durch die Zugangsdaten des oben erstellten OAuth 2.1-Clients. mcp-remote öffnet beim ersten Verbindungsaufbau deinen Browser zur Autorisierung. Der Pfad zu npx muss ggf. für dein System angepasst werden (z. B. C:\\PROGRA~1\\nodejs\\npx.cmd unter Windows).', + 'settings.mcp.copy': 'Kopieren', + 'settings.mcp.copied': 'Kopiert!', + 'settings.mcp.apiTokens': 'API-Tokens', + 'settings.mcp.createToken': 'Neuen Token erstellen', + 'settings.mcp.noTokens': + 'Noch keine Tokens. Erstelle einen, um MCP-Clients zu verbinden.', + 'settings.mcp.tokenCreatedAt': 'Erstellt', + 'settings.mcp.tokenUsedAt': 'Verwendet', + 'settings.mcp.deleteTokenTitle': 'Token löschen', + 'settings.mcp.deleteTokenMessage': + 'Dieser Token wird sofort ungültig. Jeder MCP-Client, der ihn verwendet, verliert den Zugang.', + 'settings.mcp.modal.createTitle': 'API-Token erstellen', + 'settings.mcp.modal.tokenName': 'Token-Name', + 'settings.mcp.modal.tokenNamePlaceholder': + 'z. B. Claude Desktop, Arbeits-Laptop', + 'settings.mcp.modal.creating': 'Wird erstellt…', + 'settings.mcp.modal.create': 'Token erstellen', + 'settings.mcp.modal.createdTitle': 'Token erstellt', + 'settings.mcp.modal.createdWarning': + 'Dieser Token wird nur einmal angezeigt. Kopiere und speichere ihn jetzt — er kann nicht wiederhergestellt werden.', + 'settings.mcp.modal.done': 'Fertig', + 'settings.mcp.toast.created': 'Token erstellt', + 'settings.mcp.toast.createError': 'Token konnte nicht erstellt werden', + 'settings.mcp.toast.deleted': 'Token gelöscht', + 'settings.mcp.toast.deleteError': 'Token konnte nicht gelöscht werden', + 'settings.mcp.apiTokensDeprecated': + 'API-Tokens sind veraltet und werden in einer zukünftigen Version entfernt. Bitte verwende stattdessen OAuth 2.1-Clients.', + 'settings.oauth.clients': 'OAuth 2.1-Clients', + 'settings.oauth.clientsHint': + 'Registriere OAuth 2.1-Clients, damit externe MCP-Anwendungen (Claude Web, Cursor usw.) sich ohne statische Tokens verbinden können.', + 'settings.oauth.createClient': 'Neuer Client', + 'settings.oauth.noClients': 'Keine OAuth-Clients registriert.', + 'settings.oauth.clientId': 'Client-ID', + 'settings.oauth.clientSecret': 'Client-Secret', + 'settings.oauth.deleteClient': 'Client löschen', + 'settings.oauth.deleteClientMessage': + 'Dieser Client und alle aktiven Sessions werden dauerhaft entfernt. Jede Anwendung, die ihn nutzt, verliert sofort den Zugriff.', + 'settings.oauth.rotateSecret': 'Secret erneuern', + 'settings.oauth.rotateSecretMessage': + 'Ein neues Client-Secret wird generiert und alle bestehenden Sessions werden sofort ungültig. Aktualisiere deine Anwendung, bevor du diesen Dialog schließt.', + 'settings.oauth.rotateSecretConfirm': 'Erneuern', + 'settings.oauth.rotateSecretConfirming': 'Wird erneuert…', + 'settings.oauth.rotateSecretDoneTitle': 'Neues Secret generiert', + 'settings.oauth.rotateSecretDoneWarning': + 'Dieses Secret wird nur einmal angezeigt. Kopiere es jetzt und aktualisiere deine Anwendung — alle vorherigen Sessions wurden ungültig gemacht.', + 'settings.oauth.activeSessions': 'Aktive OAuth-Sessions', + 'settings.oauth.sessionScopes': 'Berechtigungen', + 'settings.oauth.sessionExpires': 'Läuft ab', + 'settings.oauth.revoke': 'Widerrufen', + 'settings.oauth.revokeSession': 'Session widerrufen', + 'settings.oauth.revokeSessionMessage': + 'Dadurch wird der Zugriff für diese OAuth-Session sofort widerrufen.', + 'settings.oauth.modal.createTitle': 'OAuth-Client registrieren', + 'settings.oauth.modal.presets': 'Schnellvorlagen', + 'settings.oauth.modal.clientName': 'Anwendungsname', + 'settings.oauth.modal.clientNamePlaceholder': + 'z. B. Claude Web, Meine MCP-App', + 'settings.oauth.modal.redirectUris': 'Redirect-URIs', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Eine URI pro Zeile. HTTPS erforderlich (localhost ausgenommen). Exakte Übereinstimmung erforderlich.', + 'settings.oauth.modal.scopes': 'Erlaubte Berechtigungen', + 'settings.oauth.modal.scopesHint': + 'list_trips und get_trip_summary sind immer verfügbar — keine Berechtigung nötig. Sie helfen der KI, Trip-IDs zu ermitteln.', + 'settings.oauth.modal.selectAll': 'Alle auswählen', + 'settings.oauth.modal.deselectAll': 'Alle abwählen', + 'settings.oauth.modal.creating': 'Wird registriert…', + 'settings.oauth.modal.create': 'Client registrieren', + 'settings.oauth.modal.createdTitle': 'Client registriert', + 'settings.oauth.modal.createdWarning': + 'Das Client-Secret wird nur einmal angezeigt. Kopiere es jetzt — es kann nicht wiederhergestellt werden.', + 'settings.oauth.toast.createError': + 'OAuth-Client konnte nicht registriert werden', + 'settings.oauth.toast.deleted': 'OAuth-Client gelöscht', + 'settings.oauth.toast.deleteError': + 'OAuth-Client konnte nicht gelöscht werden', + 'settings.oauth.toast.revoked': 'Session widerrufen', + 'settings.oauth.toast.revokeError': 'Session konnte nicht widerrufen werden', + 'settings.oauth.toast.rotateError': + 'Client-Secret konnte nicht erneuert werden', + 'settings.oauth.modal.machineClient': + 'Maschineller Client (kein Browser-Login)', + 'settings.oauth.modal.machineClientHint': + 'Verwendet den client_credentials Grant — keine Redirect-URIs erforderlich. Das Token wird direkt über client_id + client_secret ausgestellt und handelt in Ihrem Namen innerhalb der gewählten Scopes.', + 'settings.oauth.modal.machineClientUsage': + 'Token abrufen: POST /oauth/token mit grant_type=client_credentials, client_id und client_secret. Kein Browser, kein Refresh-Token.', + 'settings.oauth.badge.machine': 'Maschine', + 'settings.account': 'Konto', + 'settings.about': 'Über', + 'settings.about.reportBug': 'Bug melden', + 'settings.about.reportBugHint': 'Problem gefunden? Melde es uns', + 'settings.about.featureRequest': 'Feature vorschlagen', + 'settings.about.featureRequestHint': 'Schlage ein neues Feature vor', + 'settings.about.wikiHint': 'Dokumentation & Anleitungen', + 'settings.about.supporters.badge': 'Monatliche Unterstützer', + 'settings.about.supporters.title': 'Reisebegleitung für TREK', + 'settings.about.supporters.subtitle': + 'Während du deine nächste Route planst, planen diese Leute mit, wie TREK weitergeht. Ihr monatlicher Beitrag fließt direkt in Entwicklung und echten Zeitaufwand — damit TREK Open Source bleibt.', + 'settings.about.supporters.since': 'Unterstützer seit {date}', + 'settings.about.supporters.tierEmpty': 'Sei die/der Erste', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK ist ein selbst gehosteter Reiseplaner, der dir hilft, deine Trips von der ersten Idee bis zur letzten Erinnerung zu organisieren. Tagesplanung, Budget, Packlisten, Fotos und vieles mehr — alles an einem Ort, auf deinem eigenen Server.', + 'settings.about.madeWith': 'Entwickelt mit', + 'settings.about.madeBy': + 'von Maurice und einer wachsenden Open-Source-Community.', + 'settings.username': 'Benutzername', + 'settings.email': 'E-Mail', + 'settings.role': 'Rolle', + 'settings.roleAdmin': 'Administrator', + 'settings.oidcLinked': 'Verknüpft mit', + 'settings.changePassword': 'Passwort ändern', + 'settings.mustChangePassword': + 'Sie müssen Ihr Passwort ändern, bevor Sie fortfahren können. Bitte legen Sie unten ein neues Passwort fest.', + 'settings.currentPassword': 'Aktuelles Passwort', + 'settings.currentPasswordRequired': 'Aktuelles Passwort wird benötigt', + 'settings.newPassword': 'Neues Passwort', + 'settings.confirmPassword': 'Neues Passwort bestätigen', + 'settings.updatePassword': 'Passwort aktualisieren', + 'settings.passwordRequired': 'Bitte aktuelles und neues Passwort eingeben', + 'settings.passwordTooShort': 'Passwort muss mindestens 8 Zeichen lang sein', + 'settings.passwordMismatch': 'Passwörter stimmen nicht überein', + 'settings.passwordWeak': + 'Passwort muss Groß-, Kleinbuchstaben, eine Zahl und ein Sonderzeichen enthalten', + 'settings.passwordChanged': 'Passwort erfolgreich geändert', + 'settings.deleteAccount': 'Löschen', + 'settings.deleteAccountTitle': 'Account wirklich löschen?', + 'settings.deleteAccountWarning': + 'Dein Account und alle deine Reisen, Orte und Dateien werden unwiderruflich gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.', + 'settings.deleteAccountConfirm': 'Endgültig löschen', + 'settings.deleteBlockedTitle': 'Löschung nicht möglich', + 'settings.deleteBlockedMessage': + 'Du bist der einzige Administrator. Ernenne zuerst einen anderen Benutzer zum Admin, bevor du deinen Account löschen kannst.', + 'settings.roleUser': 'Benutzer', + 'settings.saveProfile': 'Speichern', + 'settings.toast.mapSaved': 'Karteneinstellungen gespeichert', + 'settings.toast.keysSaved': 'API-Schlüssel gespeichert', + 'settings.toast.displaySaved': 'Anzeigeeinstellungen gespeichert', + 'settings.toast.profileSaved': 'Profil aktualisiert', + 'settings.uploadAvatar': 'Profilbild hochladen', + 'settings.removeAvatar': 'Profilbild entfernen', + 'settings.avatarUploaded': 'Profilbild aktualisiert', + 'settings.avatarRemoved': 'Profilbild entfernt', + 'settings.avatarError': 'Fehler beim Hochladen', + 'settings.mfa.title': 'Zwei-Faktor-Authentifizierung (2FA)', + 'settings.mfa.description': + 'Zusätzlicher Schritt bei der Anmeldung mit E-Mail und Passwort. Nutze eine Authenticator-App (Google Authenticator, Authy, …).', + 'settings.mfa.requiredByPolicy': + 'Dein Administrator verlangt Zwei-Faktor-Authentifizierung. Richte unten eine Authenticator-App ein, bevor du fortfährst.', + 'settings.mfa.backupTitle': 'Backup-Codes', + 'settings.mfa.backupDescription': + 'Verwende diese Einmal-Codes, wenn du keinen Zugriff mehr auf deine Authenticator-App hast.', + 'settings.mfa.backupWarning': + 'Jetzt speichern. Jeder Code kann nur einmal verwendet werden.', + 'settings.mfa.backupCopy': 'Codes kopieren', + 'settings.mfa.backupDownload': 'TXT herunterladen', + 'settings.mfa.backupPrint': 'Drucken / PDF', + 'settings.mfa.backupCopied': 'Backup-Codes kopiert', + 'settings.mfa.enabled': '2FA ist für dein Konto aktiv.', + 'settings.mfa.disabled': '2FA ist nicht aktiviert.', + 'settings.mfa.setup': 'Authenticator einrichten', + 'settings.mfa.scanQr': + 'QR-Code mit der App scannen oder den Schlüssel manuell eingeben.', + 'settings.mfa.secretLabel': 'Geheimer Schlüssel (manuell)', + 'settings.mfa.codePlaceholder': '6-stelliger Code', + 'settings.mfa.enable': '2FA aktivieren', + 'settings.mfa.cancelSetup': 'Abbrechen', + 'settings.mfa.disableTitle': '2FA deaktivieren', + 'settings.mfa.disableHint': + 'Passwort und einen aktuellen Code aus der Authenticator-App eingeben.', + 'settings.mfa.disable': '2FA deaktivieren', + 'settings.mfa.toastEnabled': 'Zwei-Faktor-Authentifizierung aktiviert', + 'settings.mfa.toastDisabled': 'Zwei-Faktor-Authentifizierung deaktiviert', + 'settings.mfa.demoBlocked': 'In der Demo nicht verfügbar', + 'settings.notifyVersionAvailable': 'Neue Version verfügbar', + 'settings.notificationPreferences.noChannels': + 'Keine Benachrichtigungskanäle konfiguriert. Bitte einen Administrator, E-Mail- oder Webhook-Benachrichtigungen einzurichten.', + 'settings.webhookUrl.label': 'Webhook-URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Gib deine Discord-, Slack- oder benutzerdefinierte Webhook-URL ein, um Benachrichtigungen zu erhalten.', + 'settings.webhookUrl.saved': 'Webhook-URL gespeichert', + 'settings.webhookUrl.test': 'Testen', + 'settings.webhookUrl.testSuccess': 'Test-Webhook erfolgreich gesendet', + 'settings.webhookUrl.testFailed': 'Test-Webhook fehlgeschlagen', + 'settings.ntfyUrl.topicLabel': 'Ntfy-Thema', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy-Server-URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Gib dein Ntfy-Thema ein, um Push-Benachrichtigungen zu erhalten. Lasse das Server-Feld leer, um den vom Administrator konfigurierten Standard zu verwenden.', + 'settings.ntfyUrl.tokenLabel': 'Zugriffstoken (optional)', + 'settings.ntfyUrl.tokenHint': 'Erforderlich für passwortgeschützte Themen.', + 'settings.ntfyUrl.saved': 'Ntfy-Einstellungen gespeichert', + 'settings.ntfyUrl.test': 'Testen', + 'settings.ntfyUrl.testSuccess': + 'Test-Ntfy-Benachrichtigung erfolgreich gesendet', + 'settings.ntfyUrl.testFailed': 'Test-Ntfy-Benachrichtigung fehlgeschlagen', + 'settings.ntfyUrl.tokenCleared': 'Zugriffstoken gelöscht', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/de/share.ts b/shared/src/i18n/de/share.ts new file mode 100644 index 00000000..82d9eec6 --- /dev/null +++ b/shared/src/i18n/de/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Öffentlicher Link', + 'share.linkHint': + 'Erstelle einen Link den jeder ohne Login nutzen kann, um diese Reise anzuschauen. Nur lesen — keine Bearbeitung möglich.', + 'share.createLink': 'Link erstellen', + 'share.deleteLink': 'Link löschen', + 'share.createError': 'Link konnte nicht erstellt werden', + 'share.permMap': 'Karte & Plan', + 'share.permBookings': 'Buchungen', + 'share.permPacking': 'Packliste', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/de/shared.ts b/shared/src/i18n/de/shared.ts new file mode 100644 index 00000000..f4093b87 --- /dev/null +++ b/shared/src/i18n/de/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link abgelaufen oder ungültig', + 'shared.expiredHint': 'Dieser geteilte Reise-Link ist nicht mehr aktiv.', + 'shared.readOnly': 'Nur-Lesen Ansicht', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Buchungen', + 'shared.tabPacking': 'Packliste', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'Tage', + 'shared.places': 'Orte', + 'shared.other': 'Sonstige', + 'shared.totalBudget': 'Gesamtbudget', + 'shared.messages': 'Nachrichten', + 'shared.sharedVia': 'Geteilt über', + 'shared.confirmed': 'Bestätigt', + 'shared.pending': 'Ausstehend', +}; +export default shared; diff --git a/shared/src/i18n/de/stats.ts b/shared/src/i18n/de/stats.ts new file mode 100644 index 00000000..53ff4c77 --- /dev/null +++ b/shared/src/i18n/de/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Länder', + 'stats.cities': 'Städte', + 'stats.trips': 'Reisen', + 'stats.places': 'Orte', + 'stats.worldProgress': 'Weltfortschritt', + 'stats.visited': 'besucht', + 'stats.remaining': 'verbleibend', + 'stats.visitedCountries': 'Besuchte Länder', +}; +export default stats; diff --git a/shared/src/i18n/de/system_notice.ts b/shared/src/i18n/de/system_notice.ts new file mode 100644 index 00000000..2cff367b --- /dev/null +++ b/shared/src/i18n/de/system_notice.ts @@ -0,0 +1,64 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Willkommen bei TREK', + 'system_notice.welcome_v1.body': + 'Dein All-in-one-Reiseplaner. Erstelle Reisepläne, teile sie mit Freunden und bleib organisiert – online und offline.', + 'system_notice.welcome_v1.cta_label': 'Reise planen', + 'system_notice.welcome_v1.hero_alt': + 'Malerisches Reiseziel mit TREK-Planungs-UI', + 'system_notice.welcome_v1.highlight_plan': + 'Tagesweise Reisepläne für jede Reise', + 'system_notice.welcome_v1.highlight_share': + 'Gemeinsam mit Reisepartnern planen', + 'system_notice.welcome_v1.highlight_offline': + 'Funktioniert offline auf dem Handy', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Vorherige Meldung', + 'system_notice.pager.next': 'Nächste Meldung', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Zu Meldung {n}', + 'system_notice.pager.position': 'Meldung {current} von {total}', + 'system_notice.v3_photos.title': 'Fotos wurden in 3.0 verschoben', + 'system_notice.v3_photos.body': + '**Fotos** im Trip-Planer wurden entfernt. Deine Fotos sind sicher — TREK hat deine Immich- oder Synology-Bibliothek nie verändert.\n\nFotos befinden sich jetzt im **Journey**-Addon. Journey ist optional — falls es noch nicht verfügbar ist, bitte deinen Admin, es unter Admin → Addons zu aktivieren.', + 'system_notice.v3_journey.title': 'Neu: Journey — dein Reisetagebuch', + 'system_notice.v3_journey.body': + 'Dokumentiere deine Reisen als lebendige Geschichten mit Zeitachsen, Fotogalerien und interaktiven Karten.', + 'system_notice.v3_journey.cta_label': 'Journey öffnen', + 'system_notice.v3_journey.highlight_timeline': 'Zeitleiste und Galerie', + 'system_notice.v3_journey.highlight_photos': + 'Import von Immich oder Synology', + 'system_notice.v3_journey.highlight_share': + 'Öffentlich teilen — kein Login nötig', + 'system_notice.v3_journey.highlight_export': 'Als PDF-Fotobuch exportieren', + 'system_notice.v3_features.title': 'Weitere Highlights in 3.0', + 'system_notice.v3_features.body': + 'Ein paar weitere Neuerungen in diesem Release.', + 'system_notice.v3_features.highlight_dashboard': + 'Mobile-first Dashboard-Redesign', + 'system_notice.v3_features.highlight_offline': + 'Vollständiger Offline-Modus als PWA', + 'system_notice.v3_features.highlight_search': + 'Echtzeit-Autovervollständigung für Orte', + 'system_notice.v3_features.highlight_import': + 'Orte aus KMZ/KML-Dateien importieren', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1-Upgrade', + 'system_notice.v3_mcp.body': + 'Die MCP-Integration wurde vollständig überarbeitet. OAuth 2.1 ist jetzt die empfohlene Authentifizierungsmethode. Statische Tokens (trek_…) sind veraltet und werden in einer zukünftigen Version entfernt.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 empfohlen (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': + '24 feingranulare Berechtigungs-Scopes', + 'system_notice.v3_mcp.highlight_deprecated': + 'Statische trek_-Tokens veraltet', + 'system_notice.v3_mcp.highlight_tools': 'Erweitertes Toolset & Prompts', + 'system_notice.v3_thankyou.title': 'Ein persönliches Wort von mir', + 'system_notice.v3_thankyou.body': + 'Bevor du weiterklickst — einen Moment noch.\n\nTREK hat als Nebenprojekt für meine eigenen Reisen angefangen. Ich hätte nie gedacht, dass es jemals so weit kommt, dass 4.000 von euch damit ihre Abenteuer planen. Jeder Stern, jedes Issue, jeder Feature-Wunsch — ich lese sie alle, und sie halten mich am Laufen durch die späten Nächte zwischen Vollzeitjob und Studium.\n\nEins will ich euch sagen: TREK wird immer Open Source bleiben, immer self-hosted, immer eures. Kein Tracking, keine Abos, keine versteckten Haken. Einfach ein Tool, gebaut von jemandem, der das Reisen genauso liebt wie ihr.\n\nBesonderer Dank an [jubnl](https://github.com/jubnl) — du bist ein unglaublicher Mitstreiter geworden. So vieles, was 3.0 großartig macht, trägt deine Handschrift. Danke, dass du an dieses Projekt geglaubt hast, als es noch holprig war.\n\nUnd an jeden einzelnen von euch, der einen Bug gemeldet, einen String übersetzt, TREK mit Freunden geteilt oder einfach damit eine Reise geplant hat — **danke**. Ihr seid der Grund, warum es das hier gibt.\n\nAuf viele weitere Abenteuer zusammen.\n\n— Maurice\n\n---\n\n[Tritt der Community auf Discord bei](https://discord.gg/7Q6M6jDwzf)\n\nWenn TREK deine Reisen besser macht, hält ein [kleiner Kaffee](https://ko-fi.com/mauriceboe) die Lichter an.', + 'system_notice.v3014_whitespace_collision.title': + 'Aktion erforderlich: Benutzerkontokonflikt', + 'system_notice.v3014_whitespace_collision.body': + 'Das 3.0.14-Upgrade hat einen oder mehrere Konflikte bei Benutzernamen oder E-Mail-Adressen festgestellt, die durch führende oder nachgestellte Leerzeichen in gespeicherten Konten verursacht wurden. Betroffene Konten wurden automatisch umbenannt. Prüfe die Serverprotokolle auf Zeilen, die mit **[migration] WHITESPACE COLLISION** beginnen, um die betroffenen Konten zu identifizieren.', +}; +export default system_notice; diff --git a/shared/src/i18n/de/todo.ts b/shared/src/i18n/de/todo.ts new file mode 100644 index 00000000..53036a96 --- /dev/null +++ b/shared/src/i18n/de/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Packliste', + 'todo.subtab.todo': 'Aufgaben', + 'todo.completed': 'erledigt', + 'todo.filter.all': 'Alle', + 'todo.filter.open': 'Offen', + 'todo.filter.done': 'Erledigt', + 'todo.uncategorized': 'Ohne Kategorie', + 'todo.namePlaceholder': 'Aufgabenname', + 'todo.descriptionPlaceholder': 'Beschreibung (optional)', + 'todo.unassigned': 'Nicht zugewiesen', + 'todo.noCategory': 'Keine Kategorie', + 'todo.hasDescription': 'Hat Beschreibung', + 'todo.addItem': 'Neue Aufgabe hinzufügen', + 'todo.sidebar.sortBy': 'Sortieren nach', + 'todo.priority': 'Priorität', + 'todo.newCategoryLabel': 'neu', + 'todo.newCategory': 'Kategoriename', + 'todo.addCategory': 'Kategorie hinzufügen', + 'todo.newItem': 'Neue Aufgabe', + 'todo.empty': 'Noch keine Aufgaben. Erstelle eine Aufgabe um loszulegen!', + 'todo.filter.my': 'Meine Aufgaben', + 'todo.filter.overdue': 'Überfällig', + 'todo.sidebar.tasks': 'Aufgaben', + 'todo.sidebar.categories': 'Kategorien', + 'todo.detail.title': 'Aufgabe', + 'todo.detail.description': 'Beschreibung', + 'todo.detail.category': 'Kategorie', + 'todo.detail.dueDate': 'Fällig am', + 'todo.detail.assignedTo': 'Zuständig', + 'todo.detail.delete': 'Löschen', + 'todo.detail.save': 'Speichern', + 'todo.sortByPrio': 'Priorität', + 'todo.detail.priority': 'Priorität', + 'todo.detail.noPriority': 'Keine', + 'todo.detail.create': 'Aufgabe erstellen', +}; +export default todo; diff --git a/shared/src/i18n/de/transport.ts b/shared/src/i18n/de/transport.ts new file mode 100644 index 00000000..51d0ade3 --- /dev/null +++ b/shared/src/i18n/de/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Transport hinzufügen', + 'transport.modalTitle.create': 'Transport hinzufügen', + 'transport.modalTitle.edit': 'Transport bearbeiten', + 'transport.title': 'Transporte', + 'transport.addManual': 'Manuelles Transportmittel', +}; +export default transport; diff --git a/shared/src/i18n/de/trip.ts b/shared/src/i18n/de/trip.ts new file mode 100644 index 00000000..10373723 --- /dev/null +++ b/shared/src/i18n/de/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Karte', + 'trip.tabs.transports': 'Transport', + 'trip.tabs.reservations': 'Buchungen', + 'trip.tabs.reservationsShort': 'Buchung', + 'trip.tabs.packing': 'Liste', + 'trip.tabs.packingShort': 'Liste', + 'trip.tabs.lists': 'Listen', + 'trip.tabs.listsShort': 'Listen', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'Dateien', + 'trip.loading': 'Reise wird geladen...', + 'trip.loadingPhotos': 'Fotos der Orte werden geladen...', + 'trip.mobilePlan': 'Planung', + 'trip.mobilePlaces': 'Orte', + 'trip.toast.placeUpdated': 'Ort aktualisiert', + 'trip.toast.placeAdded': 'Ort hinzugefügt', + 'trip.toast.placeDeleted': 'Ort gelöscht', + 'trip.toast.selectDay': 'Bitte wähle zuerst einen Tag aus', + 'trip.toast.assignedToDay': 'Ort wurde dem Tag zugewiesen', + 'trip.toast.reorderError': 'Fehler beim Sortieren', + 'trip.toast.reservationUpdated': 'Reservierung aktualisiert', + 'trip.toast.reservationAdded': 'Reservierung hinzugefügt', + 'trip.toast.deleted': 'Gelöscht', + 'trip.confirm.deletePlace': 'Möchtest du diesen Ort wirklich löschen?', + 'trip.confirm.deletePlaces': '{count} Orte löschen?', + 'trip.toast.placesDeleted': '{count} Orte gelöscht', +}; +export default trip; diff --git a/shared/src/i18n/de/trips.ts b/shared/src/i18n/de/trips.ts new file mode 100644 index 00000000..2f7d454d --- /dev/null +++ b/shared/src/i18n/de/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.reminder': 'Erinnerung', + 'trips.reminderNone': 'Keine', + 'trips.reminderDay': 'Tag', + 'trips.reminderDays': 'Tage', + 'trips.reminderCustom': 'Benutzerdefiniert', + 'trips.memberRemoved': '{username} entfernt', + 'trips.memberRemoveError': 'Entfernen fehlgeschlagen', + 'trips.memberAdded': '{username} hinzugefügt', + 'trips.memberAddError': 'Hinzufügen fehlgeschlagen', + 'trips.reminderDaysBefore': 'Tage vor Abreise', + 'trips.reminderDisabledHint': + 'Reiseerinnerungen sind deaktiviert. Aktivieren Sie sie unter Admin > Einstellungen > Benachrichtigungen.', +}; +export default trips; diff --git a/shared/src/i18n/de/undo.ts b/shared/src/i18n/de/undo.ts new file mode 100644 index 00000000..bc165dc3 --- /dev/null +++ b/shared/src/i18n/de/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Rückgängig', + 'undo.tooltip': 'Rückgängig: {action}', + 'undo.assignPlace': 'Ort einem Tag zugewiesen', + 'undo.removeAssignment': 'Ort von Tag entfernt', + 'undo.reorder': 'Orte neu sortiert', + 'undo.optimize': 'Route optimiert', + 'undo.deletePlace': 'Ort gelöscht', + 'undo.deletePlaces': 'Orte gelöscht', + 'undo.moveDay': 'Ort zu anderem Tag verschoben', + 'undo.lock': 'Ortssperre umgeschaltet', + 'undo.importGpx': 'GPX-Import', + 'undo.importKeyholeMarkup': 'KMZ/KML-Import', + 'undo.importGoogleList': 'Google Maps-Import', + 'undo.importNaverList': 'Naver Maps-Import', + 'undo.addPlace': 'Ort hinzugefügt', + 'undo.done': 'Rückgängig gemacht: {action}', +}; +export default undo; diff --git a/shared/src/i18n/de/vacay.ts b/shared/src/i18n/de/vacay.ts new file mode 100644 index 00000000..c5a014fc --- /dev/null +++ b/shared/src/i18n/de/vacay.ts @@ -0,0 +1,105 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Urlaubstage planen und verwalten', + 'vacay.settings': 'Einstellungen', + 'vacay.year': 'Jahr', + 'vacay.addYear': 'Nächstes Jahr hinzufügen', + 'vacay.addPrevYear': 'Vorheriges Jahr hinzufügen', + 'vacay.removeYear': 'Jahr entfernen', + 'vacay.removeYearConfirm': '{year} entfernen?', + 'vacay.removeYearHint': + 'Alle Urlaubseinträge und Betriebsferien für dieses Jahr werden unwiderruflich gelöscht.', + 'vacay.remove': 'Entfernen', + 'vacay.persons': 'Personen', + 'vacay.noPersons': 'Keine Personen angelegt', + 'vacay.addPerson': 'Person hinzufügen', + 'vacay.editPerson': 'Person bearbeiten', + 'vacay.removePerson': 'Person entfernen', + 'vacay.removePersonConfirm': '{name} wirklich entfernen?', + 'vacay.removePersonHint': + 'Alle Urlaubseinträge dieser Person werden unwiderruflich gelöscht.', + 'vacay.personName': 'Name', + 'vacay.personNamePlaceholder': 'Name eingeben', + 'vacay.color': 'Farbe', + 'vacay.add': 'Hinzufügen', + 'vacay.legend': 'Legende', + 'vacay.publicHoliday': 'Feiertag', + 'vacay.companyHoliday': 'Betriebsferien', + 'vacay.weekend': 'Wochenende', + 'vacay.modeVacation': 'Urlaub', + 'vacay.modeCompany': 'Betriebsferien', + 'vacay.entitlement': 'Urlaubsanspruch', + 'vacay.entitlementDays': 'Tage', + 'vacay.used': 'Weg', + 'vacay.remaining': 'Rest', + 'vacay.carriedOver': 'aus {year}', + 'vacay.blockWeekends': 'Wochenenden sperren', + 'vacay.blockWeekendsHint': 'Verhindert Urlaubseinträge an Wochenendtagen', + 'vacay.weekendDays': 'Wochenendtage', + 'vacay.mon': 'Mo', + 'vacay.tue': 'Di', + 'vacay.wed': 'Mi', + 'vacay.thu': 'Do', + 'vacay.fri': 'Fr', + 'vacay.sat': 'Sa', + 'vacay.sun': 'So', + 'vacay.publicHolidays': 'Feiertage', + 'vacay.publicHolidaysHint': 'Feiertage im Kalender markieren', + 'vacay.selectCountry': 'Land wählen', + 'vacay.selectRegion': 'Region wählen (optional)', + 'vacay.addCalendar': 'Kalender hinzufügen', + 'vacay.calendarLabel': 'Bezeichnung (optional)', + 'vacay.calendarColor': 'Farbe', + 'vacay.noCalendars': 'Noch keine Feiertagskalender angelegt', + 'vacay.companyHolidays': 'Betriebsferien', + 'vacay.companyHolidaysHint': + 'Erlaubt das Markieren von unternehmensweiten Feiertagen', + 'vacay.companyHolidaysNoDeduct': + 'Betriebsferien werden nicht vom Urlaubskontingent abgezogen.', + 'vacay.weekStart': 'Woche beginnt am', + 'vacay.weekStartHint': + 'Wähle ob die Kalenderwoche am Montag oder Sonntag beginnt', + 'vacay.carryOver': 'Urlaubsmitnahme', + 'vacay.carryOverHint': 'Resturlaub automatisch ins Folgejahr übertragen', + 'vacay.sharing': 'Teilen', + 'vacay.sharingHint': 'Teile deinen Urlaubsplan mit anderen TREK-Benutzern', + 'vacay.owner': 'Besitzer', + 'vacay.shareEmailPlaceholder': 'E-Mail des TREK-Benutzers', + 'vacay.shareSuccess': 'Plan erfolgreich geteilt', + 'vacay.shareError': 'Plan konnte nicht geteilt werden', + 'vacay.dissolve': 'Fusion auflösen', + 'vacay.dissolveHint': + 'Kalender wieder trennen. Deine Einträge bleiben erhalten.', + 'vacay.dissolveAction': 'Auflösen', + 'vacay.dissolved': 'Kalender getrennt', + 'vacay.fusedWith': 'Fusioniert mit', + 'vacay.you': 'du', + 'vacay.noData': 'Keine Daten', + 'vacay.changeColor': 'Farbe ändern', + 'vacay.inviteUser': 'Benutzer einladen', + 'vacay.inviteHint': + 'Lade einen anderen TREK-Benutzer ein, um einen gemeinsamen Urlaubskalender zu teilen.', + 'vacay.selectUser': 'Benutzer wählen', + 'vacay.sendInvite': 'Einladung senden', + 'vacay.inviteSent': 'Einladung gesendet', + 'vacay.inviteError': 'Einladung konnte nicht gesendet werden', + 'vacay.pending': 'ausstehend', + 'vacay.noUsersAvailable': 'Keine Benutzer verfügbar', + 'vacay.accept': 'Annehmen', + 'vacay.decline': 'Ablehnen', + 'vacay.acceptFusion': 'Annehmen & Fusionieren', + 'vacay.inviteTitle': 'Fusionsanfrage', + 'vacay.inviteWantsToFuse': 'möchte einen Urlaubskalender mit dir teilen.', + 'vacay.fuseInfo1': + 'Beide sehen alle Urlaubseinträge in einem gemeinsamen Kalender.', + 'vacay.fuseInfo2': + 'Beide können Einträge für den jeweils anderen erstellen und bearbeiten.', + 'vacay.fuseInfo3': + 'Beide können Einträge löschen und den Urlaubsanspruch ändern.', + 'vacay.fuseInfo4': + 'Einstellungen wie Feiertage und Betriebsferien werden geteilt.', + 'vacay.fuseInfo5': + 'Die Fusion kann jederzeit von beiden Seiten aufgelöst werden. Einträge bleiben erhalten.', +}; +export default vacay; diff --git a/shared/src/i18n/en/admin.ts b/shared/src/i18n/en/admin.ts new file mode 100644 index 00000000..8b9c408d --- /dev/null +++ b/shared/src/i18n/en/admin.ts @@ -0,0 +1,358 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notifications', + 'admin.notifications.hint': + 'Choose one notification channel. Only one can be active at a time.', + 'admin.notifications.none': 'Disabled', + 'admin.notifications.email': 'Email (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Allow users to configure their own ntfy topics for push notifications. Set the default server below to pre-fill user settings.', + 'admin.notifications.save': 'Save notification settings', + 'admin.notifications.saved': 'Notification settings saved', + 'admin.notifications.testWebhook': 'Send test webhook', + 'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully', + 'admin.notifications.testWebhookFailed': 'Test webhook failed', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'In-app notifications are always active and cannot be disabled globally.', + 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'This webhook is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user webhooks and always fires when set.', + 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL saved', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Test webhook sent successfully', + 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin webhook always fires when a URL is configured', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Also used as the default server for user ntfy notifications. Leave blank to default to ntfy.sh. Users can override this in their own settings.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Admin access token cleared', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNotificationsHint': + 'Configure which channels deliver admin-only notifications (e.g. version alerts).', + 'admin.notifications.tripReminders.title': 'Trip Reminders', + 'admin.notifications.tripReminders.hint': + 'Send a reminder notification before a trip starts (requires reminder days to be set on the trip).', + 'admin.notifications.tripReminders.enabled': 'Trip reminders enabled', + 'admin.notifications.tripReminders.disabled': 'Trip reminders disabled', + 'admin.smtp.title': 'Email & Notifications', + 'admin.smtp.hint': 'SMTP configuration for sending email notifications.', + 'admin.smtp.testButton': 'Send test email', + 'admin.webhook.hint': + 'Allow users to configure their own webhook URLs for notifications (Discord, Slack, etc.).', + 'admin.smtp.testSuccess': 'Test email sent successfully', + 'admin.smtp.testFailed': 'Test email failed', + 'admin.title': 'Administration', + 'admin.subtitle': 'User management and system settings', + 'admin.tabs.users': 'Users', + 'admin.tabs.categories': 'Categories', + 'admin.tabs.backup': 'Backup', + 'admin.tabs.notifications': 'Notifications', + 'admin.tabs.audit': 'Audit', + 'admin.stats.users': 'Users', + 'admin.stats.trips': 'Trips', + 'admin.stats.places': 'Places', + 'admin.stats.photos': 'Photos', + 'admin.stats.files': 'Files', + 'admin.table.user': 'User', + 'admin.table.email': 'Email', + 'admin.table.role': 'Role', + 'admin.table.created': 'Created', + 'admin.table.lastLogin': 'Last Login', + 'admin.table.actions': 'Actions', + 'admin.you': '(You)', + 'admin.editUser': 'Edit User', + 'admin.newPassword': 'New Password', + 'admin.newPasswordHint': 'Leave empty to keep current password', + 'admin.deleteUser': + 'Delete user "{name}"? All trips will be permanently deleted.', + 'admin.deleteUserTitle': 'Delete user', + 'admin.newPasswordPlaceholder': 'Enter new password…', + 'admin.toast.loadError': 'Failed to load admin data', + 'admin.toast.userUpdated': 'User updated', + 'admin.toast.updateError': 'Failed to update', + 'admin.toast.userDeleted': 'User deleted', + 'admin.toast.deleteError': 'Failed to delete', + 'admin.toast.cannotDeleteSelf': 'Cannot delete your own account', + 'admin.toast.userCreated': 'User created', + 'admin.toast.createError': 'Failed to create user', + 'admin.toast.fieldsRequired': 'Username, email and password are required', + 'admin.createUser': 'Create User', + 'admin.invite.title': 'Invite Links', + 'admin.invite.subtitle': 'Create one-time registration links', + 'admin.invite.create': 'Create Link', + 'admin.invite.createAndCopy': 'Create & Copy', + 'admin.invite.empty': 'No invite links created yet', + 'admin.invite.maxUses': 'Max. Uses', + 'admin.invite.expiry': 'Expires after', + 'admin.invite.uses': 'used', + 'admin.invite.expiresAt': 'expires', + 'admin.invite.createdBy': 'by', + 'admin.invite.active': 'Active', + 'admin.invite.expired': 'Expired', + 'admin.invite.usedUp': 'Used up', + 'admin.invite.copied': 'Invite link copied to clipboard', + 'admin.invite.copyLink': 'Copy link', + 'admin.invite.deleted': 'Invite link deleted', + 'admin.invite.createError': 'Failed to create invite link', + 'admin.invite.deleteError': 'Failed to delete invite link', + 'admin.tabs.settings': 'Settings', + 'admin.allowRegistration': 'Allow Registration', + 'admin.allowRegistrationHint': 'New users can register themselves', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Require two-factor authentication (2FA)', + 'admin.requireMfaHint': + 'Users without 2FA must complete setup in Settings before using the app.', + 'admin.apiKeys': 'API Keys', + 'admin.apiKeysHint': + 'Optional. Enables extended place data like photos and weather.', + 'admin.mapsKey': 'Google Maps API Key', + 'admin.mapsKeyHint': + 'Required for place search. Get at console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Without an API key, OpenStreetMap is used for place search. With a Google API key, photos, ratings, and opening hours can be loaded as well. Get one at console.cloud.google.com.', + 'admin.recommended': 'Recommended', + 'admin.weatherKey': 'OpenWeatherMap API Key', + 'admin.weatherKeyHint': 'For weather data. Free at openweathermap.org', + 'admin.validateKey': 'Test', + 'admin.keyValid': 'Connected', + 'admin.keyInvalid': 'Invalid', + 'admin.keySaved': 'API keys saved', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Allow login via external providers like Google, Apple, Authentik or Keycloak.', + 'admin.oidcDisplayName': 'Display Name', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'The OpenID Connect Issuer URL of the provider. e.g. https://accounts.google.com', + 'admin.oidcSaved': 'OIDC configuration saved', + 'admin.oidcOnlyMode': 'Disable password authentication', + 'admin.oidcOnlyModeHint': + 'When enabled, only SSO login is permitted. Password-based login and registration are blocked.', + 'admin.fileTypes': 'Allowed File Types', + 'admin.fileTypesHint': 'Configure which file types users can upload.', + 'admin.fileTypesFormat': + 'Comma-separated extensions (e.g. jpg,png,pdf,doc). Use * to allow all types.', + 'admin.fileTypesSaved': 'File type settings saved', + 'admin.placesPhotos.title': 'Place Photos', + 'admin.placesPhotos.subtitle': + 'Fetch photos from the Google Places API. Disable to save API quota. Wikimedia photos are unaffected.', + 'admin.placesAutocomplete.title': 'Place Autocomplete', + 'admin.placesAutocomplete.subtitle': + 'Use the Google Places API for search suggestions. Disable to save API quota.', + 'admin.placesDetails.title': 'Place Details', + 'admin.placesDetails.subtitle': + 'Fetch detailed place information (hours, rating, website) from the Google Places API. Disable to save API quota.', + 'admin.bagTracking.title': 'Bag Tracking', + 'admin.bagTracking.subtitle': + 'Enable weight and bag assignment for packing items', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Real-time messaging for trip collaboration', + 'admin.collab.notes.title': 'Notes', + 'admin.collab.notes.subtitle': 'Shared notes and documents', + 'admin.collab.polls.title': 'Polls', + 'admin.collab.polls.subtitle': 'Group polls and voting', + 'admin.collab.whatsnext.title': "What's Next", + 'admin.collab.whatsnext.subtitle': 'Activity suggestions and next steps', + 'admin.tabs.config': 'Personalization', + 'admin.tabs.defaults': 'User Defaults', + 'admin.defaultSettings.title': 'Default User Settings', + 'admin.defaultSettings.description': + 'Set instance-wide defaults. Users who have not changed a setting will see these values. Their own changes always take priority.', + 'admin.defaultSettings.saved': 'Default saved', + 'admin.defaultSettings.reset': 'Reset to built-in default', + 'admin.defaultSettings.resetToBuiltIn': 'reset', + 'admin.tabs.templates': 'Packing Templates', + 'admin.packingTemplates.title': 'Packing Templates', + 'admin.packingTemplates.subtitle': + 'Create reusable packing lists for your trips', + 'admin.packingTemplates.create': 'New Template', + 'admin.packingTemplates.namePlaceholder': + 'Template name (e.g. Beach Holiday)', + 'admin.packingTemplates.empty': 'No templates created yet', + 'admin.packingTemplates.items': 'items', + 'admin.packingTemplates.categories': 'categories', + 'admin.packingTemplates.itemName': 'Item name', + 'admin.packingTemplates.itemCategory': 'Category', + 'admin.packingTemplates.categoryName': 'Category name (e.g. Clothing)', + 'admin.packingTemplates.addCategory': 'Add category', + 'admin.packingTemplates.created': 'Template created', + 'admin.packingTemplates.deleted': 'Template deleted', + 'admin.packingTemplates.loadError': 'Failed to load templates', + 'admin.packingTemplates.createError': 'Failed to create template', + 'admin.packingTemplates.deleteError': 'Failed to delete template', + 'admin.packingTemplates.saveError': 'Failed to save', + 'admin.tabs.addons': 'Addons', + 'admin.addons.title': 'Addons', + 'admin.addons.subtitle': + 'Enable or disable features to customize your TREK experience.', + 'admin.addons.catalog.packing.name': 'Lists', + 'admin.addons.catalog.packing.description': + 'Packing lists and to-do tasks for your trips', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Track expenses and plan your trip budget', + 'admin.addons.catalog.documents.name': 'Documents', + 'admin.addons.catalog.documents.description': + 'Store and manage travel documents', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Personal vacation planner with calendar view', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'World map with visited countries and travel stats', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Real-time notes, polls, and chat for trip planning', + 'admin.addons.catalog.memories.name': 'Photos (Immich)', + 'admin.addons.catalog.memories.description': + 'Share trip photos via your Immich instance', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol for AI assistant integration', + 'admin.addons.subtitleBefore': + 'Enable or disable features to customize your ', + 'admin.addons.subtitleAfter': ' experience.', + 'admin.addons.enabled': 'Enabled', + 'admin.addons.disabled': 'Disabled', + 'admin.addons.type.trip': 'Trip', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integration', + 'admin.addons.tripHint': 'Available as a tab within each trip', + 'admin.addons.globalHint': + 'Available as a standalone section in the main navigation', + 'admin.addons.integrationHint': + 'Backend services and API integrations with no dedicated page', + 'admin.addons.toast.updated': 'Addon updated', + 'admin.addons.toast.error': 'Failed to update addon', + 'admin.addons.noAddons': 'No addons available', + 'admin.weather.title': 'Weather Data', + 'admin.weather.badge': 'Since March 24, 2026', + 'admin.weather.description': + 'TREK uses Open-Meteo as its weather data source. Open-Meteo is a free, open-source weather service — no API key required.', + 'admin.weather.forecast': '16-day forecast', + 'admin.weather.forecastDesc': 'Previously 5 days (OpenWeatherMap)', + 'admin.weather.climate': 'Historical climate data', + 'admin.weather.climateDesc': + 'Averages from the last 85 years for days beyond the 16-day forecast', + 'admin.weather.requests': '10,000 requests / day', + 'admin.weather.requestsDesc': 'Free, no API key required', + 'admin.weather.locationHint': + 'Weather is based on the first place with coordinates in each day. If no place is assigned to a day, any place from the place list is used as a reference.', + 'admin.tabs.mcpTokens': 'MCP Access', + 'admin.mcpTokens.title': 'MCP Access', + 'admin.mcpTokens.subtitle': + 'Manage OAuth sessions and API tokens across all users', + 'admin.mcpTokens.sectionTitle': 'API Tokens', + 'admin.mcpTokens.owner': 'Owner', + 'admin.mcpTokens.tokenName': 'Token Name', + 'admin.mcpTokens.created': 'Created', + 'admin.mcpTokens.lastUsed': 'Last Used', + 'admin.mcpTokens.never': 'Never', + 'admin.mcpTokens.empty': 'No MCP tokens have been created yet', + 'admin.mcpTokens.deleteTitle': 'Delete Token', + 'admin.mcpTokens.deleteMessage': + 'This will revoke the token immediately. The user will lose MCP access through this token.', + 'admin.mcpTokens.deleteSuccess': 'Token deleted', + 'admin.mcpTokens.deleteError': 'Failed to delete token', + 'admin.mcpTokens.loadError': 'Failed to load tokens', + 'admin.oauthSessions.sectionTitle': 'OAuth Sessions', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Owner', + 'admin.oauthSessions.scopes': 'Scopes', + 'admin.oauthSessions.created': 'Created', + 'admin.oauthSessions.empty': 'No active OAuth sessions', + 'admin.oauthSessions.revokeTitle': 'Revoke Session', + 'admin.oauthSessions.revokeMessage': + 'This will revoke the OAuth session immediately. The client will lose MCP access.', + 'admin.oauthSessions.revokeSuccess': 'Session revoked', + 'admin.oauthSessions.revokeError': 'Failed to revoke session', + 'admin.oauthSessions.loadError': 'Failed to load OAuth sessions', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Security-sensitive and administration events (backups, users, MFA, settings).', + 'admin.audit.empty': 'No audit entries yet.', + 'admin.audit.refresh': 'Refresh', + 'admin.audit.loadMore': 'Load more', + 'admin.audit.showing': '{count} loaded · {total} total', + 'admin.audit.col.time': 'Time', + 'admin.audit.col.user': 'User', + 'admin.audit.col.action': 'Action', + 'admin.audit.col.resource': 'Resource', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Details', + 'admin.github.title': 'Release History', + 'admin.github.subtitle': 'Latest updates from {repo}', + 'admin.github.latest': 'Latest', + 'admin.github.prerelease': 'Pre-release', + 'admin.github.showDetails': 'Show details', + 'admin.github.hideDetails': 'Hide details', + 'admin.github.loadMore': 'Load more', + 'admin.github.loading': 'Loading...', + 'admin.github.error': 'Failed to load releases', + 'admin.github.by': 'by', + 'admin.github.support': 'Helps me keep building TREK', + 'admin.update.available': 'Update available', + 'admin.update.text': + 'TREK {version} is available. You are running {current}.', + 'admin.update.button': 'View on GitHub', + 'admin.update.install': 'Install Update', + 'admin.update.confirmTitle': 'Install Update?', + 'admin.update.confirmText': + 'TREK will be updated from {current} to {version}. The server will restart automatically afterwards.', + 'admin.update.dataInfo': + 'All your data (trips, users, API keys, uploads, Vacay, Atlas, budgets) will be preserved.', + 'admin.update.warning': + 'The app will be briefly unavailable during the restart.', + 'admin.update.confirm': 'Update Now', + 'admin.update.installing': 'Updating…', + 'admin.update.success': 'Update installed! Server is restarting…', + 'admin.update.failed': 'Update failed', + 'admin.update.backupHint': 'We recommend creating a backup before updating.', + 'admin.update.backupLink': 'Go to Backup', + 'admin.update.howTo': 'How to Update', + 'admin.update.dockerText': + 'Your TREK instance runs in Docker. To update to {version}, run the following commands on your server:', + 'admin.update.reloadHint': 'Please reload the page in a few seconds.', + 'admin.tabs.permissions': 'Permissions', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + 'Trip tracking & travel journal with check-ins, photos, and daily stories', +}; +export default admin; diff --git a/shared/src/i18n/en/airport.ts b/shared/src/i18n/en/airport.ts new file mode 100644 index 00000000..8be3c19d --- /dev/null +++ b/shared/src/i18n/en/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Airport code or city (e.g. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/en/atlas.ts b/shared/src/i18n/en/atlas.ts new file mode 100644 index 00000000..f960c86b --- /dev/null +++ b/shared/src/i18n/en/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Your travel footprint around the world', + 'atlas.countries': 'Countries', + 'atlas.trips': 'Trips', + 'atlas.places': 'Places', + 'atlas.unmark': 'Remove', + 'atlas.confirmMark': 'Mark this country as visited?', + 'atlas.confirmUnmark': 'Remove this country from your visited list?', + 'atlas.confirmUnmarkRegion': 'Remove this region from your visited list?', + 'atlas.markVisited': 'Mark as visited', + 'atlas.markVisitedHint': 'Add this country to your visited list', + 'atlas.markRegionVisitedHint': 'Add this region to your visited list', + 'atlas.addToBucket': 'Add to bucket list', + 'atlas.addPoi': 'Add place', + 'atlas.searchCountry': 'Search a country...', + 'atlas.bucketNamePlaceholder': 'Name (country, city, place...)', + 'atlas.month': 'Month', + 'atlas.year': 'Year', + 'atlas.addToBucketHint': 'Save as a place you want to visit', + 'atlas.bucketWhen': 'When do you plan to visit?', + 'atlas.statsTab': 'Stats', + 'atlas.bucketTab': 'Bucket List', + 'atlas.addBucket': 'Add to bucket list', + 'atlas.bucketNotesPlaceholder': 'Notes (optional)', + 'atlas.bucketEmpty': 'Your bucket list is empty', + 'atlas.bucketEmptyHint': 'Add places you dream of visiting', + 'atlas.days': 'Days', + 'atlas.visitedCountries': 'Visited Countries', + 'atlas.cities': 'Cities', + 'atlas.noData': 'No travel data yet', + 'atlas.noDataHint': 'Create a trip and add places to see your world map', + 'atlas.lastTrip': 'Last trip', + 'atlas.nextTrip': 'Next trip', + 'atlas.daysLeft': 'days left', + 'atlas.streak': 'Streak', + 'atlas.years': 'years', + 'atlas.yearInRow': 'year in a row', + 'atlas.yearsInRow': 'years in a row', + 'atlas.tripIn': 'trip in', + 'atlas.tripsIn': 'trips in', + 'atlas.since': 'since', + 'atlas.europe': 'Europe', + 'atlas.asia': 'Asia', + 'atlas.northAmerica': 'N. America', + 'atlas.southAmerica': 'S. America', + 'atlas.africa': 'Africa', + 'atlas.oceania': 'Oceania', + 'atlas.other': 'Other', + 'atlas.firstVisit': 'First trip', + 'atlas.lastVisitLabel': 'Last trip', + 'atlas.tripSingular': 'Trip', + 'atlas.tripPlural': 'Trips', + 'atlas.placeVisited': 'Place visited', + 'atlas.placesVisited': 'Places visited', +}; +export default atlas; diff --git a/shared/src/i18n/en/backup.ts b/shared/src/i18n/en/backup.ts new file mode 100644 index 00000000..e1bc00ff --- /dev/null +++ b/shared/src/i18n/en/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Data Backup', + 'backup.subtitle': 'Database and all uploaded files', + 'backup.refresh': 'Refresh', + 'backup.upload': 'Upload Backup', + 'backup.uploading': 'Uploading…', + 'backup.create': 'Create Backup', + 'backup.creating': 'Creating…', + 'backup.empty': 'No backups yet', + 'backup.createFirst': 'Create first backup', + 'backup.download': 'Download', + 'backup.restore': 'Restore', + 'backup.confirm.restore': + 'Restore backup "{name}"?\n\nAll current data will be replaced with the backup.', + 'backup.confirm.uploadRestore': + 'Upload and restore backup file "{name}"?\n\nAll current data will be overwritten.', + 'backup.confirm.delete': 'Delete backup "{name}"?', + 'backup.toast.loadError': 'Failed to load backups', + 'backup.toast.created': 'Backup created successfully', + 'backup.toast.createError': 'Failed to create backup', + 'backup.toast.restored': 'Backup restored. Page will reload…', + 'backup.toast.restoreError': 'Failed to restore', + 'backup.toast.uploadError': 'Failed to upload', + 'backup.toast.deleted': 'Backup deleted', + 'backup.toast.deleteError': 'Failed to delete', + 'backup.toast.downloadError': 'Download failed', + 'backup.toast.settingsSaved': 'Auto-backup settings saved', + 'backup.toast.settingsError': 'Failed to save settings', + 'backup.auto.title': 'Auto-Backup', + 'backup.auto.subtitle': 'Automatic backup on a schedule', + 'backup.auto.enable': 'Enable auto-backup', + 'backup.auto.enableHint': + 'Backups will be created automatically on the chosen schedule', + 'backup.auto.interval': 'Interval', + 'backup.auto.hour': 'Run at hour', + 'backup.auto.hourHint': 'Server local time ({format} format)', + 'backup.auto.dayOfWeek': 'Day of week', + 'backup.auto.dayOfMonth': 'Day of month', + 'backup.auto.dayOfMonthHint': + 'Limited to 1–28 for compatibility with all months', + 'backup.auto.scheduleSummary': 'Schedule', + 'backup.auto.summaryDaily': 'Every day at {hour}:00', + 'backup.auto.summaryWeekly': 'Every {day} at {hour}:00', + 'backup.auto.summaryMonthly': 'Day {day} of every month at {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Auto-backup is configured via Docker environment variables. To change these settings, update your docker-compose.yml and restart the container.', + 'backup.auto.copyEnv': 'Copy Docker env vars', + 'backup.auto.envCopied': 'Docker env vars copied to clipboard', + 'backup.auto.keepLabel': 'Delete old backups after', + 'backup.dow.sunday': 'Sun', + 'backup.dow.monday': 'Mon', + 'backup.dow.tuesday': 'Tue', + 'backup.dow.wednesday': 'Wed', + 'backup.dow.thursday': 'Thu', + 'backup.dow.friday': 'Fri', + 'backup.dow.saturday': 'Sat', + 'backup.interval.hourly': 'Hourly', + 'backup.interval.daily': 'Daily', + 'backup.interval.weekly': 'Weekly', + 'backup.interval.monthly': 'Monthly', + 'backup.keep.1day': '1 day', + 'backup.keep.3days': '3 days', + 'backup.keep.7days': '7 days', + 'backup.keep.14days': '14 days', + 'backup.keep.30days': '30 days', + 'backup.keep.forever': 'Keep forever', + 'backup.restoreConfirmTitle': 'Restore Backup?', + 'backup.restoreWarning': + 'All current data (trips, places, users, uploads) will be permanently replaced by the backup. This action cannot be undone.', + 'backup.restoreTip': + 'Tip: Create a backup of the current state before restoring.', + 'backup.restoreConfirm': 'Yes, restore', +}; +export default backup; diff --git a/shared/src/i18n/en/budget.ts b/shared/src/i18n/en/budget.ts new file mode 100644 index 00000000..733abca5 --- /dev/null +++ b/shared/src/i18n/en/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'Export CSV', + 'budget.emptyTitle': 'No budget created yet', + 'budget.emptyText': + 'Create categories and entries to plan your travel budget', + 'budget.emptyPlaceholder': 'Enter category name...', + 'budget.createCategory': 'Create Category', + 'budget.category': 'Category', + 'budget.categoryName': 'Category Name', + 'budget.table.name': 'Name', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Persons', + 'budget.table.days': 'Days', + 'budget.table.perPerson': 'Per Person', + 'budget.table.perDay': 'Per Day', + 'budget.table.perPersonDay': 'P. p / Day', + 'budget.table.note': 'Note', + 'budget.table.date': 'Date', + 'budget.newEntry': 'New Entry', + 'budget.defaultEntry': 'New Entry', + 'budget.defaultCategory': 'New Category', + 'budget.total': 'Total', + 'budget.totalBudget': 'Total Budget', + 'budget.byCategory': 'By Category', + 'budget.editTooltip': 'Click to edit', + 'budget.linkedToReservation': 'Linked to a reservation — edit the name there', + 'budget.confirm.deleteCategory': + 'Are you sure you want to delete the category "{name}" with {count} entries?', + 'budget.deleteCategory': 'Delete Category', + 'budget.perPerson': 'Per Person', + 'budget.paid': 'Paid', + 'budget.open': 'Open', + 'budget.noMembers': 'No members assigned', + 'budget.settlement': 'Settlement', + 'budget.settlementInfo': + 'Click a member avatar on a budget item to mark them green — this means they paid. The settlement then shows who owes whom and how much.', + 'budget.netBalances': 'Net Balances', + 'budget.categoriesLabel': 'categories', +}; +export default budget; diff --git a/shared/src/i18n/en/categories.ts b/shared/src/i18n/en/categories.ts new file mode 100644 index 00000000..adff391f --- /dev/null +++ b/shared/src/i18n/en/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categories', + 'categories.subtitle': 'Manage categories for places', + 'categories.new': 'New Category', + 'categories.empty': 'No categories yet', + 'categories.namePlaceholder': 'Category name', + 'categories.icon': 'Icon', + 'categories.color': 'Color', + 'categories.customColor': 'Choose custom color', + 'categories.preview': 'Preview', + 'categories.defaultName': 'Category', + 'categories.update': 'Update', + 'categories.create': 'Create', + 'categories.confirm.delete': + 'Delete category? Places in this category will not be deleted.', + 'categories.toast.loadError': 'Failed to load categories', + 'categories.toast.nameRequired': 'Please enter a name', + 'categories.toast.updated': 'Category updated', + 'categories.toast.created': 'Category created', + 'categories.toast.saveError': 'Failed to save', + 'categories.toast.deleted': 'Category deleted', + 'categories.toast.deleteError': 'Failed to delete', +}; +export default categories; diff --git a/shared/src/i18n/en/collab.ts b/shared/src/i18n/en/collab.ts new file mode 100644 index 00000000..aee6b9a5 --- /dev/null +++ b/shared/src/i18n/en/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Notes', + 'collab.tabs.polls': 'Polls', + 'collab.whatsNext.title': "What's Next", + 'collab.whatsNext.today': 'Today', + 'collab.whatsNext.tomorrow': 'Tomorrow', + 'collab.whatsNext.empty': 'No upcoming activities', + 'collab.whatsNext.until': 'to', + 'collab.whatsNext.emptyHint': 'Activities with times will appear here', + 'collab.chat.send': 'Send', + 'collab.chat.placeholder': 'Type a message...', + 'collab.chat.empty': 'Start the conversation', + 'collab.chat.emptyHint': 'Messages are shared with all trip members', + 'collab.chat.emptyDesc': + 'Share ideas, plans, and updates with your travel group', + 'collab.chat.today': 'Today', + 'collab.chat.yesterday': 'Yesterday', + 'collab.chat.deletedMessage': 'deleted a message', + 'collab.chat.reply': 'Reply', + 'collab.chat.loadMore': 'Load older messages', + 'collab.chat.justNow': 'just now', + 'collab.chat.minutesAgo': '{n}m ago', + 'collab.chat.hoursAgo': '{n}h ago', + 'collab.notes.title': 'Notes', + 'collab.notes.new': 'New Note', + 'collab.notes.empty': 'No notes yet', + 'collab.notes.emptyHint': 'Start capturing ideas and plans', + 'collab.notes.all': 'All', + 'collab.notes.titlePlaceholder': 'Note title', + 'collab.notes.contentPlaceholder': 'Write something...', + 'collab.notes.categoryPlaceholder': 'Category', + 'collab.notes.newCategory': 'New category...', + 'collab.notes.category': 'Category', + 'collab.notes.noCategory': 'No category', + 'collab.notes.color': 'Color', + 'collab.notes.save': 'Save', + 'collab.notes.cancel': 'Cancel', + 'collab.notes.edit': 'Edit', + 'collab.notes.delete': 'Delete', + 'collab.notes.pin': 'Pin', + 'collab.notes.unpin': 'Unpin', + 'collab.notes.daysAgo': '{n}d ago', + 'collab.notes.categorySettings': 'Manage Categories', + 'collab.notes.create': 'Create', + 'collab.notes.website': 'Website', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Attach files', + 'collab.notes.noCategoriesYet': 'No categories yet', + 'collab.notes.emptyDesc': 'Create a note to get started', + 'collab.polls.title': 'Polls', + 'collab.polls.new': 'New Poll', + 'collab.polls.empty': 'No polls yet', + 'collab.polls.emptyHint': 'Ask the group and vote together', + 'collab.polls.question': 'Question', + 'collab.polls.questionPlaceholder': 'What should we do?', + 'collab.polls.addOption': '+ Add option', + 'collab.polls.optionPlaceholder': 'Option {n}', + 'collab.polls.create': 'Create Poll', + 'collab.polls.close': 'Close', + 'collab.polls.closed': 'Closed', + 'collab.polls.votes': '{n} votes', + 'collab.polls.vote': '{n} vote', + 'collab.polls.multipleChoice': 'Multiple choice', + 'collab.polls.multiChoice': 'Multiple choice', + 'collab.polls.deadline': 'Deadline', + 'collab.polls.option': 'Option', + 'collab.polls.options': 'Options', + 'collab.polls.delete': 'Delete', + 'collab.polls.closedSection': 'Closed', +}; +export default collab; diff --git a/shared/src/i18n/en/common.ts b/shared/src/i18n/en/common.ts new file mode 100644 index 00000000..ea5cd89e --- /dev/null +++ b/shared/src/i18n/en/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Save', + 'common.showMore': 'Show more', + 'common.showLess': 'Show less', + 'common.cancel': 'Cancel', + 'common.clear': 'Clear', + 'common.delete': 'Delete', + 'common.edit': 'Edit', + 'common.add': 'Add', + 'common.loading': 'Loading...', + 'common.import': 'Import', + 'common.select': 'Select', + 'common.selectAll': 'Select all', + 'common.deselectAll': 'Deselect all', + 'common.error': 'Error', + 'common.unknownError': 'Unknown error', + 'common.tooManyAttempts': 'Too many attempts. Please try again later.', + 'common.back': 'Back', + 'common.all': 'All', + 'common.close': 'Close', + 'common.open': 'Open', + 'common.upload': 'Upload', + 'common.search': 'Search', + 'common.confirm': 'Confirm', + 'common.ok': 'OK', + 'common.yes': 'Yes', + 'common.no': 'No', + 'common.or': 'or', + 'common.none': 'None', + 'common.date': 'Date', + 'common.rename': 'Rename', + 'common.discardChanges': 'Discard Changes', + 'common.discard': 'Discard', + 'common.name': 'Name', + 'common.email': 'Email', + 'common.password': 'Password', + 'common.saving': 'Saving...', + 'common.justNow': 'just now', + 'common.hoursAgo': '{count}h ago', + 'common.daysAgo': '{count}d ago', + 'common.saved': 'Saved', + 'common.update': 'Update', + 'common.change': 'Change', + 'common.uploading': 'Uploading…', + 'common.backToPlanning': 'Back to Planning', + 'common.reset': 'Reset', + 'common.expand': 'Expand', + 'common.collapse': 'Collapse', + 'common.copy': 'Copy', + 'common.copied': 'Copied', +}; +export default common; diff --git a/shared/src/i18n/en/dashboard.ts b/shared/src/i18n/en/dashboard.ts new file mode 100644 index 00000000..b72a19fc --- /dev/null +++ b/shared/src/i18n/en/dashboard.ts @@ -0,0 +1,121 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'My Trips', + 'dashboard.subtitle.loading': 'Loading trips...', + 'dashboard.subtitle.trips': '{count} trips ({archived} archived)', + 'dashboard.subtitle.empty': 'Start your first trip', + 'dashboard.subtitle.activeOne': '{count} active trip', + 'dashboard.subtitle.activeMany': '{count} active trips', + 'dashboard.subtitle.archivedSuffix': ' · {count} archived', + 'dashboard.newTrip': 'New Trip', + 'dashboard.gridView': 'Grid view', + 'dashboard.listView': 'List view', + 'dashboard.currency': 'Currency', + 'dashboard.timezone': 'Timezones', + 'dashboard.localTime': 'Local', + 'dashboard.timezoneCustomTitle': 'Custom Timezone', + 'dashboard.timezoneCustomLabelPlaceholder': 'Label (optional)', + 'dashboard.timezoneCustomTzPlaceholder': 'e.g. America/New_York', + 'dashboard.timezoneCustomAdd': 'Add', + 'dashboard.timezoneCustomErrorEmpty': 'Enter a timezone identifier', + 'dashboard.timezoneCustomErrorInvalid': + 'Invalid timezone. Use format like Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Already added', + 'dashboard.emptyTitle': 'No trips yet', + 'dashboard.emptyText': 'Create your first trip and start planning!', + 'dashboard.emptyButton': 'Create First Trip', + 'dashboard.nextTrip': 'Next Trip', + 'dashboard.shared': 'Shared', + 'dashboard.sharedBy': 'Shared by {name}', + 'dashboard.days': 'Days', + 'dashboard.places': 'Places', + 'dashboard.members': 'Buddies', + 'dashboard.archive': 'Archive', + 'dashboard.copyTrip': 'Copy', + 'dashboard.copySuffix': 'copy', + 'dashboard.restore': 'Restore', + 'dashboard.archived': 'Archived', + 'dashboard.status.ongoing': 'Ongoing', + 'dashboard.status.today': 'Today', + 'dashboard.status.tomorrow': 'Tomorrow', + 'dashboard.status.past': 'Past', + 'dashboard.status.daysLeft': '{count} days left', + 'dashboard.toast.loadError': 'Failed to load trips', + 'dashboard.toast.created': 'Trip created successfully!', + 'dashboard.toast.createError': 'Failed to create trip', + 'dashboard.toast.updated': 'Trip updated!', + 'dashboard.toast.updateError': 'Failed to update trip', + 'dashboard.toast.deleted': 'Trip deleted', + 'dashboard.toast.deleteError': 'Failed to delete trip', + 'dashboard.toast.archived': 'Trip archived', + 'dashboard.toast.archiveError': 'Failed to archive trip', + 'dashboard.toast.restored': 'Trip restored', + 'dashboard.toast.restoreError': 'Failed to restore trip', + 'dashboard.toast.copied': 'Trip copied!', + 'dashboard.toast.copyError': 'Failed to copy trip', + 'dashboard.confirm.delete': + 'Delete trip "{title}"? All places and plans will be permanently deleted.', + 'dashboard.confirm.copy.title': 'Copy this trip?', + 'dashboard.confirm.copy.willCopy': 'Will be copied', + 'dashboard.confirm.copy.will1': 'Days, places & day assignments', + 'dashboard.confirm.copy.will2': 'Accommodations & reservations', + 'dashboard.confirm.copy.will3': 'Budget items & category order', + 'dashboard.confirm.copy.will4': 'Packing lists (unchecked)', + 'dashboard.confirm.copy.will5': 'TODOs (unassigned & unchecked)', + 'dashboard.confirm.copy.will6': 'Day notes', + 'dashboard.confirm.copy.wontCopy': "Won't be copied", + 'dashboard.confirm.copy.wont1': 'Collaborators & member assignments', + 'dashboard.confirm.copy.wont2': 'Collab notes, polls & messages', + 'dashboard.confirm.copy.wont3': 'Files & photos', + 'dashboard.confirm.copy.wont4': 'Share tokens', + 'dashboard.confirm.copy.confirm': 'Copy trip', + 'dashboard.editTrip': 'Edit Trip', + 'dashboard.createTrip': 'Create New Trip', + 'dashboard.tripTitle': 'Title', + 'dashboard.tripTitlePlaceholder': 'e.g. Summer in Japan', + 'dashboard.tripDescription': 'Description', + 'dashboard.tripDescriptionPlaceholder': 'What is this trip about?', + 'dashboard.startDate': 'Start Date', + 'dashboard.endDate': 'End Date', + 'dashboard.dayCount': 'Number of Days', + 'dashboard.dayCountHint': + 'How many days to plan for when no travel dates are set.', + 'dashboard.noDateHint': + 'No date set — 7 default days will be created. You can change this anytime.', + 'dashboard.coverImage': 'Cover Image', + 'dashboard.addCoverImage': 'Add cover image (or drag & drop)', + 'dashboard.addMembers': 'Travel buddies', + 'dashboard.addMember': 'Add member', + 'dashboard.coverSaved': 'Cover image saved', + 'dashboard.coverUploadError': 'Failed to upload', + 'dashboard.coverRemoveError': 'Failed to remove', + 'dashboard.titleRequired': 'Title is required', + 'dashboard.endDateError': 'End date must be after start date', + 'dashboard.greeting.morning': 'Good morning,', + 'dashboard.greeting.afternoon': 'Good afternoon,', + 'dashboard.greeting.evening': 'Good evening,', + 'dashboard.mobile.liveNow': 'Live Now', + 'dashboard.mobile.tripProgress': 'Trip progress', + 'dashboard.mobile.daysLeft': '{count} days left', + 'dashboard.mobile.places': 'Places', + 'dashboard.mobile.buddies': 'Buddies', + 'dashboard.mobile.newTrip': 'New Trip', + 'dashboard.mobile.currency': 'Currency', + 'dashboard.mobile.timezone': 'Timezone', + 'dashboard.mobile.upcomingTrips': 'Upcoming Trips', + 'dashboard.mobile.yourTrips': 'Your Trips', + 'dashboard.mobile.trips': 'trips', + 'dashboard.mobile.starts': 'Starts', + 'dashboard.mobile.duration': 'Duration', + 'dashboard.mobile.day': 'day', + 'dashboard.mobile.days': 'days', + 'dashboard.mobile.ongoing': 'Ongoing', + 'dashboard.mobile.startsToday': 'Starts today', + 'dashboard.mobile.tomorrow': 'Tomorrow', + 'dashboard.mobile.inDays': 'In {count} days', + 'dashboard.mobile.inMonths': 'In {count} months', + 'dashboard.mobile.completed': 'Completed', + 'dashboard.mobile.currencyConverter': 'Currency Converter', +}; +export default dashboard; diff --git a/shared/src/i18n/en/day.ts b/shared/src/i18n/en/day.ts new file mode 100644 index 00000000..96f47973 --- /dev/null +++ b/shared/src/i18n/en/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Rain probability', + 'day.precipitation': 'Precipitation', + 'day.wind': 'Wind', + 'day.sunrise': 'Sunrise', + 'day.sunset': 'Sunset', + 'day.hourlyForecast': 'Hourly Forecast', + 'day.climateHint': + 'Historical averages — real forecast available within 16 days of this date.', + 'day.noWeather': 'No weather data available. Add a place with coordinates.', + 'day.overview': 'Daily Overview', + 'day.accommodation': 'Accommodation', + 'day.addAccommodation': 'Add accommodation', + 'day.hotelDayRange': 'Apply to days', + 'day.noPlacesForHotel': 'Add places to your trip first', + 'day.allDays': 'All', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Until', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Confirmation', + 'day.editAccommodation': 'Edit accommodation', + 'day.reservations': 'Reservations', +}; +export default day; diff --git a/shared/src/i18n/en/dayplan.ts b/shared/src/i18n/en/dayplan.ts new file mode 100644 index 00000000..8f1825f6 --- /dev/null +++ b/shared/src/i18n/en/dayplan.ts @@ -0,0 +1,48 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Export calendar (ICS)', + 'dayplan.emptyDay': 'No places planned for this day', + 'dayplan.cannotReorderTransport': + 'Bookings with a fixed time cannot be reordered', + 'dayplan.confirmRemoveTimeTitle': 'Remove time?', + 'dayplan.confirmRemoveTimeBody': + 'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.', + 'dayplan.confirmRemoveTimeAction': 'Remove time & move', + 'dayplan.cannotDropOnTimed': + 'Items cannot be placed between time-bound entries', + 'dayplan.cannotBreakChronology': + 'This would break the chronological order of timed items and bookings', + 'dayplan.addNote': 'Add Note', + 'dayplan.expandAll': 'Expand all days', + 'dayplan.collapseAll': 'Collapse all days', + 'dayplan.editNote': 'Edit Note', + 'dayplan.noteAdd': 'Add Note', + 'dayplan.noteEdit': 'Edit Note', + 'dayplan.noteTitle': 'Note', + 'dayplan.noteSubtitle': 'Daily Note', + 'dayplan.totalCost': 'Total Cost', + 'dayplan.days': 'Days', + 'dayplan.dayN': 'Day {n}', + 'dayplan.calculating': 'Calculating...', + 'dayplan.route': 'Route', + 'dayplan.optimize': 'Optimize', + 'dayplan.optimized': 'Route optimized', + 'dayplan.routeError': 'Failed to calculate route', + 'dayplan.toast.needTwoPlaces': + 'At least two places needed for route optimization', + 'dayplan.toast.routeOptimized': 'Route optimized', + 'dayplan.toast.noGeoPlaces': + 'No places with coordinates found for route calculation', + 'dayplan.confirmed': 'Confirmed', + 'dayplan.pendingRes': 'Pending', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Export day plan as PDF', + 'dayplan.pdfError': 'Failed to export PDF', + 'dayplan.mobile.addPlace': 'Add Place', + 'dayplan.mobile.searchPlaces': 'Search places...', + 'dayplan.mobile.allAssigned': 'All places assigned', + 'dayplan.mobile.noMatch': 'No match', + 'dayplan.mobile.createNew': 'Create new place', +}; +export default dayplan; diff --git a/shared/src/i18n/en/externalNotifications.ts b/shared/src/i18n/en/externalNotifications.ts new file mode 100644 index 00000000..cfe2dc81 --- /dev/null +++ b/shared/src/i18n/en/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const en: NotificationLocale = { + email: { + footer: 'You received this because you have notifications enabled in TREK.', + manage: 'Manage preferences in Settings', + madeWith: 'Made with', + openTrek: 'Open TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Trip invite: "${p.trip}"`, + body: `${p.actor} invited ${p.invitee || 'a member'} to the trip "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `New booking: ${p.booking}`, + body: `${p.actor} added a new ${p.type} "${p.booking}" to "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Trip reminder: ${p.trip}`, + body: `Your trip "${p.trip}" is coming up soon!`, + }), + todo_due: (p) => ({ + title: `To-do due: ${p.todo}`, + body: `"${p.todo}" in "${p.trip}" is due on ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion Invite', + body: `${p.actor} invited you to fuse vacation plans. Open TREK to accept or decline.`, + }), + photos_shared: (p) => ({ + title: `${p.count} photos shared`, + body: `${p.actor} shared ${p.count} photo(s) in "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `New message in "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Packing: ${p.category}`, + body: `${p.actor} assigned you to the "${p.category}" packing category in "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'New TREK version available', + body: `TREK ${p.version} is now available. Visit the admin panel to update.`, + }), + synology_session_cleared: () => ({ + title: 'Synology session cleared', + body: 'Your Synology account or URL changed. You have been logged out of Synology Photos.', + }), + }, + passwordReset: { + subject: 'Reset your password', + greeting: 'Hi', + body: 'We received a request to reset the password for your TREK account. Click the button below to set a new password.', + ctaIntro: 'Reset password', + expiry: 'This link expires in 60 minutes.', + ignore: + "If you didn't request this, you can safely ignore this email — your password won't change.", + }, +}; + +export default en; diff --git a/shared/src/i18n/en/files.ts b/shared/src/i18n/en/files.ts new file mode 100644 index 00000000..779ad58b --- /dev/null +++ b/shared/src/i18n/en/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Files', + 'files.pageTitle': 'Files & Documents', + 'files.subtitle': '{count} files for {trip}', + 'files.download': 'Download', + 'files.openError': 'Could not open file', + 'files.downloadPdf': 'Download PDF', + 'files.count': '{count} files', + 'files.countSingular': '1 file', + 'files.uploaded': '{count} uploaded', + 'files.uploadError': 'Upload failed', + 'files.dropzone': 'Drop files here', + 'files.dropzoneHint': 'or click to browse', + 'files.allowedTypes': + 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Uploading...', + 'files.filterAll': 'All', + 'files.filterPdf': 'PDFs', + 'files.filterImages': 'Images', + 'files.filterDocs': 'Documents', + 'files.filterCollab': 'Collab Notes', + 'files.sourceCollab': 'From Collab Notes', + 'files.empty': 'No files yet', + 'files.emptyHint': 'Upload files to attach them to your trip', + 'files.openTab': 'Open in new tab', + 'files.confirm.delete': 'Are you sure you want to delete this file?', + 'files.toast.deleted': 'File deleted', + 'files.toast.deleteError': 'Failed to delete file', + 'files.sourcePlan': 'Day Plan', + 'files.sourceBooking': 'Booking', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Attach', + 'files.pasteHint': 'You can also paste images from clipboard (Ctrl+V)', + 'files.trash': 'Trash', + 'files.trashEmpty': 'Trash is empty', + 'files.emptyTrash': 'Empty Trash', + 'files.restore': 'Restore', + 'files.star': 'Star', + 'files.unstar': 'Unstar', + 'files.assign': 'Assign', + 'files.assignTitle': 'Assign File', + 'files.assignPlace': 'Place', + 'files.assignBooking': 'Booking', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Unassigned', + 'files.unlink': 'Remove link', + 'files.toast.trashed': 'Moved to trash', + 'files.toast.restored': 'File restored', + 'files.toast.trashEmptied': 'Trash emptied', + 'files.toast.assigned': 'File assigned', + 'files.toast.assignError': 'Assignment failed', + 'files.toast.restoreError': 'Restore failed', + 'files.confirm.permanentDelete': + 'Permanently delete this file? This cannot be undone.', + 'files.confirm.emptyTrash': + 'Permanently delete all trashed files? This cannot be undone.', + 'files.noteLabel': 'Note', + 'files.notePlaceholder': 'Add a note...', +}; +export default files; diff --git a/shared/src/i18n/en/index.ts b/shared/src/i18n/en/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/en/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/en/inspector.ts b/shared/src/i18n/en/inspector.ts new file mode 100644 index 00000000..d42c8804 --- /dev/null +++ b/shared/src/i18n/en/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Open', + 'inspector.closed': 'Closed', + 'inspector.openingHours': 'Opening Hours', + 'inspector.showHours': 'Show opening hours', + 'inspector.files': 'Files', + 'inspector.filesCount': '{count} files', + 'inspector.remove': 'Remove', + 'inspector.removeFromDay': 'Remove from Day', + 'inspector.addToDay': 'Add to Day', + 'inspector.confirmedRes': 'Confirmed Reservation', + 'inspector.pendingRes': 'Pending Reservation', + 'inspector.google': 'Open in Google Maps', + 'inspector.website': 'Open Website', + 'inspector.addRes': 'Reservation', + 'inspector.editRes': 'Edit Reservation', + 'inspector.participants': 'Participants', + 'inspector.trackStats': 'Track Stats', +}; +export default inspector; diff --git a/shared/src/i18n/en/journey.ts b/shared/src/i18n/en/journey.ts new file mode 100644 index 00000000..e672dc14 --- /dev/null +++ b/shared/src/i18n/en/journey.ts @@ -0,0 +1,244 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Search journeys…', + 'journey.search.noResults': 'No journeys match "{query}"', + 'journey.title': 'Journey', + 'journey.subtitle': 'Track your travels as they happen', + 'journey.new': 'New Journey', + 'journey.create': 'Create', + 'journey.titlePlaceholder': 'Where are you going?', + 'journey.empty': 'No journeys yet', + 'journey.emptyHint': 'Start documenting your next trip', + 'journey.deleted': 'Journey deleted', + 'journey.createError': 'Could not create journey', + 'journey.deleteError': 'Could not delete journey', + 'journey.deleteConfirmTitle': 'Delete', + 'journey.deleteConfirmMessage': 'Delete "{title}"? This cannot be undone.', + 'journey.deleteConfirmGeneric': 'Are you sure you want to delete this?', + 'journey.notFound': 'Journey not found', + 'journey.photos': 'Photos', + 'journey.timelineEmpty': 'No stops yet', + 'journey.timelineEmptyHint': + 'Add a check-in or write a journal entry to get started', + 'journey.status.draft': 'Draft', + 'journey.status.active': 'Active', + 'journey.status.completed': 'Completed', + 'journey.status.upcoming': 'Upcoming', + 'journey.status.archived': 'Archived', + 'journey.checkin.add': 'Check in', + 'journey.checkin.namePlaceholder': 'Location name', + 'journey.checkin.notesPlaceholder': 'Notes (optional)', + 'journey.checkin.save': 'Save', + 'journey.checkin.error': 'Could not save check-in', + 'journey.entry.add': 'Journal', + 'journey.entry.edit': 'Edit entry', + 'journey.entry.titlePlaceholder': 'Title (optional)', + 'journey.entry.bodyPlaceholder': 'What happened today?', + 'journey.entry.save': 'Save', + 'journey.entry.error': 'Could not save entry', + 'journey.photo.add': 'Photo', + 'journey.photo.uploadError': 'Upload failed', + 'journey.share.share': 'Share', + 'journey.share.public': 'Public', + 'journey.share.linkCopied': 'Public link copied', + 'journey.share.disabled': 'Public sharing disabled', + 'journey.editor.titlePlaceholder': 'Give this moment a name...', + 'journey.editor.bodyPlaceholder': 'Tell the story of this day...', + 'journey.editor.placePlaceholder': 'Location (optional)', + 'journey.editor.tagsPlaceholder': + 'Tags: hidden gem, best meal, must revisit...', + 'journey.visibility.private': 'Private', + 'journey.visibility.shared': 'Shared', + 'journey.visibility.public': 'Public', + 'journey.emptyState.title': 'Your story starts here', + 'journey.emptyState.subtitle': + 'Check in at a place or write your first journal entry', + 'journey.frontpage.subtitle': + "Turn your trips into stories you'll never forget", + 'journey.frontpage.createJourney': 'Create Journey', + 'journey.frontpage.activeJourney': 'Active Journey', + 'journey.frontpage.allJourneys': 'All Journeys', + 'journey.frontpage.journeys': 'journeys', + 'journey.frontpage.createNew': 'Create a new Journey', + 'journey.frontpage.createNewSub': + 'Pick trips, write stories, share your adventures', + 'journey.frontpage.live': 'Live', + 'journey.frontpage.synced': 'Synced', + 'journey.frontpage.continueWriting': 'Continue writing', + 'journey.frontpage.updated': 'Updated {time}', + 'journey.frontpage.suggestionLabel': 'Trip just ended', + 'journey.frontpage.suggestionText': + 'Turn {title} into a Journey', + 'journey.frontpage.dismiss': 'Dismiss', + 'journey.frontpage.journeyName': 'Journey Name', + 'journey.frontpage.namePlaceholder': 'e.g. Southeast Asia 2026', + 'journey.frontpage.selectTrips': 'Select Trips', + 'journey.frontpage.tripsSelected': 'trips selected', + 'journey.frontpage.trips': 'trips', + 'journey.frontpage.placesImported': 'places will be imported', + 'journey.frontpage.places': 'places', + 'journey.detail.backToJourney': 'Back to Journey', + 'journey.detail.syncedWithTrips': 'Synced with Trips', + 'journey.detail.addEntry': 'Add Entry', + 'journey.detail.newEntry': 'New Entry', + 'journey.detail.editEntry': 'Edit Entry', + 'journey.detail.noEntries': 'No entries yet', + 'journey.detail.noEntriesHint': + 'Add a trip to get started with skeleton entries', + 'journey.detail.noPhotos': 'No photos yet', + 'journey.detail.noPhotosHint': + 'Upload photos to entries or browse your Immich/Synology library', + 'journey.detail.journeyTab': 'Journey', + 'journey.detail.journeyStats': 'Journey Stats', + 'journey.detail.syncedTrips': 'Synced Trips', + 'journey.detail.noTripsLinked': 'No trips linked yet', + 'journey.detail.contributors': 'Contributors', + 'journey.detail.readMore': 'Read more', + 'journey.detail.prosCons': 'Pros & Cons', + 'journey.detail.photos': 'photos', + 'journey.detail.day': 'Day {number}', + 'journey.detail.places': 'places', + 'journey.stats.days': 'Days', + 'journey.stats.cities': 'Cities', + 'journey.stats.entries': 'Entries', + 'journey.stats.photos': 'Photos', + 'journey.stats.places': 'Places', + 'journey.skeletons.show': 'Show suggestions', + 'journey.skeletons.hide': 'Hide suggestions', + 'journey.verdict.lovedIt': 'Loved it', + 'journey.verdict.couldBeBetter': 'Could be better', + 'journey.synced.places': 'places', + 'journey.synced.synced': 'synced', + 'journey.editor.discardChangesConfirm': + 'You have unsaved changes. Discard them?', + 'journey.editor.uploadFailed': 'Photo upload failed', + 'journey.editor.uploadPhotos': 'Upload photos', + 'journey.editor.uploading': 'Uploading...', + 'journey.editor.uploadingProgress': 'Uploading {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} of {total} photos failed — save again to retry', + 'journey.editor.fromGallery': 'From Gallery', + 'journey.editor.allPhotosAdded': 'All photos already added', + 'journey.editor.writeStory': 'Write your story...', + 'journey.editor.prosCons': 'Pros & Cons', + 'journey.editor.pros': 'Pros', + 'journey.editor.cons': 'Cons', + 'journey.editor.proPlaceholder': 'Something great...', + 'journey.editor.conPlaceholder': 'Not so great...', + 'journey.editor.addAnother': 'Add another', + 'journey.editor.date': 'Date', + 'journey.editor.location': 'Location', + 'journey.editor.searchLocation': 'Search location...', + 'journey.editor.mood': 'Mood', + 'journey.editor.weather': 'Weather', + 'journey.editor.photoFirst': '1st', + 'journey.editor.makeFirst': 'Make 1st', + 'journey.editor.searching': 'Searching...', + 'journey.mood.amazing': 'Amazing', + 'journey.mood.good': 'Good', + 'journey.mood.neutral': 'Neutral', + 'journey.mood.rough': 'Rough', + 'journey.weather.sunny': 'Sunny', + 'journey.weather.partly': 'Partly cloudy', + 'journey.weather.cloudy': 'Cloudy', + 'journey.weather.rainy': 'Rainy', + 'journey.weather.stormy': 'Stormy', + 'journey.weather.cold': 'Snowy', + 'journey.trips.linkTrip': 'Link Trip', + 'journey.trips.searchTrip': 'Search Trip', + 'journey.trips.searchPlaceholder': 'Trip name or destination...', + 'journey.trips.noTripsAvailable': 'No trips available', + 'journey.trips.link': 'Link', + 'journey.trips.tripLinked': 'Trip linked', + 'journey.trips.linkFailed': 'Failed to link trip', + 'journey.trips.addTrip': 'Add Trip', + 'journey.trips.unlinkTrip': 'Unlink Trip', + 'journey.trips.unlinkMessage': + 'Unlink "{title}"? All synced entries and photos from this trip will be permanently deleted. This cannot be undone.', + 'journey.trips.unlink': 'Unlink', + 'journey.trips.tripUnlinked': 'Trip unlinked', + 'journey.trips.unlinkFailed': 'Failed to unlink trip', + 'journey.trips.noTripsLinkedSettings': 'No trips linked', + 'journey.contributors.invite': 'Invite Contributor', + 'journey.contributors.searchUser': 'Search User', + 'journey.contributors.searchPlaceholder': 'Username or email...', + 'journey.contributors.noUsers': 'No users found', + 'journey.contributors.role': 'Role', + 'journey.contributors.added': 'Contributor added', + 'journey.contributors.addFailed': 'Failed to add contributor', + 'journey.contributors.remove': 'Remove contributor', + 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', + 'journey.contributors.removed': 'Contributor removed', + 'journey.contributors.removeFailed': 'Failed to remove contributor', + 'journey.share.publicShare': 'Public Share', + 'journey.share.createLink': 'Create share link', + 'journey.share.linkCreated': 'Share link created', + 'journey.share.createFailed': 'Failed to create link', + 'journey.share.copy': 'Copy', + 'journey.share.copied': 'Copied!', + 'journey.share.timeline': 'Timeline', + 'journey.share.gallery': 'Gallery', + 'journey.share.map': 'Map', + 'journey.share.removeLink': 'Remove share link', + 'journey.share.linkDeleted': 'Share link deleted', + 'journey.share.deleteFailed': 'Failed to delete', + 'journey.share.updateFailed': 'Failed to update', + 'journey.invite.role': 'Role', + 'journey.invite.viewer': 'Viewer', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Invite', + 'journey.invite.inviting': 'Inviting...', + 'journey.settings.title': 'Journey Settings', + 'journey.settings.coverImage': 'Cover Image', + 'journey.settings.changeCover': 'Change cover', + 'journey.settings.addCover': 'Add cover image', + 'journey.settings.name': 'Name', + 'journey.settings.subtitle': 'Subtitle', + 'journey.settings.subtitlePlaceholder': 'e.g. Thailand, Vietnam & Cambodia', + 'journey.settings.endJourney': 'Archive Journey', + 'journey.settings.reopenJourney': 'Restore Journey', + 'journey.settings.archived': 'Journey archived', + 'journey.settings.reopened': 'Journey reopened', + 'journey.settings.endDescription': + 'Hides the Live badge. You can reopen anytime.', + 'journey.settings.delete': 'Delete', + 'journey.settings.deleteJourney': 'Delete Journey', + 'journey.settings.deleteMessage': + 'Delete "{title}"? All entries and photos will be lost.', + 'journey.settings.saved': 'Settings saved', + 'journey.settings.saveFailed': 'Failed to save', + 'journey.settings.coverUpdated': 'Cover updated', + 'journey.settings.coverFailed': 'Upload failed', + 'journey.settings.failedToDelete': 'Failed to delete', + 'journey.entries.deleteTitle': 'Delete Entry', + 'journey.photosUploaded': '{count} photos uploaded', + 'journey.photosUploadFailed': 'Some photos failed to upload', + 'journey.photosAdded': '{count} photos added', + 'journey.public.notFound': 'Not Found', + 'journey.public.notFoundMessage': + "This journey doesn't exist or the link has expired.", + 'journey.public.readOnly': 'Read-only · Public Journey', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Shared via', + 'journey.public.madeWith': 'Made with', + 'journey.pdf.journeyBook': 'Journey Book', + 'journey.pdf.madeWith': 'Made with TREK', + 'journey.pdf.day': 'Day', + 'journey.pdf.theEnd': 'The End', + 'journey.pdf.saveAsPdf': 'Save as PDF', + 'journey.pdf.pages': 'pages', + 'journey.picker.tripPeriod': 'Trip Period', + 'journey.picker.dateRange': 'Date Range', + 'journey.picker.allPhotos': 'All Photos', + 'journey.picker.albums': 'Albums', + 'journey.picker.selected': 'selected', + 'journey.picker.addTo': 'Add to', + 'journey.picker.newGallery': 'New Gallery', + 'journey.picker.selectAll': 'Select all', + 'journey.picker.deselectAll': 'Deselect all', + 'journey.picker.noAlbums': 'No albums found', + 'journey.picker.selectDate': 'Select date', + 'journey.picker.search': 'Search', +}; +export default journey; diff --git a/shared/src/i18n/en/login.ts b/shared/src/i18n/en/login.ts new file mode 100644 index 00000000..26f1263e --- /dev/null +++ b/shared/src/i18n/en/login.ts @@ -0,0 +1,95 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Login failed. Please check your credentials.', + 'login.tagline': 'Your Trips.\nYour Plan.', + 'login.description': + 'Plan trips collaboratively with interactive maps, budgets, and real-time sync.', + 'login.features.maps': 'Interactive Maps', + 'login.features.mapsDesc': 'Google Places, routes & clustering', + 'login.features.realtime': 'Real-Time Sync', + 'login.features.realtimeDesc': 'Plan together via WebSocket', + 'login.features.budget': 'Budget Tracking', + 'login.features.budgetDesc': 'Categories, charts & per-person costs', + 'login.features.collab': 'Collaboration', + 'login.features.collabDesc': 'Multi-user with shared trips', + 'login.features.packing': 'Packing Lists', + 'login.features.packingDesc': 'Categories, progress & suggestions', + 'login.features.bookings': 'Reservations', + 'login.features.bookingsDesc': 'Flights, hotels, restaurants & more', + 'login.features.files': 'Documents', + 'login.features.filesDesc': 'Upload & manage documents', + 'login.features.routes': 'Smart Routes', + 'login.features.routesDesc': 'Auto-optimize & Google Maps export', + 'login.selfHosted': 'Self-hosted · Open Source · Your data stays yours', + 'login.title': 'Sign In', + 'login.subtitle': 'Welcome back', + 'login.signingIn': 'Signing in…', + 'login.signIn': 'Sign In', + 'login.createAdmin': 'Create Admin Account', + 'login.createAdminHint': 'Set up the first admin account for TREK.', + 'login.setNewPassword': 'Set New Password', + 'login.setNewPasswordHint': + 'You must change your password before continuing.', + 'login.createAccount': 'Create Account', + 'login.createAccountHint': 'Register a new account.', + 'login.creating': 'Creating…', + 'login.noAccount': "Don't have an account?", + 'login.hasAccount': 'Already have an account?', + 'login.register': 'Register', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': 'Username', + 'login.oidc.registrationDisabled': + 'Registration is disabled. Contact your administrator.', + 'login.oidc.noEmail': 'No email received from provider.', + 'login.oidc.tokenFailed': 'Authentication failed.', + 'login.oidc.invalidState': 'Invalid session. Please try again.', + 'login.demoFailed': 'Demo login failed', + 'login.oidcSignIn': 'Sign in with {name}', + 'login.oidcOnly': + 'Password authentication is disabled. Please sign in using your SSO provider.', + 'login.oidcLoggedOut': + 'You have been logged out. Sign in again using your SSO provider.', + 'login.demoHint': 'Try the demo — no registration needed', + 'login.mfaTitle': 'Two-factor authentication', + 'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.', + 'login.mfaCodeLabel': 'Verification code', + 'login.mfaCodeRequired': 'Enter the code from your authenticator app.', + 'login.mfaHint': 'Open Google Authenticator, Authy, or another TOTP app.', + 'login.mfaBack': '← Back to sign in', + 'login.mfaVerify': 'Verify', + 'login.invalidInviteLink': 'Invalid or expired invite link', + 'login.oidcFailed': 'OIDC login failed', + 'login.usernameRequired': 'Username is required', + 'login.passwordMinLength': 'Password must be at least 8 characters', + 'login.forgotPassword': 'Forgot password?', + 'login.forgotPasswordTitle': 'Reset your password', + 'login.forgotPasswordBody': + "Enter the email address you signed up with. If an account exists, we'll send a reset link.", + 'login.forgotPasswordSubmit': 'Send reset link', + 'login.forgotPasswordSentTitle': 'Check your email', + 'login.forgotPasswordSentBody': + 'If an account exists for that email, a reset link is on its way. It expires in 60 minutes.', + 'login.forgotPasswordSmtpHintOff': + "Heads up: your administrator hasn't configured SMTP, so the reset link will be written to the server console instead of being emailed.", + 'login.backToLogin': 'Back to sign in', + 'login.newPassword': 'New password', + 'login.confirmPassword': 'Confirm new password', + 'login.passwordsDontMatch': "Passwords don't match", + 'login.mfaCode': '2FA code', + 'login.resetPasswordTitle': 'Set a new password', + 'login.resetPasswordBody': + 'Pick a strong password you haven’t used here before. Minimum 8 characters.', + 'login.resetPasswordMfaBody': + 'Enter your 2FA code or a backup code to complete the reset.', + 'login.resetPasswordSubmit': 'Reset password', + 'login.resetPasswordVerify': 'Verify & reset', + 'login.resetPasswordSuccessTitle': 'Password updated', + 'login.resetPasswordSuccessBody': + 'You can now sign in with your new password.', + 'login.resetPasswordInvalidLink': 'Invalid reset link', + 'login.resetPasswordInvalidLinkBody': + 'This link is missing or broken. Request a new one to continue.', + 'login.resetPasswordFailed': 'Reset failed. The link may have expired.', +}; +export default login; diff --git a/shared/src/i18n/en/map.ts b/shared/src/i18n/en/map.ts new file mode 100644 index 00000000..836e5a4a --- /dev/null +++ b/shared/src/i18n/en/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Connections', + 'map.showConnections': 'Show booking routes', + 'map.hideConnections': 'Hide booking routes', +}; +export default map; diff --git a/shared/src/i18n/en/members.ts b/shared/src/i18n/en/members.ts new file mode 100644 index 00000000..dc2319b2 --- /dev/null +++ b/shared/src/i18n/en/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Share Trip', + 'members.inviteUser': 'Invite User', + 'members.selectUser': 'Select user…', + 'members.invite': 'Invite', + 'members.allHaveAccess': 'All users already have access.', + 'members.access': 'Access', + 'members.person': 'person', + 'members.persons': 'persons', + 'members.you': 'you', + 'members.owner': 'Owner', + 'members.leaveTrip': 'Leave trip', + 'members.removeAccess': 'Remove access', + 'members.confirmLeave': 'Leave trip? You will lose access.', + 'members.confirmRemove': 'Remove access for this user?', + 'members.loadError': 'Failed to load members', + 'members.added': 'added', + 'members.addError': 'Failed to add', + 'members.removed': 'Member removed', + 'members.removeError': 'Failed to remove', +}; +export default members; diff --git a/shared/src/i18n/en/memories.ts b/shared/src/i18n/en/memories.ts new file mode 100644 index 00000000..b0a349cc --- /dev/null +++ b/shared/src/i18n/en/memories.ts @@ -0,0 +1,80 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Photos', + 'memories.notConnected': '{provider_name} not connected', + 'memories.notConnectedHint': + 'Connect your {provider_name} instance in Settings to be able add photos to this trip.', + 'memories.notConnectedMultipleHint': + 'Connect any of these photo providers: {provider_names} in Settings to be able add photos to this trip.', + 'memories.noDates': 'Add dates to your trip to load photos.', + 'memories.noPhotos': 'No photos found', + 'memories.noPhotosHint': + "No photos found in {provider_name} for this trip's date range.", + 'memories.photosFound': 'photos', + 'memories.fromOthers': 'from others', + 'memories.sharePhotos': 'Share photos', + 'memories.sharing': 'Sharing', + 'memories.reviewTitle': 'Review your photos', + 'memories.reviewHint': 'Click photos to exclude them from sharing.', + 'memories.shareCount': 'Share {count} photos', + 'memories.providerUrl': 'Server URL', + 'memories.providerApiKey': 'API Key', + 'memories.providerUsername': 'Username', + 'memories.providerPassword': 'Password', + 'memories.providerOTP': 'MFA code (if enabled)', + 'memories.skipSSLVerification': 'Skip SSL certificate verification', + 'memories.immichAutoUpload': 'Mirror journey photos to Immich on upload', + 'memories.providerUrlHintSynology': + 'Include the Photos app path in the URL, e.g. https://nas:5001/photo', + 'memories.testConnection': 'Test connection', + 'memories.testShort': 'Test', + 'memories.testFirst': 'Test connection first', + 'memories.connected': 'Connected', + 'memories.disconnected': 'Not connected', + 'memories.connectionSuccess': 'Connected to {provider_name}', + 'memories.connectionError': 'Could not connect to {provider_name}', + 'memories.saved': '{provider_name} settings saved', + 'memories.providerDisconnectedBanner': + 'Your {provider_name} connection is lost. Reconnect in Settings to view photos.', + 'memories.saveError': 'Could not save {provider_name} settings', + 'memories.addPhotos': 'Add photos', + 'memories.linkAlbum': 'Link Album', + 'memories.selectAlbum': 'Select {provider_name} Album', + 'memories.selectAlbumMultiple': 'Select Album', + 'memories.noAlbums': 'No albums found', + 'memories.syncAlbum': 'Sync album', + 'memories.unlinkAlbum': 'Unlink album', + 'memories.photos': 'photos', + 'memories.selectPhotos': 'Select photos from {provider_name}', + 'memories.selectPhotosMultiple': 'Select Photos', + 'memories.selectHint': 'Tap photos to select them.', + 'memories.selected': 'selected', + 'memories.addSelected': 'Add {count} photos', + 'memories.alreadyAdded': 'Added', + 'memories.private': 'Private', + 'memories.stopSharing': 'Stop sharing', + 'memories.oldest': 'Oldest first', + 'memories.newest': 'Newest first', + 'memories.allLocations': 'All locations', + 'memories.tripDates': 'Trip dates', + 'memories.allPhotos': 'All photos', + 'memories.confirmShareTitle': 'Share with trip members?', + 'memories.confirmShareHint': + '{count} photos will be visible to all members of this trip. You can make individual photos private later.', + 'memories.confirmShareButton': 'Share photos', + 'memories.error.loadAlbums': 'Failed to load albums', + 'memories.error.linkAlbum': 'Failed to link album', + 'memories.error.unlinkAlbum': 'Failed to unlink album', + 'memories.error.syncAlbum': 'Failed to sync album', + 'memories.error.loadPhotos': 'Failed to load photos', + 'memories.error.addPhotos': 'Failed to add photos', + 'memories.error.removePhoto': 'Failed to remove photo', + 'memories.error.toggleSharing': 'Failed to update sharing', + 'memories.saveRouteNotConfigured': + 'Save route is not configured for this provider', + 'memories.testRouteNotConfigured': + 'Test route is not configured for this provider', + 'memories.fillRequiredFields': 'Please fill all required fields', +}; +export default memories; diff --git a/shared/src/i18n/en/nav.ts b/shared/src/i18n/en/nav.ts new file mode 100644 index 00000000..e57e970e --- /dev/null +++ b/shared/src/i18n/en/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Trip', + 'nav.share': 'Share', + 'nav.settings': 'Settings', + 'nav.admin': 'Admin', + 'nav.logout': 'Log out', + 'nav.lightMode': 'Light Mode', + 'nav.darkMode': 'Dark Mode', + 'nav.autoMode': 'Auto Mode', + 'nav.administrator': 'Administrator', + 'nav.myTrips': 'My Trips', + 'nav.profile': 'Profile', + 'nav.bottomSettings': 'Settings', + 'nav.bottomAdmin': 'Admin Settings', + 'nav.bottomLogout': 'Logout', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/en/notif.ts b/shared/src/i18n/en/notif.ts new file mode 100644 index 00000000..43e55a4c --- /dev/null +++ b/shared/src/i18n/en/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notification', + 'notif.test.simple.text': 'This is a simple test notification.', + 'notif.test.boolean.text': 'Do you accept this test notification?', + 'notif.test.navigate.text': 'Click below to navigate to the dashboard.', + 'notif.trip_invite.title': 'Trip Invitation', + 'notif.trip_invite.text': '{actor} invited you to {trip}', + 'notif.booking_change.title': 'Booking Updated', + 'notif.booking_change.text': '{actor} updated a booking in {trip}', + 'notif.trip_reminder.title': 'Trip Reminder', + 'notif.trip_reminder.text': 'Your trip {trip} is coming up soon!', + 'notif.todo_due.title': 'To-do due', + 'notif.todo_due.text': '{todo} in {trip} is due on {due}', + 'notif.vacay_invite.title': 'Vacay Fusion Invite', + 'notif.vacay_invite.text': '{actor} invited you to fuse vacation plans', + 'notif.photos_shared.title': 'Photos Shared', + 'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}', + 'notif.collab_message.title': 'New Message', + 'notif.collab_message.text': '{actor} sent a message in {trip}', + 'notif.packing_tagged.title': 'Packing Assignment', + 'notif.packing_tagged.text': '{actor} assigned you to {category} in {trip}', + 'notif.version_available.title': 'New Version Available', + 'notif.version_available.text': 'TREK {version} is now available', + 'notif.action.view_trip': 'View Trip', + 'notif.action.view_collab': 'View Messages', + 'notif.action.view_packing': 'View Packing', + 'notif.action.view_photos': 'View Photos', + 'notif.action.view_vacay': 'View Vacay', + 'notif.action.view_admin': 'Go to Admin', + 'notif.action.view': 'View', + 'notif.action.accept': 'Accept', + 'notif.action.decline': 'Decline', + 'notif.generic.title': 'Notification', + 'notif.generic.text': 'You have a new notification', + 'notif.dev.unknown_event.title': '[DEV] Unknown Event', + 'notif.dev.unknown_event.text': + 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/en/notifications.ts b/shared/src/i18n/en/notifications.ts new file mode 100644 index 00000000..1b5df5ae --- /dev/null +++ b/shared/src/i18n/en/notifications.ts @@ -0,0 +1,38 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notifications', + 'notifications.markAllRead': 'Mark all read', + 'notifications.deleteAll': 'Delete all', + 'notifications.showAll': 'Show all notifications', + 'notifications.empty': 'No notifications', + 'notifications.emptyDescription': "You're all caught up!", + 'notifications.all': 'All', + 'notifications.unreadOnly': 'Unread', + 'notifications.markRead': 'Mark as read', + 'notifications.markUnread': 'Mark as unread', + 'notifications.delete': 'Delete', + 'notifications.system': 'System', + 'notifications.synologySessionCleared.title': 'Synology Photos disconnected', + 'notifications.synologySessionCleared.text': + 'Your server or account changed — go to Settings to test your connection again.', + 'notifications.versionAvailable.title': 'Update Available', + 'notifications.versionAvailable.text': 'TREK {version} is now available.', + 'notifications.versionAvailable.button': 'View Details', + 'notifications.test.title': 'Test notification from {actor}', + 'notifications.test.text': 'This is a simple test notification.', + 'notifications.test.booleanTitle': '{actor} asks for your approval', + 'notifications.test.booleanText': + 'This is a test boolean notification. Choose an action below.', + 'notifications.test.accept': 'Approve', + 'notifications.test.decline': 'Decline', + 'notifications.test.navigateTitle': 'Check something out', + 'notifications.test.navigateText': 'This is a test navigate notification.', + 'notifications.test.goThere': 'Go there', + 'notifications.test.adminTitle': 'Admin broadcast', + 'notifications.test.adminText': + '{actor} sent a test notification to all admins.', + 'notifications.test.tripTitle': '{actor} posted in your trip', + 'notifications.test.tripText': 'Test notification for trip "{trip}".', +}; +export default notifications; diff --git a/shared/src/i18n/en/oauth.ts b/shared/src/i18n/en/oauth.ts new file mode 100644 index 00000000..ef4f8f5c --- /dev/null +++ b/shared/src/i18n/en/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Trips', + 'oauth.scope.group.places': 'Places', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Packing', + 'oauth.scope.group.todos': 'To-dos', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Reservations', + 'oauth.scope.group.collab': 'Collaboration', + 'oauth.scope.group.notifications': 'Notifications', + 'oauth.scope.group.vacay': 'Vacation', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Weather', + 'oauth.scope.group.journey': 'Journey', + 'oauth.scope.trips:read.label': 'View trips & itineraries', + 'oauth.scope.trips:read.description': + 'Read trips, days, day notes, and members', + 'oauth.scope.trips:write.label': 'Edit trips & itineraries', + 'oauth.scope.trips:write.description': + 'Create and update trips, days, notes, and manage members', + 'oauth.scope.trips:delete.label': 'Delete trips', + 'oauth.scope.trips:delete.description': + 'Permanently delete entire trips — this action is irreversible', + 'oauth.scope.trips:share.label': 'Manage share links', + 'oauth.scope.trips:share.description': + 'Create, update, and revoke public share links for trips', + 'oauth.scope.places:read.label': 'View places & map data', + 'oauth.scope.places:read.description': + 'Read places, day assignments, tags, and categories', + 'oauth.scope.places:write.label': 'Manage places', + 'oauth.scope.places:write.description': + 'Create, update, and delete places, assignments, and tags', + 'oauth.scope.atlas:read.label': 'View Atlas', + 'oauth.scope.atlas:read.description': + 'Read visited countries, regions, and bucket list', + 'oauth.scope.atlas:write.label': 'Manage Atlas', + 'oauth.scope.atlas:write.description': + 'Mark countries and regions visited, manage bucket list', + 'oauth.scope.packing:read.label': 'View packing lists', + 'oauth.scope.packing:read.description': + 'Read packing items, bags, and category assignees', + 'oauth.scope.packing:write.label': 'Manage packing lists', + 'oauth.scope.packing:write.description': + 'Add, update, delete, toggle, and reorder packing items and bags', + 'oauth.scope.todos:read.label': 'View to-do lists', + 'oauth.scope.todos:read.description': + 'Read trip to-do items and category assignees', + 'oauth.scope.todos:write.label': 'Manage to-do lists', + 'oauth.scope.todos:write.description': + 'Create, update, toggle, delete, and reorder to-do items', + 'oauth.scope.budget:read.label': 'View budget', + 'oauth.scope.budget:read.description': + 'Read budget items and expense breakdown', + 'oauth.scope.budget:write.label': 'Manage budget', + 'oauth.scope.budget:write.description': + 'Create, update, and delete budget items', + 'oauth.scope.reservations:read.label': 'View reservations', + 'oauth.scope.reservations:read.description': + 'Read reservations and accommodation details', + 'oauth.scope.reservations:write.label': 'Manage reservations', + 'oauth.scope.reservations:write.description': + 'Create, update, delete, and reorder reservations', + 'oauth.scope.collab:read.label': 'View collaboration', + 'oauth.scope.collab:read.description': + 'Read collab notes, polls, and messages', + 'oauth.scope.collab:write.label': 'Manage collaboration', + 'oauth.scope.collab:write.description': + 'Create, update, and delete collab notes, polls, and messages', + 'oauth.scope.notifications:read.label': 'View notifications', + 'oauth.scope.notifications:read.description': + 'Read in-app notifications and unread counts', + 'oauth.scope.notifications:write.label': 'Manage notifications', + 'oauth.scope.notifications:write.description': + 'Mark notifications as read and respond to them', + 'oauth.scope.vacay:read.label': 'View vacation plans', + 'oauth.scope.vacay:read.description': + 'Read vacation planning data, entries, and stats', + 'oauth.scope.vacay:write.label': 'Manage vacation plans', + 'oauth.scope.vacay:write.description': + 'Create and manage vacation entries, holidays, and team plans', + 'oauth.scope.geo:read.label': 'Maps & geocoding', + 'oauth.scope.geo:read.description': + 'Search locations, resolve map URLs, and reverse geocode coordinates', + 'oauth.scope.weather:read.label': 'Weather forecasts', + 'oauth.scope.weather:read.description': + 'Fetch weather forecasts for trip locations and dates', + 'oauth.scope.journey:read.label': 'View journeys', + 'oauth.scope.journey:read.description': + 'Read journeys, entries, and contributor list', + 'oauth.scope.journey:write.label': 'Manage journeys', + 'oauth.scope.journey:write.description': + 'Create, update, and delete journeys and their entries', + 'oauth.scope.journey:share.label': 'Manage journey links', + 'oauth.scope.journey:share.description': + 'Create, update, and revoke public share links for journeys', +}; +export default oauth; diff --git a/shared/src/i18n/en/packing.ts b/shared/src/i18n/en/packing.ts new file mode 100644 index 00000000..b57c355d --- /dev/null +++ b/shared/src/i18n/en/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Packing List', + 'packing.empty': 'Packing list is empty', + 'packing.import': 'Import', + 'packing.importTitle': 'Import Packing List', + 'packing.importHint': + 'One item per line. Format: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional)', + 'packing.importPlaceholder': + 'Hygiene, Toothbrush\nClothing, T-Shirts, 200\nDocuments, Passport, , Carry-on\nElectronics, Charger, 50, Suitcase, checked', + 'packing.importCsv': 'Load CSV/TXT', + 'packing.importAction': 'Import {count}', + 'packing.importSuccess': '{count} items imported', + 'packing.importError': 'Import failed', + 'packing.importEmpty': 'No items to import', + 'packing.progress': '{packed} of {total} packed ({percent}%)', + 'packing.clearChecked': 'Remove {count} checked', + 'packing.clearCheckedShort': 'Remove {count}', + 'packing.suggestions': 'Suggestions', + 'packing.suggestionsTitle': 'Add Suggestions', + 'packing.allSuggested': 'All suggestions added', + 'packing.allPacked': 'All packed!', + 'packing.addPlaceholder': 'Add new item...', + 'packing.categoryPlaceholder': 'Category...', + 'packing.filterAll': 'All', + 'packing.filterOpen': 'Open', + 'packing.filterDone': 'Done', + 'packing.emptyTitle': 'Packing list is empty', + 'packing.emptyHint': 'Add items or use the suggestions', + 'packing.emptyFiltered': 'No items match this filter', + 'packing.menuRename': 'Rename', + 'packing.menuCheckAll': 'Check All', + 'packing.menuUncheckAll': 'Uncheck All', + 'packing.menuDeleteCat': 'Delete Category', + 'packing.noMembers': 'No trip members', + 'packing.addItem': 'Add item', + 'packing.addItemPlaceholder': 'Item name...', + 'packing.addCategory': 'Add category', + 'packing.newCategoryPlaceholder': 'Category name (e.g. Clothing)', + 'packing.applyTemplate': 'Apply template', + 'packing.template': 'Template', + 'packing.templateApplied': '{count} items added from template', + 'packing.templateError': 'Failed to apply template', + 'packing.saveAsTemplate': 'Save as template', + 'packing.templateName': 'Template name', + 'packing.templateSaved': 'Packing list saved as template', + 'packing.bags': 'Bags', + 'packing.noBag': 'Unassigned', + 'packing.totalWeight': 'Total weight', + 'packing.bagName': 'Bag name...', + 'packing.addBag': 'Add bag', + 'packing.changeCategory': 'Change Category', + 'packing.confirm.clearChecked': + 'Are you sure you want to remove {count} checked items?', + 'packing.confirm.deleteCat': + 'Are you sure you want to delete the category "{name}" with {count} items?', + 'packing.defaultCategory': 'Other', + 'packing.toast.saveError': 'Failed to save', + 'packing.toast.deleteError': 'Failed to delete', + 'packing.toast.renameError': 'Failed to rename', + 'packing.toast.addError': 'Failed to add', + 'packing.suggestions.items': [ + { + name: 'Passport', + category: 'Documents', + }, + { + name: 'ID Card', + category: 'Documents', + }, + { + name: 'Travel Insurance', + category: 'Documents', + }, + { + name: 'Flight Tickets', + category: 'Documents', + }, + { + name: 'Credit Card', + category: 'Finances', + }, + { + name: 'Cash', + category: 'Finances', + }, + { + name: 'Visa', + category: 'Documents', + }, + { + name: 'T-Shirts', + category: 'Clothing', + }, + { + name: 'Pants', + category: 'Clothing', + }, + { + name: 'Underwear', + category: 'Clothing', + }, + { + name: 'Socks', + category: 'Clothing', + }, + { + name: 'Jacket', + category: 'Clothing', + }, + { + name: 'Sleepwear', + category: 'Clothing', + }, + { + name: 'Swimwear', + category: 'Clothing', + }, + { + name: 'Rain Jacket', + category: 'Clothing', + }, + { + name: 'Comfortable Shoes', + category: 'Clothing', + }, + { + name: 'Toothbrush', + category: 'Toiletries', + }, + { + name: 'Toothpaste', + category: 'Toiletries', + }, + { + name: 'Shampoo', + category: 'Toiletries', + }, + { + name: 'Deodorant', + category: 'Toiletries', + }, + { + name: 'Sunscreen', + category: 'Toiletries', + }, + { + name: 'Razor', + category: 'Toiletries', + }, + { + name: 'Charger', + category: 'Electronics', + }, + { + name: 'Power Bank', + category: 'Electronics', + }, + { + name: 'Headphones', + category: 'Electronics', + }, + { + name: 'Travel Adapter', + category: 'Electronics', + }, + { + name: 'Camera', + category: 'Electronics', + }, + { + name: 'Pain Medication', + category: 'Health', + }, + { + name: 'Band-Aids', + category: 'Health', + }, + { + name: 'Disinfectant', + category: 'Health', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/en/pdf.ts b/shared/src/i18n/en/pdf.ts new file mode 100644 index 00000000..3ee899f6 --- /dev/null +++ b/shared/src/i18n/en/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Travel Plan', + 'pdf.planned': 'Planned', + 'pdf.costLabel': 'Cost EUR', + 'pdf.preview': 'PDF Preview', + 'pdf.saveAsPdf': 'Save as PDF', +}; +export default pdf; diff --git a/shared/src/i18n/en/perm.ts b/shared/src/i18n/en/perm.ts new file mode 100644 index 00000000..a33dbd16 --- /dev/null +++ b/shared/src/i18n/en/perm.ts @@ -0,0 +1,57 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Permission Settings', + 'perm.subtitle': 'Control who can perform actions across the application', + 'perm.saved': 'Permission settings saved', + 'perm.resetDefaults': 'Reset to defaults', + 'perm.customized': 'customized', + 'perm.level.admin': 'Admin only', + 'perm.level.tripOwner': 'Trip owner', + 'perm.level.tripMember': 'Trip members', + 'perm.level.everybody': 'Everyone', + 'perm.cat.trip': 'Trip Management', + 'perm.cat.members': 'Member Management', + 'perm.cat.files': 'Files', + 'perm.cat.content': 'Content & Schedule', + 'perm.cat.extras': 'Budget, Packing & Collaboration', + 'perm.action.trip_create': 'Create trips', + 'perm.action.trip_edit': 'Edit trip details', + 'perm.action.trip_delete': 'Delete trips', + 'perm.action.trip_archive': 'Archive / unarchive trips', + 'perm.action.trip_cover_upload': 'Upload cover image', + 'perm.action.member_manage': 'Add / remove members', + 'perm.action.file_upload': 'Upload files', + 'perm.action.file_edit': 'Edit file metadata', + 'perm.action.file_delete': 'Delete files', + 'perm.action.place_edit': 'Add / edit / delete places', + 'perm.action.day_edit': 'Edit days, notes & assignments', + 'perm.action.reservation_edit': 'Manage reservations', + 'perm.action.budget_edit': 'Manage budget', + 'perm.action.packing_edit': 'Manage packing lists', + 'perm.action.collab_edit': 'Collaboration (notes, polls, chat)', + 'perm.action.share_manage': 'Manage share links', + 'perm.actionHint.trip_create': 'Who can create new trips', + 'perm.actionHint.trip_edit': + 'Who can change trip name, dates, description and currency', + 'perm.actionHint.trip_delete': 'Who can permanently delete a trip', + 'perm.actionHint.trip_archive': 'Who can archive or unarchive a trip', + 'perm.actionHint.trip_cover_upload': + 'Who can upload or change the cover image', + 'perm.actionHint.member_manage': 'Who can invite or remove trip members', + 'perm.actionHint.file_upload': 'Who can upload files to a trip', + 'perm.actionHint.file_edit': 'Who can edit file descriptions and links', + 'perm.actionHint.file_delete': + 'Who can move files to trash or permanently delete them', + 'perm.actionHint.place_edit': 'Who can add, edit or delete places', + 'perm.actionHint.day_edit': + 'Who can edit days, day notes and place assignments', + 'perm.actionHint.reservation_edit': + 'Who can create, edit or delete reservations', + 'perm.actionHint.budget_edit': 'Who can create, edit or delete budget items', + 'perm.actionHint.packing_edit': 'Who can manage packing items and bags', + 'perm.actionHint.collab_edit': + 'Who can create notes, polls and send messages', + 'perm.actionHint.share_manage': 'Who can create or delete public share links', +}; +export default perm; diff --git a/shared/src/i18n/en/photos.ts b/shared/src/i18n/en/photos.ts new file mode 100644 index 00000000..622b6c71 --- /dev/null +++ b/shared/src/i18n/en/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Photos', + 'photos.subtitle': '{count} photos for {trip}', + 'photos.dropHere': 'Drop photos here...', + 'photos.dropHereActive': 'Drop photos here', + 'photos.captionForAll': 'Caption (for all)', + 'photos.captionPlaceholder': 'Optional caption...', + 'photos.addCaption': 'Add caption...', + 'photos.allDays': 'All Days', + 'photos.noPhotos': 'No photos yet', + 'photos.uploadHint': 'Upload your travel photos', + 'photos.clickToSelect': 'or click to select', + 'photos.linkPlace': 'Link Place', + 'photos.noPlace': 'No Place', + 'photos.uploadN': '{n} photo(s) upload', + 'photos.linkDay': 'Link Day', + 'photos.noDay': 'No Day', + 'photos.dayLabel': 'Day {number}', + 'photos.photoSelected': 'Photo selected', + 'photos.photosSelected': 'Photos selected', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · up to 30 photos', +}; +export default photos; diff --git a/shared/src/i18n/en/places.ts b/shared/src/i18n/en/places.ts new file mode 100644 index 00000000..9454fdb1 --- /dev/null +++ b/shared/src/i18n/en/places.ts @@ -0,0 +1,91 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Add Place/Activity', + 'places.importFile': 'Import file', + 'places.sidebarDrop': 'Drop to import', + 'places.importFileHint': + 'Import .gpx, .kml or .kmz files from tools like Google My Maps, Google Earth, or a GPS tracker.', + 'places.importFileDropHere': 'Click to select a file or drag and drop here', + 'places.importFileDropActive': 'Drop file to select', + 'places.importFileUnsupported': + 'Unsupported file type. Use .gpx, .kml or .kmz.', + 'places.importFileTooLarge': + 'File is too large. Maximum upload size is {maxMb} MB.', + 'places.importFileError': 'Import failed', + 'places.importAllSkipped': 'All places were already in the trip.', + 'places.gpxImported': '{count} places imported from GPX', + 'places.gpxImportTypes': 'What do you want to import?', + 'places.gpxImportWaypoints': 'Waypoints', + 'places.gpxImportRoutes': 'Routes', + 'places.gpxImportTracks': 'Tracks (with path geometry)', + 'places.gpxImportNoneSelected': 'Select at least one type to import.', + 'places.kmlImportTypes': 'What do you want to import?', + 'places.kmlImportPoints': 'Points (Placemarks)', + 'places.kmlImportPaths': 'Paths (LineStrings)', + 'places.kmlImportNoneSelected': 'Select at least one type to import.', + 'places.selectionCount': '{count} selected', + 'places.deleteSelected': 'Delete selected', + 'places.kmlKmzImported': '{count} places imported from KMZ/KML', + 'places.urlResolved': 'Place imported from URL', + 'places.importList': 'List Import', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Imported: {created} • Skipped: {skipped}', + 'places.importGoogleList': 'Google List', + 'places.importNaverList': 'Naver List', + 'places.googleListHint': + 'Paste a shared Google Maps list link to import all places.', + 'places.googleListImported': '{count} places imported from "{list}"', + 'places.googleListError': 'Failed to import Google Maps list', + 'places.naverListHint': + 'Paste a shared Naver Maps list link to import all places.', + 'places.naverListImported': '{count} places imported from "{list}"', + 'places.naverListError': 'Failed to import Naver Maps list', + 'places.viewDetails': 'View Details', + 'places.assignToDay': 'Add to which day?', + 'places.all': 'All', + 'places.unplanned': 'Unplanned', + 'places.filterTracks': 'Tracks', + 'places.search': 'Search places...', + 'places.allCategories': 'All Categories', + 'places.categoriesSelected': 'categories', + 'places.clearFilter': 'Clear filter', + 'places.count': '{count} places', + 'places.countSingular': '1 place', + 'places.allPlanned': 'All places are planned', + 'places.noneFound': 'No places found', + 'places.editPlace': 'Edit Place', + 'places.formName': 'Name', + 'places.formNamePlaceholder': 'e.g. Eiffel Tower', + 'places.formDescription': 'Description', + 'places.formDescriptionPlaceholder': 'Short description...', + 'places.formAddress': 'Address', + 'places.formAddressPlaceholder': 'Street, City, Country', + 'places.formLat': 'Latitude (e.g. 48.8566)', + 'places.formLng': 'Longitude (e.g. 2.3522)', + 'places.formCategory': 'Category', + 'places.noCategory': 'No Category', + 'places.categoryNamePlaceholder': 'Category name', + 'places.formTime': 'Time', + 'places.startTime': 'Start', + 'places.endTime': 'End', + 'places.endTimeBeforeStart': 'End time is before start time', + 'places.timeCollision': 'Time overlap with:', + 'places.formWebsite': 'Website', + 'places.formNotes': 'Notes', + 'places.formNotesPlaceholder': 'Personal notes...', + 'places.formReservation': 'Reservation', + 'places.reservationNotesPlaceholder': + 'Reservation notes, confirmation number...', + 'places.mapsSearchPlaceholder': 'Search places...', + 'places.mapsSearchError': 'Place search failed.', + 'places.loadingDetails': 'Loading place details…', + 'places.osmHint': + 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', + 'places.osmActive': + 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.', + 'places.categoryCreateError': 'Failed to create category', + 'places.nameRequired': 'Please enter a name', + 'places.saveError': 'Failed to save', +}; +export default places; diff --git a/shared/src/i18n/en/planner.ts b/shared/src/i18n/en/planner.ts new file mode 100644 index 00000000..259ff606 --- /dev/null +++ b/shared/src/i18n/en/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Places', + 'planner.bookings': 'Bookings', + 'planner.packingList': 'Packing List', + 'planner.documents': 'Documents', + 'planner.dayPlan': 'Day Plan', + 'planner.reservations': 'Reservations', + 'planner.minTwoPlaces': 'At least 2 places with coordinates needed', + 'planner.noGeoPlaces': 'No places with coordinates available', + 'planner.routeCalculated': 'Route calculated', + 'planner.routeCalcFailed': 'Route could not be calculated', + 'planner.routeError': 'Error calculating route', + 'planner.icsExportFailed': 'ICS export failed', + 'planner.routeOptimized': 'Route optimized', + 'planner.reservationUpdated': 'Reservation updated', + 'planner.reservationAdded': 'Reservation added', + 'planner.confirmDeleteReservation': 'Delete reservation?', + 'planner.reservationDeleted': 'Reservation deleted', + 'planner.days': 'Days', + 'planner.allPlaces': 'All Places', + 'planner.totalPlaces': '{n} places total', + 'planner.noDaysPlanned': 'No days planned yet', + 'planner.editTrip': 'Edit trip →', + 'planner.placeOne': '1 place', + 'planner.placeN': '{n} places', + 'planner.addNote': 'Add note', + 'planner.noEntries': 'No entries for this day', + 'planner.addPlace': 'Add place/activity', + 'planner.addPlaceShort': '+ Add place/activity', + 'planner.resPending': 'Reservation pending · ', + 'planner.resConfirmed': 'Reservation confirmed · ', + 'planner.notePlaceholder': 'Note…', + 'planner.noteTimePlaceholder': 'Time (optional)', + 'planner.noteExamplePlaceholder': + 'e.g. S3 at 14:30 from central station, ferry from pier 7, lunch break…', + 'planner.totalCost': 'Total cost', + 'planner.searchPlaces': 'Search places…', + 'planner.allCategories': 'All Categories', + 'planner.noPlacesFound': 'No places found', + 'planner.addFirstPlace': 'Add first place', + 'planner.noReservations': 'No reservations', + 'planner.addFirstReservation': 'Add first reservation', + 'planner.new': 'New', + 'planner.addToDay': '+ Day', + 'planner.calculating': 'Calculating…', + 'planner.route': 'Route', + 'planner.optimize': 'Optimize', + 'planner.openGoogleMaps': 'Open in Google Maps', + 'planner.selectDayHint': + 'Select a day from the left list to see the day plan', + 'planner.noPlacesForDay': 'No places for this day yet', + 'planner.addPlacesLink': 'Add places →', + 'planner.minTotal': 'min. total', + 'planner.noReservation': 'No reservation', + 'planner.removeFromDay': 'Remove from day', + 'planner.addToThisDay': 'Add to day', + 'planner.overview': 'Overview', + 'planner.noDays': 'No days yet', + 'planner.editTripToAddDays': 'Edit trip to add days', + 'planner.dayCount': '{n} Days', + 'planner.clickToUnlock': 'Click to unlock', + 'planner.keepPosition': 'Keep position during route optimization', + 'planner.dayDetails': 'Day details', + 'planner.dayN': 'Day {n}', +}; +export default planner; diff --git a/shared/src/i18n/en/register.ts b/shared/src/i18n/en/register.ts new file mode 100644 index 00000000..1b03d4dc --- /dev/null +++ b/shared/src/i18n/en/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Passwords do not match', + 'register.passwordTooShort': 'Password must be at least 8 characters', + 'register.failed': 'Registration failed', + 'register.getStarted': 'Get Started', + 'register.subtitle': 'Create an account and start planning your dream trips.', + 'register.feature1': 'Unlimited trip plans', + 'register.feature2': 'Interactive map view', + 'register.feature3': 'Manage places and categories', + 'register.feature4': 'Track reservations', + 'register.feature5': 'Create packing lists', + 'register.feature6': 'Store photos and files', + 'register.createAccount': 'Create Account', + 'register.startPlanning': 'Start your trip planning', + 'register.minChars': 'Min. 6 characters', + 'register.confirmPassword': 'Confirm Password', + 'register.repeatPassword': 'Repeat password', + 'register.registering': 'Registering...', + 'register.register': 'Register', + 'register.hasAccount': 'Already have an account?', + 'register.signIn': 'Sign In', +}; +export default register; diff --git a/shared/src/i18n/en/reservations.ts b/shared/src/i18n/en/reservations.ts new file mode 100644 index 00000000..be51e44e --- /dev/null +++ b/shared/src/i18n/en/reservations.ts @@ -0,0 +1,118 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Bookings', + 'reservations.empty': 'No reservations yet', + 'reservations.emptyHint': 'Add reservations for flights, hotels and more', + 'reservations.add': 'Add Reservation', + 'reservations.addManual': 'Manual Booking', + 'reservations.placeHint': + 'Tip: Reservations are best created directly from a place to link them with your day plan.', + 'reservations.confirmed': 'Confirmed', + 'reservations.pending': 'Pending', + 'reservations.summary': '{confirmed} confirmed, {pending} pending', + 'reservations.fromPlan': 'From Plan', + 'reservations.showFiles': 'Show Files', + 'reservations.editTitle': 'Edit Reservation', + 'reservations.status': 'Status', + 'reservations.datetime': 'Date & Time', + 'reservations.startTime': 'Start time', + 'reservations.endTime': 'End time', + 'reservations.date': 'Date', + 'reservations.time': 'Time', + 'reservations.timeAlt': 'Time (alternative, e.g. 19:30)', + 'reservations.notes': 'Notes', + 'reservations.notesPlaceholder': 'Additional notes...', + 'reservations.meta.airline': 'Airline', + 'reservations.meta.flightNumber': 'Flight No.', + 'reservations.meta.from': 'From', + 'reservations.meta.to': 'To', + 'reservations.needsReview': 'Review', + 'reservations.needsReviewHint': + 'Airport could not be matched automatically — please confirm the location.', + 'reservations.searchLocation': 'Search station, port, address…', + 'reservations.meta.trainNumber': 'Train No.', + 'reservations.meta.platform': 'Platform', + 'reservations.meta.seat': 'Seat', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in until', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Accommodation', + 'reservations.meta.pickAccommodation': 'Link to accommodation', + 'reservations.meta.noAccommodation': 'None', + 'reservations.meta.hotelPlace': 'Accommodation', + 'reservations.meta.pickHotel': 'Select accommodation', + 'reservations.meta.fromDay': 'From', + 'reservations.meta.toDay': 'To', + 'reservations.meta.selectDay': 'Select day', + 'reservations.type.flight': 'Flight', + 'reservations.type.hotel': 'Accommodation', + 'reservations.type.restaurant': 'Restaurant', + 'reservations.type.train': 'Train', + 'reservations.type.car': 'Car', + 'reservations.type.cruise': 'Cruise', + 'reservations.type.event': 'Event', + 'reservations.type.tour': 'Tour', + 'reservations.type.other': 'Other', + 'reservations.confirm.delete': + 'Are you sure you want to delete the reservation "{name}"?', + 'reservations.confirm.deleteTitle': 'Delete booking?', + 'reservations.confirm.deleteBody': '"{name}" will be permanently deleted.', + 'reservations.toast.updated': 'Reservation updated', + 'reservations.toast.removed': 'Reservation deleted', + 'reservations.toast.fileUploaded': 'File uploaded', + 'reservations.toast.uploadError': 'Failed to upload', + 'reservations.newTitle': 'New Reservation', + 'reservations.bookingType': 'Booking Type', + 'reservations.titleLabel': 'Title', + 'reservations.titlePlaceholder': 'e.g. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Location / Address', + 'reservations.locationPlaceholder': 'Address, Airport, Hotel...', + 'reservations.confirmationCode': 'Booking Code', + 'reservations.confirmationPlaceholder': 'e.g. ABC12345', + 'reservations.day': 'Day', + 'reservations.noDay': 'No Day', + 'reservations.place': 'Place', + 'reservations.noPlace': 'No Place', + 'reservations.pendingSave': 'will be saved…', + 'reservations.uploading': 'Uploading...', + 'reservations.attachFile': 'Attach file', + 'reservations.linkExisting': 'Link existing file', + 'reservations.toast.saveError': 'Failed to save', + 'reservations.toast.updateError': 'Failed to update', + 'reservations.toast.deleteError': 'Failed to delete', + 'reservations.confirm.remove': 'Remove reservation for "{name}"?', + 'reservations.linkAssignment': 'Link to day assignment', + 'reservations.pickAssignment': 'Select an assignment from your plan...', + 'reservations.noAssignment': 'No link (standalone)', + 'reservations.price': 'Price', + 'reservations.budgetCategory': 'Budget category', + 'reservations.budgetCategoryPlaceholder': 'e.g. Transport, Accommodation', + 'reservations.budgetCategoryAuto': 'Auto (from booking type)', + 'reservations.budgetHint': + 'A budget entry will be created automatically when saving.', + 'reservations.departureDate': 'Departure', + 'reservations.arrivalDate': 'Arrival', + 'reservations.departureTime': 'Dep. time', + 'reservations.arrivalTime': 'Arr. time', + 'reservations.pickupDate': 'Pickup', + 'reservations.returnDate': 'Return', + 'reservations.pickupTime': 'Pickup time', + 'reservations.returnTime': 'Return time', + 'reservations.endDate': 'End date', + 'reservations.meta.departureTimezone': 'Dep. TZ', + 'reservations.meta.arrivalTimezone': 'Arr. TZ', + 'reservations.span.departure': 'Departure', + 'reservations.span.arrival': 'Arrival', + 'reservations.span.inTransit': 'In transit', + 'reservations.span.pickup': 'Pickup', + 'reservations.span.return': 'Return', + 'reservations.span.active': 'Active', + 'reservations.span.start': 'Start', + 'reservations.span.end': 'End', + 'reservations.span.ongoing': 'Ongoing', + 'reservations.validation.endBeforeStart': + 'End date/time must be after start date/time', + 'reservations.addBooking': 'Add booking', +}; +export default reservations; diff --git a/shared/src/i18n/en/settings.ts b/shared/src/i18n/en/settings.ts new file mode 100644 index 00000000..df8f15ef --- /dev/null +++ b/shared/src/i18n/en/settings.ts @@ -0,0 +1,292 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Settings', + 'settings.subtitle': 'Configure your personal settings', + 'settings.tabs.display': 'Display', + 'settings.tabs.map': 'Map', + 'settings.tabs.notifications': 'Notifications', + 'settings.tabs.integrations': 'Integrations', + 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'About', + 'settings.map': 'Map', + 'settings.mapTemplate': 'Map Template', + 'settings.mapTemplatePlaceholder.select': 'Select template...', + 'settings.mapDefaultHint': 'Leave empty for OpenStreetMap (default)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL template for map tiles', + 'settings.mapProvider': 'Map Provider', + 'settings.mapProviderHint': + 'Affects Trip Planner and Journey maps. Atlas always uses Leaflet.', + 'settings.mapLeafletSubtitle': 'Classic 2D, any raster tiles', + 'settings.mapMapboxSubtitle': 'Vector tiles, 3D buildings & terrain', + 'settings.mapExperimental': 'Experimental', + 'settings.mapMapboxToken': 'Mapbox Access Token', + 'settings.mapMapboxTokenHint': 'Public token (pk.*) from', + 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', + 'settings.mapStyle': 'Map Style', + 'settings.mapStylePlaceholder': 'Select a Mapbox style', + 'settings.mapStyleHint': 'Preset or your own mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D Buildings & Terrain', + 'settings.map3dHint': + 'Pitch + real 3D building extrusions — works on every style, including satellite.', + 'settings.mapHighQuality': 'High Quality Mode', + 'settings.mapHighQualityHint': + 'Antialiasing + globe projection for sharper edges and a realistic world view.', + 'settings.mapHighQualityWarning': + 'May impact performance on lower-end devices.', + 'settings.mapTipLabel': 'Tip:', + 'settings.mapTip': + 'right-click and drag to rotate/pitch the map. Middle-click to add a place (right-click is reserved for rotation).', + 'settings.latitude': 'Latitude', + 'settings.longitude': 'Longitude', + 'settings.saveMap': 'Save Map', + 'settings.apiKeys': 'API Keys', + 'settings.mapsKey': 'Google Maps API Key', + 'settings.mapsKeyHint': + 'For place search. Requires Places API (New). Get at console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API Key', + 'settings.weatherKeyHint': 'For weather data. Free at openweathermap.org/api', + 'settings.keyPlaceholder': 'Enter key...', + 'settings.configured': 'Configured', + 'settings.saveKeys': 'Save Keys', + 'settings.display': 'Display', + 'settings.colorMode': 'Color Mode', + 'settings.light': 'Light', + 'settings.dark': 'Dark', + 'settings.auto': 'Auto', + 'settings.language': 'Language', + 'settings.temperature': 'Temperature Unit', + 'settings.timeFormat': 'Time Format', + 'settings.bookingLabels': 'Booking route labels', + 'settings.bookingLabelsHint': + 'Show station / airport names on the map. When off, only the icon is shown.', + 'settings.blurBookingCodes': 'Blur Booking Codes', + 'settings.notifications': 'Notifications', + 'settings.notifyTripInvite': 'Trip invitations', + 'settings.notifyBookingChange': 'Booking changes', + 'settings.notifyTripReminder': 'Trip reminders', + 'settings.notifyTodoDue': 'Todo due soon', + 'settings.notifyVacayInvite': 'Vacay fusion invitations', + 'settings.notifyPhotosShared': 'Shared photos (Immich)', + 'settings.notifyCollabMessage': 'Chat messages (Collab)', + 'settings.notifyPackingTagged': 'Packing list: assignments', + 'settings.notifyWebhook': 'Webhook notifications', + 'settings.notifyVersionAvailable': 'New version available', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + 'No notification channels are configured. Ask an admin to set up email or webhook notifications.', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Enter your Discord, Slack, or custom webhook URL to receive notifications.', + 'settings.webhookUrl.saved': 'Webhook URL saved', + 'settings.webhookUrl.test': 'Test', + 'settings.webhookUrl.testSuccess': 'Test webhook sent successfully', + 'settings.webhookUrl.testFailed': 'Test webhook failed', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.notificationsDisabled': + 'Notifications are not configured. Ask an admin to enable email or webhook notifications.', + 'settings.notificationsActive': 'Active channel', + 'settings.notificationsManagedByAdmin': + 'Notification events are configured by your administrator.', + 'settings.on': 'On', + 'settings.off': 'Off', + 'settings.mcp.title': 'MCP Configuration', + 'settings.mcp.endpoint': 'MCP Endpoint', + 'settings.mcp.clientConfig': 'Client Configuration', + 'settings.mcp.clientConfigHint': + 'Replace with an API token from the list below. The path to npx may need to be adjusted for your system (e.g. C:\\PROGRA~1\\nodejs\\npx.cmd on Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).', + 'settings.mcp.copy': 'Copy', + 'settings.mcp.copied': 'Copied!', + 'settings.mcp.apiTokens': 'API Tokens', + 'settings.mcp.createToken': 'Create New Token', + 'settings.mcp.noTokens': 'No tokens yet. Create one to connect MCP clients.', + 'settings.mcp.tokenCreatedAt': 'Created', + 'settings.mcp.tokenUsedAt': 'Used', + 'settings.mcp.deleteTokenTitle': 'Delete Token', + 'settings.mcp.deleteTokenMessage': + 'This token will stop working immediately. Any MCP client using it will lose access.', + 'settings.mcp.modal.createTitle': 'Create API Token', + 'settings.mcp.modal.tokenName': 'Token Name', + 'settings.mcp.modal.tokenNamePlaceholder': 'e.g. Claude Desktop, Work laptop', + 'settings.mcp.modal.creating': 'Creating…', + 'settings.mcp.modal.create': 'Create Token', + 'settings.mcp.modal.createdTitle': 'Token Created', + 'settings.mcp.modal.createdWarning': + 'This token will only be shown once. Copy and store it now — it cannot be recovered.', + 'settings.mcp.modal.done': 'Done', + 'settings.mcp.toast.created': 'Token created', + 'settings.mcp.toast.createError': 'Failed to create token', + 'settings.mcp.toast.deleted': 'Token deleted', + 'settings.mcp.toast.deleteError': 'Failed to delete token', + 'settings.mcp.apiTokensDeprecated': + 'API Tokens are deprecated and will be removed in a future release. Please use OAuth 2.1 Clients instead.', + 'settings.oauth.clients': 'OAuth 2.1 Clients', + 'settings.oauth.clientsHint': + 'Register OAuth 2.1 clients to let third-party MCP applications (Claude Web, Cursor, etc.) connect without static tokens.', + 'settings.oauth.createClient': 'New Client', + 'settings.oauth.noClients': 'No OAuth clients registered.', + 'settings.oauth.clientId': 'Client ID', + 'settings.oauth.clientSecret': 'Client Secret', + 'settings.oauth.deleteClient': 'Delete Client', + 'settings.oauth.deleteClientMessage': + 'This client and all active sessions will be permanently removed. Any application using it will lose access immediately.', + 'settings.oauth.rotateSecret': 'Rotate Secret', + 'settings.oauth.rotateSecretMessage': + 'A new client secret will be generated and all existing sessions will be invalidated immediately. Update your application before closing this dialog.', + 'settings.oauth.rotateSecretConfirm': 'Rotate', + 'settings.oauth.rotateSecretConfirming': 'Rotating…', + 'settings.oauth.rotateSecretDoneTitle': 'New Secret Generated', + 'settings.oauth.rotateSecretDoneWarning': + 'This secret is shown only once. Copy it now and update your application — all previous sessions have been invalidated.', + 'settings.oauth.activeSessions': 'Active OAuth Sessions', + 'settings.oauth.sessionScopes': 'Scopes', + 'settings.oauth.sessionExpires': 'Expires', + 'settings.oauth.revoke': 'Revoke', + 'settings.oauth.revokeSession': 'Revoke Session', + 'settings.oauth.revokeSessionMessage': + 'This will immediately revoke access for this OAuth session.', + 'settings.oauth.modal.createTitle': 'Register OAuth Client', + 'settings.oauth.modal.presets': 'Quick presets', + 'settings.oauth.modal.clientName': 'Application Name', + 'settings.oauth.modal.clientNamePlaceholder': 'e.g. Claude Web, My MCP App', + 'settings.oauth.modal.redirectUris': 'Redirect URIs', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.', + 'settings.oauth.modal.scopes': 'Allowed Scopes', + 'settings.oauth.modal.scopesHint': + 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.', + 'settings.oauth.modal.selectAll': 'Select all', + 'settings.oauth.modal.deselectAll': 'Deselect all', + 'settings.oauth.modal.creating': 'Registering…', + 'settings.oauth.modal.create': 'Register Client', + 'settings.oauth.modal.createdTitle': 'Client Registered', + 'settings.oauth.modal.createdWarning': + 'The client secret is shown only once. Copy it now — it cannot be recovered.', + 'settings.oauth.toast.createError': 'Failed to register OAuth client', + 'settings.oauth.toast.deleted': 'OAuth client deleted', + 'settings.oauth.toast.deleteError': 'Failed to delete OAuth client', + 'settings.oauth.toast.revoked': 'Session revoked', + 'settings.oauth.toast.revokeError': 'Failed to revoke session', + 'settings.oauth.toast.rotateError': 'Failed to rotate client secret', + 'settings.oauth.modal.machineClient': 'Machine client (no browser login)', + 'settings.oauth.modal.machineClientHint': + 'Use client_credentials grant — no redirect URIs needed. The token is issued directly via client_id + client_secret and acts as you within the selected scopes.', + 'settings.oauth.modal.machineClientUsage': + 'Get a token: POST /oauth/token with grant_type=client_credentials, client_id, and client_secret. No browser, no refresh token.', + 'settings.oauth.badge.machine': 'machine', + 'settings.account': 'Account', + 'settings.about': 'About', + 'settings.about.reportBug': 'Report a Bug', + 'settings.about.reportBugHint': 'Found an issue? Let us know', + 'settings.about.featureRequest': 'Feature Request', + 'settings.about.featureRequestHint': 'Suggest a new feature', + 'settings.about.wikiHint': 'Documentation & guides', + 'settings.about.supporters.badge': 'Monthly Supporters', + 'settings.about.supporters.title': 'Travel companions for TREK', + 'settings.about.supporters.subtitle': + "While you're planning your next route, these folks are helping plan TREK's future. Their monthly contribution goes straight into development and real hours spent — so TREK stays Open Source.", + 'settings.about.supporters.since': 'supporter since {date}', + 'settings.about.supporters.tierEmpty': 'Be the first', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK is a self-hosted travel planner that helps you organize your trips from the first idea to the last memory. Day planning, budget, packing lists, photos and much more — all in one place, on your own server.', + 'settings.about.madeWith': 'Made with', + 'settings.about.madeBy': 'by Maurice and a growing open-source community.', + 'settings.username': 'Username', + 'settings.email': 'Email', + 'settings.role': 'Role', + 'settings.roleAdmin': 'Administrator', + 'settings.oidcLinked': 'Linked with', + 'settings.changePassword': 'Change Password', + 'settings.currentPassword': 'Current password', + 'settings.currentPasswordRequired': 'Current password is required', + 'settings.newPassword': 'New password', + 'settings.confirmPassword': 'Confirm new password', + 'settings.updatePassword': 'Update password', + 'settings.passwordRequired': 'Please enter current and new password', + 'settings.passwordTooShort': 'Password must be at least 8 characters', + 'settings.passwordMismatch': 'Passwords do not match', + 'settings.passwordWeak': + 'Password must contain uppercase, lowercase, a number, and a special character', + 'settings.passwordChanged': 'Password changed successfully', + 'settings.mustChangePassword': + 'You must change your password before you can continue. Please set a new password below.', + 'settings.deleteAccount': 'Delete account', + 'settings.deleteAccountTitle': 'Delete your account?', + 'settings.deleteAccountWarning': + 'Your account and all your trips, places, and files will be permanently deleted. This action cannot be undone.', + 'settings.deleteAccountConfirm': 'Delete permanently', + 'settings.deleteBlockedTitle': 'Deletion not possible', + 'settings.deleteBlockedMessage': + 'You are the only administrator. Promote another user to admin before deleting your account.', + 'settings.roleUser': 'User', + 'settings.saveProfile': 'Save Profile', + 'settings.toast.mapSaved': 'Map settings saved', + 'settings.toast.keysSaved': 'API keys saved', + 'settings.toast.displaySaved': 'Display settings saved', + 'settings.toast.profileSaved': 'Profile saved', + 'settings.uploadAvatar': 'Upload Profile Picture', + 'settings.removeAvatar': 'Remove Profile Picture', + 'settings.avatarUploaded': 'Profile picture updated', + 'settings.avatarRemoved': 'Profile picture removed', + 'settings.avatarError': 'Upload failed', + 'settings.mfa.title': 'Two-factor authentication (2FA)', + 'settings.mfa.description': + 'Adds a second step when you sign in with email and password. Use an authenticator app (Google Authenticator, Authy, etc.).', + 'settings.mfa.requiredByPolicy': + 'Your administrator requires two-factor authentication. Set up an authenticator app below before continuing.', + 'settings.mfa.backupTitle': 'Backup codes', + 'settings.mfa.backupDescription': + 'Use these one-time backup codes if you lose access to your authenticator app.', + 'settings.mfa.backupWarning': + 'Save these codes now. Each code can only be used once.', + 'settings.mfa.backupCopy': 'Copy codes', + 'settings.mfa.backupDownload': 'Download TXT', + 'settings.mfa.backupPrint': 'Print / PDF', + 'settings.mfa.backupCopied': 'Backup codes copied', + 'settings.mfa.enabled': '2FA is enabled on your account.', + 'settings.mfa.disabled': '2FA is not enabled.', + 'settings.mfa.setup': 'Set up authenticator', + 'settings.mfa.scanQr': + 'Scan this QR code with your app, or enter the secret manually.', + 'settings.mfa.secretLabel': 'Secret key (manual entry)', + 'settings.mfa.codePlaceholder': '6-digit code', + 'settings.mfa.enable': 'Enable 2FA', + 'settings.mfa.cancelSetup': 'Cancel', + 'settings.mfa.disableTitle': 'Disable 2FA', + 'settings.mfa.disableHint': + 'Enter your account password and a current code from your authenticator.', + 'settings.mfa.disable': 'Disable 2FA', + 'settings.mfa.toastEnabled': 'Two-factor authentication enabled', + 'settings.mfa.toastDisabled': 'Two-factor authentication disabled', + 'settings.mfa.demoBlocked': 'Not available in demo mode', +}; +export default settings; diff --git a/shared/src/i18n/en/share.ts b/shared/src/i18n/en/share.ts new file mode 100644 index 00000000..694a8d37 --- /dev/null +++ b/shared/src/i18n/en/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Public Link', + 'share.linkHint': + 'Create a link anyone can use to view this trip without logging in. Read-only — no editing possible.', + 'share.createLink': 'Create link', + 'share.deleteLink': 'Delete link', + 'share.createError': 'Could not create link', + 'share.permMap': 'Map & Plan', + 'share.permBookings': 'Bookings', + 'share.permPacking': 'Packing', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/en/shared.ts b/shared/src/i18n/en/shared.ts new file mode 100644 index 00000000..1ca02418 --- /dev/null +++ b/shared/src/i18n/en/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link expired or invalid', + 'shared.expiredHint': 'This shared trip link is no longer active.', + 'shared.readOnly': 'Read-only shared view', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Bookings', + 'shared.tabPacking': 'Packing', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'days', + 'shared.places': 'places', + 'shared.other': 'Other', + 'shared.totalBudget': 'Total Budget', + 'shared.messages': 'messages', + 'shared.sharedVia': 'Shared via', + 'shared.confirmed': 'Confirmed', + 'shared.pending': 'Pending', +}; +export default shared; diff --git a/shared/src/i18n/en/stats.ts b/shared/src/i18n/en/stats.ts new file mode 100644 index 00000000..642a59f2 --- /dev/null +++ b/shared/src/i18n/en/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Countries', + 'stats.cities': 'Cities', + 'stats.trips': 'Trips', + 'stats.places': 'Places', + 'stats.worldProgress': 'World Progress', + 'stats.visited': 'visited', + 'stats.remaining': 'remaining', + 'stats.visitedCountries': 'Visited Countries', +}; +export default stats; diff --git a/shared/src/i18n/en/system_notice.ts b/shared/src/i18n/en/system_notice.ts new file mode 100644 index 00000000..616a1f21 --- /dev/null +++ b/shared/src/i18n/en/system_notice.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.v3_photos.title': 'Photos have moved in 3.0', + 'system_notice.v3_photos.body': + '**Photos** in the Trip Planner have been removed. Your photos are safe — TREK never modified your Immich or Synology library.\n\nPhotos now live in the **Journey** addon. Journey is optional — if it is not yet available, ask your admin to enable it under Admin → Addons.', + 'system_notice.v3_journey.title': 'Meet Journey — travel journal', + 'system_notice.v3_journey.body': + 'Document your trips as rich travel stories with timelines, photo galleries, and interactive maps.', + 'system_notice.v3_journey.cta_label': 'Open Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Day-by-day timeline & gallery', + 'system_notice.v3_journey.highlight_photos': 'Import from Immich or Synology', + 'system_notice.v3_journey.highlight_share': + 'Share publicly — no login needed', + 'system_notice.v3_journey.highlight_export': 'Export as a PDF photo book', + 'system_notice.v3_features.title': 'More highlights in 3.0', + 'system_notice.v3_features.body': + 'A few more things worth knowing about this release.', + 'system_notice.v3_features.highlight_dashboard': + 'Mobile-first dashboard redesign', + 'system_notice.v3_features.highlight_offline': 'Full offline mode as a PWA', + 'system_notice.v3_features.highlight_search': + 'Real-time place search autocomplete', + 'system_notice.v3_features.highlight_import': + 'Import places from KMZ/KML files', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 upgrade', + 'system_notice.v3_mcp.body': + 'The MCP integration has been fully overhauled. OAuth 2.1 is now the recommended auth method. Legacy static tokens (trek_…) are deprecated and will be removed in a future release.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommended (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 fine-grained permission scopes', + 'system_notice.v3_mcp.highlight_deprecated': 'Static trek_ tokens deprecated', + 'system_notice.v3_mcp.highlight_tools': 'Expanded toolset & prompts', + 'system_notice.v3_thankyou.title': 'A personal note from me', + 'system_notice.v3_thankyou.body': + "Before you go — I want to take a moment.\n\nTREK started as a side project I built for my own trips. I never imagined it would grow into something that 4,000 of you now trust to plan your adventures. Every star, every issue, every feature request — I read them all, and they keep me going through late nights between a full-time job and university.\n\nI want you to know: TREK will always be open source, always self-hosted, always yours. No tracking, no subscriptions, no strings attached. Just a tool built by someone who loves traveling as much as you do.\n\nSpecial thanks to [jubnl](https://github.com/jubnl) — you have become an incredible collaborator. So much of what makes 3.0 great carries your fingerprints. Thank you for believing in this project when it was still rough around the edges.\n\nAnd to every single one of you who filed a bug, translated a string, shared TREK with a friend, or simply used it to plan a trip — **thank you**. You are the reason this exists.\n\nHere's to many more adventures together.\n\n— Maurice\n\n---\n\n[Join the community on Discord](https://discord.gg/7Q6M6jDwzf)\n\nIf TREK makes your travels better, a [small coffee](https://ko-fi.com/mauriceboe) always keeps the lights on.", + 'system_notice.v3014_whitespace_collision.title': + 'Action required: user account conflict', + 'system_notice.v3014_whitespace_collision.body': + 'The 3.0.14 upgrade detected one or more username or email collisions caused by leading/trailing whitespace in stored accounts. Affected accounts were renamed automatically. Check the server logs for lines starting with **[migration] WHITESPACE COLLISION** to identify which accounts need review.', + 'system_notice.welcome_v1.title': 'Welcome to TREK', + 'system_notice.welcome_v1.body': + 'Your all-in-one travel planner. Build itineraries, share trips with friends, and stay organized — online or offline.', + 'system_notice.welcome_v1.cta_label': 'Plan a trip', + 'system_notice.welcome_v1.hero_alt': + 'A scenic travel destination with TREK planning UI overlay', + 'system_notice.welcome_v1.highlight_plan': + 'Day-by-day itineraries for any trip', + 'system_notice.welcome_v1.highlight_share': + 'Collaborate with travel partners', + 'system_notice.welcome_v1.highlight_offline': 'Works offline on mobile', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Previous notice', + 'system_notice.pager.next': 'Next notice', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Go to notice {n}', + 'system_notice.pager.position': 'Notice {current} of {total}', +}; +export default system_notice; diff --git a/shared/src/i18n/en/todo.ts b/shared/src/i18n/en/todo.ts new file mode 100644 index 00000000..0ab4e502 --- /dev/null +++ b/shared/src/i18n/en/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Packing List', + 'todo.subtab.todo': 'To-Do', + 'todo.completed': 'completed', + 'todo.filter.all': 'All', + 'todo.filter.open': 'Open', + 'todo.filter.done': 'Done', + 'todo.uncategorized': 'Uncategorized', + 'todo.namePlaceholder': 'Task name', + 'todo.descriptionPlaceholder': 'Description (optional)', + 'todo.unassigned': 'Unassigned', + 'todo.noCategory': 'No category', + 'todo.hasDescription': 'Has description', + 'todo.addItem': 'Add new task', + 'todo.sidebar.sortBy': 'Sort by', + 'todo.priority': 'Priority', + 'todo.newCategoryLabel': 'new', + 'todo.newCategory': 'Category name', + 'todo.addCategory': 'Add category', + 'todo.newItem': 'New task', + 'todo.empty': 'No tasks yet. Add a task to get started!', + 'todo.filter.my': 'My Tasks', + 'todo.filter.overdue': 'Overdue', + 'todo.sidebar.tasks': 'Tasks', + 'todo.sidebar.categories': 'Categories', + 'todo.detail.title': 'Task', + 'todo.detail.description': 'Description', + 'todo.detail.category': 'Category', + 'todo.detail.dueDate': 'Due date', + 'todo.detail.assignedTo': 'Assigned to', + 'todo.detail.delete': 'Delete', + 'todo.detail.save': 'Save changes', + 'todo.sortByPrio': 'Priority', + 'todo.detail.priority': 'Priority', + 'todo.detail.noPriority': 'None', + 'todo.detail.create': 'Create task', +}; +export default todo; diff --git a/shared/src/i18n/en/transport.ts b/shared/src/i18n/en/transport.ts new file mode 100644 index 00000000..7038b558 --- /dev/null +++ b/shared/src/i18n/en/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Add transport', + 'transport.modalTitle.create': 'Add transport', + 'transport.modalTitle.edit': 'Edit transport', + 'transport.title': 'Transports', + 'transport.addManual': 'Manual Transport', +}; +export default transport; diff --git a/shared/src/i18n/en/trip.ts b/shared/src/i18n/en/trip.ts new file mode 100644 index 00000000..cb62f959 --- /dev/null +++ b/shared/src/i18n/en/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transports', + 'trip.tabs.reservations': 'Bookings', + 'trip.tabs.reservationsShort': 'Book', + 'trip.tabs.packing': 'Packing List', + 'trip.tabs.packingShort': 'Packing', + 'trip.tabs.lists': 'Lists', + 'trip.tabs.listsShort': 'Lists', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'Files', + 'trip.loading': 'Loading trip...', + 'trip.loadingPhotos': 'Loading place photos...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Places', + 'trip.toast.placeUpdated': 'Place updated', + 'trip.toast.placeAdded': 'Place added', + 'trip.toast.placeDeleted': 'Place deleted', + 'trip.toast.selectDay': 'Please select a day first', + 'trip.toast.assignedToDay': 'Place assigned to day', + 'trip.toast.reorderError': 'Failed to reorder', + 'trip.toast.reservationUpdated': 'Reservation updated', + 'trip.toast.reservationAdded': 'Reservation added', + 'trip.toast.deleted': 'Deleted', + 'trip.confirm.deletePlace': 'Are you sure you want to delete this place?', + 'trip.confirm.deletePlaces': 'Delete {count} places?', + 'trip.toast.placesDeleted': '{count} places deleted', +}; +export default trip; diff --git a/shared/src/i18n/en/trips.ts b/shared/src/i18n/en/trips.ts new file mode 100644 index 00000000..34f4b31b --- /dev/null +++ b/shared/src/i18n/en/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} removed', + 'trips.memberRemoveError': 'Failed to remove', + 'trips.memberAdded': '{username} added', + 'trips.memberAddError': 'Failed to add', + 'trips.reminder': 'Reminder', + 'trips.reminderNone': 'None', + 'trips.reminderDay': 'day', + 'trips.reminderDays': 'days', + 'trips.reminderCustom': 'Custom', + 'trips.reminderDaysBefore': 'days before departure', + 'trips.reminderDisabledHint': + 'Trip reminders are disabled. Enable them in Admin > Settings > Notifications.', +}; +export default trips; diff --git a/shared/src/i18n/en/undo.ts b/shared/src/i18n/en/undo.ts new file mode 100644 index 00000000..54d92bc6 --- /dev/null +++ b/shared/src/i18n/en/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Undo', + 'undo.tooltip': 'Undo: {action}', + 'undo.assignPlace': 'Place assigned to day', + 'undo.removeAssignment': 'Place removed from day', + 'undo.reorder': 'Places reordered', + 'undo.optimize': 'Route optimized', + 'undo.deletePlace': 'Place deleted', + 'undo.deletePlaces': 'Places deleted', + 'undo.moveDay': 'Place moved to another day', + 'undo.lock': 'Place lock toggled', + 'undo.importGpx': 'GPX import', + 'undo.importKeyholeMarkup': 'KMZ/KML import', + 'undo.importGoogleList': 'Google Maps import', + 'undo.importNaverList': 'Naver Maps import', + 'undo.addPlace': 'Place added', + 'undo.done': 'Undone: {action}', +}; +export default undo; diff --git a/shared/src/i18n/en/vacay.ts b/shared/src/i18n/en/vacay.ts new file mode 100644 index 00000000..3dbd4f60 --- /dev/null +++ b/shared/src/i18n/en/vacay.ts @@ -0,0 +1,103 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Plan and manage vacation days', + 'vacay.settings': 'Settings', + 'vacay.year': 'Year', + 'vacay.addYear': 'Add next year', + 'vacay.addPrevYear': 'Add previous year', + 'vacay.removeYear': 'Remove year', + 'vacay.removeYearConfirm': 'Remove {year}?', + 'vacay.removeYearHint': + 'All vacation entries and company holidays for this year will be permanently deleted.', + 'vacay.remove': 'Remove', + 'vacay.persons': 'Persons', + 'vacay.noPersons': 'No persons added', + 'vacay.addPerson': 'Add Person', + 'vacay.editPerson': 'Edit Person', + 'vacay.removePerson': 'Remove Person', + 'vacay.removePersonConfirm': 'Remove {name}?', + 'vacay.removePersonHint': + 'All vacation entries for this person will be permanently deleted.', + 'vacay.personName': 'Name', + 'vacay.personNamePlaceholder': 'Enter name', + 'vacay.color': 'Color', + 'vacay.add': 'Add', + 'vacay.legend': 'Legend', + 'vacay.publicHoliday': 'Public Holiday', + 'vacay.companyHoliday': 'Company Holiday', + 'vacay.weekend': 'Weekend', + 'vacay.modeVacation': 'Vacation', + 'vacay.modeCompany': 'Company Holiday', + 'vacay.entitlement': 'Entitlement', + 'vacay.entitlementDays': 'Days', + 'vacay.used': 'Used', + 'vacay.remaining': 'Left', + 'vacay.carriedOver': 'from {year}', + 'vacay.blockWeekends': 'Block Weekends', + 'vacay.blockWeekendsHint': 'Prevent vacation entries on weekend days', + 'vacay.weekendDays': 'Weekend days', + 'vacay.mon': 'Mon', + 'vacay.tue': 'Tue', + 'vacay.wed': 'Wed', + 'vacay.thu': 'Thu', + 'vacay.fri': 'Fri', + 'vacay.sat': 'Sat', + 'vacay.sun': 'Sun', + 'vacay.publicHolidays': 'Public Holidays', + 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar', + 'vacay.selectCountry': 'Select country', + 'vacay.selectRegion': 'Select region (optional)', + 'vacay.addCalendar': 'Add calendar', + 'vacay.calendarLabel': 'Label (optional)', + 'vacay.calendarColor': 'Color', + 'vacay.noCalendars': 'No holiday calendars added yet', + 'vacay.companyHolidays': 'Company Holidays', + 'vacay.companyHolidaysHint': 'Allow marking company-wide holiday days', + 'vacay.companyHolidaysNoDeduct': + 'Company holidays do not count towards vacation days.', + 'vacay.weekStart': 'Week starts on', + 'vacay.weekStartHint': + 'Choose whether the calendar week starts on Monday or Sunday', + 'vacay.carryOver': 'Carry Over', + 'vacay.carryOverHint': + 'Automatically carry remaining vacation days into the next year', + 'vacay.sharing': 'Sharing', + 'vacay.sharingHint': 'Share your vacation plan with other TREK users', + 'vacay.owner': 'Owner', + 'vacay.shareEmailPlaceholder': 'Email of TREK user', + 'vacay.shareSuccess': 'Plan shared successfully', + 'vacay.shareError': 'Could not share plan', + 'vacay.dissolve': 'Dissolve Fusion', + 'vacay.dissolveHint': 'Separate calendars again. Your entries will be kept.', + 'vacay.dissolveAction': 'Dissolve', + 'vacay.dissolved': 'Calendar separated', + 'vacay.fusedWith': 'Fused with', + 'vacay.you': 'you', + 'vacay.noData': 'No data', + 'vacay.changeColor': 'Change color', + 'vacay.inviteUser': 'Invite User', + 'vacay.inviteHint': + 'Invite another TREK user to share a combined vacation calendar.', + 'vacay.selectUser': 'Select user', + 'vacay.sendInvite': 'Send Invite', + 'vacay.inviteSent': 'Invite sent', + 'vacay.inviteError': 'Could not send invite', + 'vacay.pending': 'pending', + 'vacay.noUsersAvailable': 'No users available', + 'vacay.accept': 'Accept', + 'vacay.decline': 'Decline', + 'vacay.acceptFusion': 'Accept & Fuse', + 'vacay.inviteTitle': 'Fusion Request', + 'vacay.inviteWantsToFuse': 'wants to share a vacation calendar with you.', + 'vacay.fuseInfo1': + 'Both of you will see all vacation entries in one shared calendar.', + 'vacay.fuseInfo2': 'Both parties can create and edit entries for each other.', + 'vacay.fuseInfo3': + 'Both parties can delete entries and change vacation entitlements.', + 'vacay.fuseInfo4': + 'Settings like public holidays and company holidays are shared.', + 'vacay.fuseInfo5': + 'The fusion can be dissolved at any time by either party. Your entries will be preserved.', +}; +export default vacay; diff --git a/shared/src/i18n/es/admin.ts b/shared/src/i18n/es/admin.ts new file mode 100644 index 00000000..e76b44d1 --- /dev/null +++ b/shared/src/i18n/es/admin.ts @@ -0,0 +1,373 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notificaciones', + 'admin.notifications.hint': + 'Elija un canal de notificación. Solo uno puede estar activo a la vez.', + 'admin.notifications.none': 'Desactivado', + 'admin.notifications.email': 'Correo (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Guardar configuración de notificaciones', + 'admin.notifications.saved': 'Configuración de notificaciones guardada', + 'admin.notifications.testWebhook': 'Enviar webhook de prueba', + 'admin.notifications.testWebhookSuccess': + 'Webhook de prueba enviado correctamente', + 'admin.notifications.testWebhookFailed': 'Error al enviar webhook de prueba', + 'admin.smtp.title': 'Correo y notificaciones', + 'admin.smtp.hint': + 'Configuración SMTP para el envío de notificaciones por correo.', + 'admin.smtp.testButton': 'Enviar correo de prueba', + 'admin.webhook.hint': + 'Enviar notificaciones a un webhook externo (Discord, Slack, etc.).', + 'admin.smtp.testSuccess': 'Correo de prueba enviado correctamente', + 'admin.smtp.testFailed': 'Error al enviar correo de prueba', + 'admin.title': 'Administración', + 'admin.subtitle': 'Gestión de usuarios y ajustes del sistema', + 'admin.tabs.users': 'Usuarios', + 'admin.tabs.categories': 'Categorías', + 'admin.tabs.backup': 'Copia de seguridad', + 'admin.tabs.audit': 'Auditoría', + 'admin.stats.users': 'Usuarios', + 'admin.stats.trips': 'Viajes', + 'admin.stats.places': 'Lugares', + 'admin.stats.photos': 'Fotos', + 'admin.stats.files': 'Archivos', + 'admin.table.user': 'Usuario', + 'admin.table.email': 'Correo', + 'admin.table.role': 'Rol', + 'admin.table.created': 'Creado', + 'admin.table.lastLogin': 'Último acceso', + 'admin.table.actions': 'Acciones', + 'admin.you': '(Tú)', + 'admin.editUser': 'Editar usuario', + 'admin.newPassword': 'Nueva contraseña', + 'admin.newPasswordHint': 'Déjalo vacío para mantener la contraseña actual', + 'admin.deleteUser': + '¿Eliminar al usuario "{name}"? Todos sus viajes se borrarán permanentemente.', + 'admin.deleteUserTitle': 'Eliminar usuario', + 'admin.newPasswordPlaceholder': 'Introduce una nueva contraseña…', + 'admin.toast.loadError': 'No se pudieron cargar los datos de administración', + 'admin.toast.userUpdated': 'Usuario actualizado', + 'admin.toast.updateError': 'No se pudo actualizar', + 'admin.toast.userDeleted': 'Usuario eliminado', + 'admin.toast.deleteError': 'No se pudo eliminar', + 'admin.toast.cannotDeleteSelf': 'No puedes eliminar tu propia cuenta', + 'admin.toast.userCreated': 'Usuario creado', + 'admin.toast.createError': 'No se pudo crear el usuario', + 'admin.toast.fieldsRequired': 'Usuario, correo y contraseña son obligatorios', + 'admin.createUser': 'Crear usuario', + 'admin.invite.title': 'Enlaces de invitación', + 'admin.invite.subtitle': 'Crear enlaces de registro de un solo uso', + 'admin.invite.create': 'Crear enlace', + 'admin.invite.createAndCopy': 'Crear y copiar', + 'admin.invite.empty': 'No se han creado enlaces de invitación', + 'admin.invite.maxUses': 'Usos máx.', + 'admin.invite.expiry': 'Expira después de', + 'admin.invite.uses': 'usado(s)', + 'admin.invite.expiresAt': 'expira el', + 'admin.invite.createdBy': 'por', + 'admin.invite.active': 'Activo', + 'admin.invite.expired': 'Expirado', + 'admin.invite.usedUp': 'Agotado', + 'admin.invite.copied': 'Enlace de invitación copiado', + 'admin.invite.copyLink': 'Copiar enlace', + 'admin.invite.deleted': 'Enlace de invitación eliminado', + 'admin.invite.createError': 'Error al crear el enlace', + 'admin.invite.deleteError': 'Error al eliminar el enlace', + 'admin.tabs.settings': 'Ajustes', + 'admin.allowRegistration': 'Permitir el registro', + 'admin.allowRegistrationHint': + 'Los nuevos usuarios pueden registrarse por sí mismos', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Exigir autenticación en dos factores (2FA)', + 'admin.requireMfaHint': + 'Los usuarios sin 2FA deben completar la configuración en Ajustes antes de usar la aplicación.', + 'admin.apiKeys': 'Claves API', + 'admin.apiKeysHint': + 'Opcional. Activa datos ampliados de lugares, como fotos y previsión del tiempo.', + 'admin.mapsKey': 'Clave API de Google Maps', + 'admin.mapsKeyHint': + 'Obligatoria para buscar lugares. Consíguela en console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Sin una clave API, la búsqueda de lugares usa OpenStreetMap. Con una clave de Google también se pueden cargar fotos, valoraciones y horarios de apertura. Consíguela en console.cloud.google.com.', + 'admin.recommended': 'Recomendado', + 'admin.weatherKey': 'Clave API de OpenWeatherMap', + 'admin.weatherKeyHint': + 'Para datos meteorológicos. Gratis en openweathermap.org', + 'admin.validateKey': 'Probar', + 'admin.keyValid': 'Conectado', + 'admin.keyInvalid': 'No válida', + 'admin.keySaved': 'Claves API guardadas', + 'admin.oidcTitle': 'Inicio de sesión único (OIDC)', + 'admin.oidcSubtitle': + 'Permite iniciar sesión mediante proveedores externos como Google, Apple, Authentik o Keycloak.', + 'admin.oidcDisplayName': 'Nombre visible', + 'admin.oidcIssuer': 'URL del emisor', + 'admin.oidcIssuerHint': + 'La URL Issuer de OpenID Connect del proveedor. Ej.: https://accounts.google.com', + 'admin.oidcSaved': 'Configuración OIDC guardada', + 'admin.fileTypes': 'Tipos de archivo permitidos', + 'admin.fileTypesHint': + 'Configura qué tipos de archivo pueden subir los usuarios.', + 'admin.fileTypesFormat': + 'Extensiones separadas por comas (p. ej. jpg,png,pdf,doc). Usa * para permitir todos los tipos.', + 'admin.fileTypesSaved': 'Ajustes de tipos de archivo guardados', + 'admin.placesPhotos.title': 'Fotos de Lugares', + 'admin.placesPhotos.subtitle': + 'Obtiene fotos de la Google Places API. Desactiva para ahorrar cuota de API. Las fotos de Wikimedia no se ven afectadas.', + 'admin.placesAutocomplete.title': 'Autocompletado de Lugares', + 'admin.placesAutocomplete.subtitle': + 'Usa la Google Places API para sugerencias de búsqueda. Desactiva para ahorrar cuota de API.', + 'admin.placesDetails.title': 'Detalles del Lugar', + 'admin.placesDetails.subtitle': + 'Obtiene información detallada del lugar (horarios, valoración, web) de la Google Places API. Desactiva para ahorrar cuota de API.', + 'admin.bagTracking.title': 'Seguimiento de equipaje', + 'admin.bagTracking.subtitle': + 'Activar peso y asignación de equipaje para artículos de la lista', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': + 'Mensajería en tiempo real para la colaboración', + 'admin.collab.notes.title': 'Notas', + 'admin.collab.notes.subtitle': 'Notas y documentos compartidos', + 'admin.collab.polls.title': 'Encuestas', + 'admin.collab.polls.subtitle': 'Encuestas y votaciones grupales', + 'admin.collab.whatsnext.title': 'Qué sigue', + 'admin.collab.whatsnext.subtitle': + 'Sugerencias de actividades y próximos pasos', + 'admin.tabs.config': 'Personalización', + 'admin.tabs.defaults': 'Valores predeterminados', + 'admin.defaultSettings.title': 'Configuración predeterminada de usuarios', + 'admin.defaultSettings.description': + 'Establece valores predeterminados para toda la instancia. Los usuarios que no hayan cambiado una opción verán estos valores. Sus propios cambios siempre tienen prioridad.', + 'admin.defaultSettings.saved': 'Predeterminado guardado', + 'admin.defaultSettings.reset': 'Restaurar al valor predeterminado integrado', + 'admin.defaultSettings.resetToBuiltIn': 'restaurar', + 'admin.tabs.templates': 'Plantillas de equipaje', + 'admin.packingTemplates.title': 'Plantillas de equipaje', + 'admin.packingTemplates.subtitle': + 'Crear listas de equipaje reutilizables para tus viajes', + 'admin.packingTemplates.create': 'Nueva plantilla', + 'admin.packingTemplates.namePlaceholder': + 'Nombre de la plantilla (ej. Vacaciones en la playa)', + 'admin.packingTemplates.empty': 'No se han creado plantillas aún', + 'admin.packingTemplates.items': 'artículos', + 'admin.packingTemplates.categories': 'categorías', + 'admin.packingTemplates.itemName': 'Nombre del artículo', + 'admin.packingTemplates.itemCategory': 'Categoría', + 'admin.packingTemplates.categoryName': 'Nombre de categoría (ej. Ropa)', + 'admin.packingTemplates.addCategory': 'Añadir categoría', + 'admin.packingTemplates.created': 'Plantilla creada', + 'admin.packingTemplates.deleted': 'Plantilla eliminada', + 'admin.packingTemplates.loadError': 'Error al cargar plantillas', + 'admin.packingTemplates.createError': 'Error al crear plantilla', + 'admin.packingTemplates.deleteError': 'Error al eliminar plantilla', + 'admin.packingTemplates.saveError': 'Error al guardar', + 'admin.tabs.addons': 'Complementos', + 'admin.addons.title': 'Complementos', + 'admin.addons.subtitle': + 'Activa o desactiva funciones para personalizar tu experiencia en TREK.', + 'admin.addons.subtitleBefore': + 'Activa o desactiva funciones para personalizar tu experiencia en ', + 'admin.addons.subtitleAfter': '.', + 'admin.addons.enabled': 'Activo', + 'admin.addons.disabled': 'Desactivado', + 'admin.addons.type.trip': 'Viaje', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integración', + 'admin.addons.tripHint': 'Disponible como pestaña dentro de cada viaje', + 'admin.addons.globalHint': + 'Disponible como sección independiente en la navegación principal', + 'admin.addons.integrationHint': + 'Servicios backend e integraciones de API sin página dedicada', + 'admin.addons.toast.updated': 'Complemento actualizado', + 'admin.addons.toast.error': 'No se pudo actualizar el complemento', + 'admin.addons.noAddons': 'No hay complementos disponibles', + 'admin.weather.title': 'Datos meteorológicos', + 'admin.weather.badge': 'Desde el 24 de marzo de 2026', + 'admin.weather.description': + 'TREK utiliza Open-Meteo como fuente de datos meteorológicos. Open-Meteo es un servicio meteorológico gratuito y de código abierto: no requiere clave API.', + 'admin.weather.forecast': 'Pronóstico de 16 días', + 'admin.weather.forecastDesc': 'Antes eran 5 días (OpenWeatherMap)', + 'admin.weather.climate': 'Datos climáticos históricos', + 'admin.weather.climateDesc': + 'Promedios de los últimos 85 años para fechas posteriores al pronóstico de 16 días', + 'admin.weather.requests': '10.000 solicitudes / día', + 'admin.weather.requestsDesc': 'Gratis, sin necesidad de clave API', + 'admin.weather.locationHint': + 'El tiempo se basa en el primer lugar con coordenadas de cada día. Si no hay ningún lugar asignado a un día, se usa como referencia cualquier lugar de la lista.', + 'admin.tabs.mcpTokens': 'Acceso MCP', + 'admin.mcpTokens.title': 'Acceso MCP', + 'admin.mcpTokens.subtitle': + 'Gestionar sesiones OAuth y tokens de API de todos los usuarios', + 'admin.mcpTokens.sectionTitle': 'Tokens de API', + 'admin.mcpTokens.owner': 'Propietario', + 'admin.mcpTokens.tokenName': 'Nombre del token', + 'admin.mcpTokens.created': 'Creado', + 'admin.mcpTokens.lastUsed': 'Último uso', + 'admin.mcpTokens.never': 'Nunca', + 'admin.mcpTokens.empty': 'Aún no se han creado tokens MCP', + 'admin.mcpTokens.deleteTitle': 'Eliminar token', + 'admin.mcpTokens.deleteMessage': + 'Este token se revocará inmediatamente. El usuario perderá el acceso MCP a través de este token.', + 'admin.mcpTokens.deleteSuccess': 'Token eliminado', + 'admin.mcpTokens.deleteError': 'No se pudo eliminar el token', + 'admin.mcpTokens.loadError': 'No se pudieron cargar los tokens', + 'admin.oauthSessions.sectionTitle': 'Sesiones OAuth', + 'admin.oauthSessions.clientName': 'Cliente', + 'admin.oauthSessions.owner': 'Propietario', + 'admin.oauthSessions.scopes': 'Permisos', + 'admin.oauthSessions.created': 'Creado', + 'admin.oauthSessions.empty': 'No hay sesiones OAuth activas', + 'admin.oauthSessions.revokeTitle': 'Revocar sesión', + 'admin.oauthSessions.revokeMessage': + 'Esto revocará la sesión OAuth inmediatamente. El cliente perderá el acceso MCP.', + 'admin.oauthSessions.revokeSuccess': 'Sesión revocada', + 'admin.oauthSessions.revokeError': 'No se pudo revocar la sesión', + 'admin.oauthSessions.loadError': 'No se pudieron cargar las sesiones OAuth', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Eventos sensibles de seguridad y administración (copias de seguridad, usuarios, MFA, ajustes).', + 'admin.audit.empty': 'Aún no hay entradas de auditoría.', + 'admin.audit.refresh': 'Actualizar', + 'admin.audit.loadMore': 'Cargar más', + 'admin.audit.showing': '{count} cargados · {total} en total', + 'admin.audit.col.time': 'Fecha y hora', + 'admin.audit.col.user': 'Usuario', + 'admin.audit.col.action': 'Acción', + 'admin.audit.col.resource': 'Recurso', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Detalles', + 'admin.github.title': 'Historial de versiones', + 'admin.github.subtitle': 'Últimas novedades de {repo}', + 'admin.github.latest': 'Última', + 'admin.github.prerelease': 'Prelanzamiento', + 'admin.github.showDetails': 'Mostrar detalles', + 'admin.github.hideDetails': 'Ocultar detalles', + 'admin.github.loadMore': 'Cargar más', + 'admin.github.loading': 'Cargando...', + 'admin.github.support': 'Ayuda a seguir desarrollando TREK', + 'admin.github.error': 'No se pudieron cargar las versiones', + 'admin.github.by': 'por', + 'admin.update.available': 'Actualización disponible', + 'admin.update.text': + 'TREK {version} está disponible. Estás usando {current}.', + 'admin.update.button': 'Ver en GitHub', + 'admin.update.install': 'Instalar actualización', + 'admin.update.confirmTitle': '¿Instalar actualización?', + 'admin.update.confirmText': + 'TREK se actualizará de {current} a {version}. Después, el servidor se reiniciará automáticamente.', + 'admin.update.dataInfo': + 'Todos tus datos (viajes, usuarios, claves API, subidas, Vacay, Atlas, presupuestos) se conservarán.', + 'admin.update.warning': + 'La app estará brevemente no disponible durante el reinicio.', + 'admin.update.confirm': 'Actualizar ahora', + 'admin.update.installing': 'Actualizando…', + 'admin.update.success': + '¡Actualización instalada! El servidor se está reiniciando…', + 'admin.update.failed': 'La actualización falló', + 'admin.update.backupHint': + 'Recomendamos crear una copia de seguridad antes de actualizar.', + 'admin.update.backupLink': 'Ir a Copia de seguridad', + 'admin.update.howTo': 'Cómo actualizar', + 'admin.update.dockerText': + 'Tu instancia de TREK se ejecuta en Docker. Para actualizar a {version}, ejecuta los siguientes comandos en tu servidor:', + 'admin.update.reloadHint': 'Recarga la página en unos segundos.', + 'admin.addons.catalog.memories.name': 'Fotos (Immich)', + 'admin.addons.catalog.memories.description': + 'Comparte fotos de viaje a través de tu instancia de Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Protocolo de contexto de modelo para integración con asistentes de IA', + 'admin.addons.catalog.packing.name': 'Listas', + 'admin.addons.catalog.packing.description': + 'Listas de equipaje y tareas pendientes para tus viajes', + 'admin.addons.catalog.budget.name': 'Presupuesto', + 'admin.addons.catalog.budget.description': + 'Controla los gastos y planifica el presupuesto del viaje', + 'admin.addons.catalog.documents.name': 'Documentos', + 'admin.addons.catalog.documents.description': + 'Guarda y gestiona la documentación del viaje', + 'admin.addons.catalog.vacay.name': 'Vacaciones', + 'admin.addons.catalog.vacay.description': + 'Planificador personal de vacaciones con vista de calendario', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Mapa del mundo con los países visitados y estadísticas de viaje', + 'admin.addons.catalog.collab.name': 'Colaboración', + 'admin.addons.catalog.collab.description': + 'Notas, encuestas y chat en tiempo real para organizar el viaje', + 'admin.oidcOnlyMode': 'Desactivar autenticación por contraseña', + 'admin.oidcOnlyModeHint': + 'Si está activado, solo se permite el inicio de sesión con SSO. El inicio de sesión y registro con contraseña se bloquean.', + 'admin.tabs.permissions': 'Permisos', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Las notificaciones in-app siempre están activas y no se pueden desactivar globalmente.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook de admin', + 'admin.notifications.adminWebhookPanel.hint': + 'Este webhook se usa exclusivamente para notificaciones de admin (ej. alertas de versión). Es independiente de los webhooks de usuario y se activa automáticamente si hay una URL configurada.', + 'admin.notifications.adminWebhookPanel.saved': + 'URL del webhook de admin guardada', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Webhook de prueba enviado correctamente', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Error al enviar el webhook de prueba', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'El webhook de admin se activa automáticamente si hay una URL configurada', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Permite a los usuarios configurar sus propios temas ntfy para notificaciones push. Establece el servidor predeterminado a continuación para rellenar automáticamente los ajustes del usuario.', + 'admin.notifications.testNtfy': 'Enviar Ntfy de prueba', + 'admin.notifications.testNtfySuccess': 'Ntfy de prueba enviado correctamente', + 'admin.notifications.testNtfyFailed': 'Error al enviar el Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy de admin', + 'admin.notifications.adminNtfyPanel.hint': + 'Este tema Ntfy se usa exclusivamente para notificaciones de admin (ej. alertas de versión). Es independiente de los temas por usuario y siempre se activa cuando está configurado.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL del servidor Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'También se usa como servidor predeterminado para las notificaciones ntfy de los usuarios. Déjalo en blanco para usar ntfy.sh. Los usuarios pueden cambiarlo en sus propios ajustes.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Tema de admin', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token de acceso (opcional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Token de acceso de admin eliminado', + 'admin.notifications.adminNtfyPanel.saved': + 'Configuración de Ntfy de admin guardada', + 'admin.notifications.adminNtfyPanel.test': 'Enviar Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Ntfy de prueba enviado correctamente', + 'admin.notifications.adminNtfyPanel.testFailed': + 'Error al enviar el Ntfy de prueba', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'El Ntfy de admin siempre se activa cuando hay un tema configurado', + 'admin.notifications.adminNotificationsHint': + 'Configura qué canales entregan notificaciones de admin (ej. alertas de versión). El webhook se activa automáticamente si hay una URL de webhook de admin configurada.', + 'admin.notifications.tripReminders.title': 'Recordatorios de viaje', + 'admin.notifications.tripReminders.hint': + 'Envía una notificación de recordatorio antes de que comience un viaje (requiere días de recordatorio configurados en el viaje).', + 'admin.notifications.tripReminders.enabled': + 'Recordatorios de viaje activados', + 'admin.notifications.tripReminders.disabled': + 'Recordatorios de viaje desactivados', + 'admin.tabs.notifications': 'Notificaciones', + 'admin.addons.catalog.journey.name': 'Travesía', + 'admin.addons.catalog.journey.description': + 'Seguimiento de viajes y diario de viajero con registros de ubicación, fotos e historias diarias', +}; +export default admin; diff --git a/shared/src/i18n/es/airport.ts b/shared/src/i18n/es/airport.ts new file mode 100644 index 00000000..a271b3d2 --- /dev/null +++ b/shared/src/i18n/es/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Código o ciudad del aeropuerto (ej. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/es/atlas.ts b/shared/src/i18n/es/atlas.ts new file mode 100644 index 00000000..9db1e4ad --- /dev/null +++ b/shared/src/i18n/es/atlas.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Tu huella viajera por el mundo', + 'atlas.countries': 'Países', + 'atlas.trips': 'Viajes', + 'atlas.places': 'Lugares', + 'atlas.days': 'Días', + 'atlas.visitedCountries': 'Países visitados', + 'atlas.cities': 'Ciudades', + 'atlas.noData': 'Aún no hay datos de viaje', + 'atlas.noDataHint': + 'Crea un viaje y añade lugares para ver tu mapa del mundo', + 'atlas.lastTrip': 'Último viaje', + 'atlas.nextTrip': 'Próximo viaje', + 'atlas.daysLeft': 'días restantes', + 'atlas.streak': 'Racha', + 'atlas.year': 'año', + 'atlas.years': 'años', + 'atlas.yearInRow': 'año seguido', + 'atlas.yearsInRow': 'años seguidos', + 'atlas.tripIn': 'viaje en', + 'atlas.tripsIn': 'viajes en', + 'atlas.since': 'desde', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Asia', + 'atlas.northAmerica': 'América del Norte', + 'atlas.southAmerica': 'América del Sur', + 'atlas.africa': 'África', + 'atlas.oceania': 'Oceanía', + 'atlas.other': 'Otros', + 'atlas.firstVisit': 'Primer viaje', + 'atlas.lastVisitLabel': 'Último viaje', + 'atlas.tripSingular': 'Viaje', + 'atlas.tripPlural': 'Viajes', + 'atlas.placeVisited': 'Lugar visitado', + 'atlas.placesVisited': 'Lugares visitados', + 'atlas.statsTab': 'Estadísticas', + 'atlas.bucketTab': 'Lista de deseos', + 'atlas.addBucket': 'Añadir a lista de deseos', + 'atlas.bucketNamePlaceholder': 'Lugar o destino...', + 'atlas.bucketNotesPlaceholder': 'Notas (opcional)', + 'atlas.bucketEmpty': 'Tu lista de deseos está vacía', + 'atlas.bucketEmptyHint': 'Añade lugares que sueñas con visitar', + 'atlas.unmark': 'Eliminar', + 'atlas.confirmMark': '¿Marcar este país como visitado?', + 'atlas.confirmUnmark': '¿Eliminar este país de tu lista de visitados?', + 'atlas.confirmUnmarkRegion': + '¿Eliminar esta región de tu lista de visitados?', + 'atlas.markVisited': 'Marcar como visitado', + 'atlas.markVisitedHint': 'Añadir este país a tu lista de visitados', + 'atlas.markRegionVisitedHint': 'Añadir esta región a tu lista de visitados', + 'atlas.addToBucket': 'Añadir a lista de deseos', + 'atlas.addPoi': 'Añadir lugar', + 'atlas.searchCountry': 'Buscar un país...', + 'atlas.month': 'Mes', + 'atlas.addToBucketHint': 'Guardar como lugar que quieres visitar', + 'atlas.bucketWhen': '¿Cuándo planeas visitarlo?', +}; +export default atlas; diff --git a/shared/src/i18n/es/backup.ts b/shared/src/i18n/es/backup.ts new file mode 100644 index 00000000..e556a2e9 --- /dev/null +++ b/shared/src/i18n/es/backup.ts @@ -0,0 +1,79 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Copia de seguridad de datos', + 'backup.subtitle': 'Base de datos y todos los archivos subidos', + 'backup.refresh': 'Actualizar', + 'backup.upload': 'Subir copia de seguridad', + 'backup.uploading': 'Subiendo…', + 'backup.create': 'Crear copia', + 'backup.creating': 'Creando…', + 'backup.empty': 'Aún no hay copias', + 'backup.createFirst': 'Crear la primera copia', + 'backup.download': 'Descargar', + 'backup.restore': 'Restaurar', + 'backup.confirm.restore': + '¿Restaurar la copia "{name}"?\n\nTodos los datos actuales serán reemplazados por la copia.', + 'backup.confirm.uploadRestore': + '¿Subir y restaurar el archivo de copia "{name}"?\n\nTodos los datos actuales se sobrescribirán.', + 'backup.confirm.delete': '¿Eliminar la copia "{name}"?', + 'backup.toast.loadError': 'No se pudieron cargar las copias', + 'backup.toast.created': 'Copia de seguridad creada correctamente', + 'backup.toast.createError': 'No se pudo crear la copia', + 'backup.toast.restored': 'Copia restaurada. La página se recargará…', + 'backup.toast.restoreError': 'No se pudo restaurar', + 'backup.toast.uploadError': 'No se pudo subir', + 'backup.toast.deleted': 'Copia eliminada', + 'backup.toast.deleteError': 'No se pudo eliminar', + 'backup.toast.downloadError': 'La descarga falló', + 'backup.toast.settingsSaved': 'Ajustes de copia automática guardados', + 'backup.toast.settingsError': 'No se pudieron guardar los ajustes', + 'backup.auto.title': 'Copia automática', + 'backup.auto.subtitle': + 'Copia de seguridad automática según una programación', + 'backup.auto.enable': 'Activar copia automática', + 'backup.auto.enableHint': + 'Se crearán copias automáticamente según la frecuencia elegida', + 'backup.auto.interval': 'Intervalo', + 'backup.auto.hour': 'Ejecutar a la hora', + 'backup.auto.hourHint': 'Hora local del servidor (formato {format})', + 'backup.auto.dayOfWeek': 'Día de la semana', + 'backup.auto.dayOfMonth': 'Día del mes', + 'backup.auto.dayOfMonthHint': + 'Limitado a 1–28 para compatibilidad con todos los meses', + 'backup.auto.scheduleSummary': 'Programación', + 'backup.auto.summaryDaily': 'Todos los días a las {hour}:00', + 'backup.auto.summaryWeekly': 'Cada {day} a las {hour}:00', + 'backup.auto.summaryMonthly': 'El día {day} de cada mes a las {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'La copia automática está configurada mediante variables de entorno Docker. Para cambiar estos ajustes, actualiza tu docker-compose.yml y reinicia el contenedor.', + 'backup.auto.copyEnv': 'Copiar variables de entorno Docker', + 'backup.auto.envCopied': + 'Variables de entorno Docker copiadas al portapapeles', + 'backup.auto.keepLabel': 'Eliminar copias antiguas después de', + 'backup.dow.sunday': 'Dom', + 'backup.dow.monday': 'Lun', + 'backup.dow.tuesday': 'Mar', + 'backup.dow.wednesday': 'Mié', + 'backup.dow.thursday': 'Jue', + 'backup.dow.friday': 'Vie', + 'backup.dow.saturday': 'Sáb', + 'backup.interval.hourly': 'Cada hora', + 'backup.interval.daily': 'Diaria', + 'backup.interval.weekly': 'Semanal', + 'backup.interval.monthly': 'Mensual', + 'backup.keep.1day': '1 día', + 'backup.keep.3days': '3 días', + 'backup.keep.7days': '7 días', + 'backup.keep.14days': '14 días', + 'backup.keep.30days': '30 días', + 'backup.keep.forever': 'Conservar para siempre', + 'backup.restoreConfirmTitle': '¿Restaurar copia?', + 'backup.restoreWarning': + 'Todos los datos actuales (viajes, lugares, usuarios, subidas) serán reemplazados permanentemente por la copia. Esta acción no se puede deshacer.', + 'backup.restoreTip': + 'Consejo: crea una copia del estado actual antes de restaurar.', + 'backup.restoreConfirm': 'Sí, restaurar', +}; +export default backup; diff --git a/shared/src/i18n/es/budget.ts b/shared/src/i18n/es/budget.ts new file mode 100644 index 00000000..0fb7c5e2 --- /dev/null +++ b/shared/src/i18n/es/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Presupuesto', + 'budget.exportCsv': 'Exportar CSV', + 'budget.emptyTitle': 'Aún no se ha creado ningún presupuesto', + 'budget.emptyText': + 'Crea categorías y entradas para planificar el presupuesto de tu viaje', + 'budget.emptyPlaceholder': 'Introduce el nombre de la categoría...', + 'budget.createCategory': 'Crear categoría', + 'budget.category': 'Categoría', + 'budget.categoryName': 'Nombre de la categoría', + 'budget.table.name': 'Nombre', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Personas', + 'budget.table.days': 'Días', + 'budget.table.perPerson': 'Por persona', + 'budget.table.perDay': 'Por día', + 'budget.table.perPersonDay': 'Por pers. / día', + 'budget.table.note': 'Nota', + 'budget.table.date': 'Fecha', + 'budget.newEntry': 'Nueva entrada', + 'budget.defaultEntry': 'Nueva entrada', + 'budget.defaultCategory': 'Nueva categoría', + 'budget.total': 'Total', + 'budget.totalBudget': 'Presupuesto total', + 'budget.byCategory': 'Por categoría', + 'budget.editTooltip': 'Haz clic para editar', + 'budget.linkedToReservation': + 'Vinculado a una reserva — edite el nombre allí', + 'budget.confirm.deleteCategory': + '¿Seguro que quieres eliminar la categoría "{name}" con {count} entradas?', + 'budget.deleteCategory': 'Eliminar categoría', + 'budget.perPerson': 'Por persona', + 'budget.paid': 'Pagado', + 'budget.open': 'Abrir', + 'budget.noMembers': 'No hay miembros asignados', + 'budget.settlement': 'Liquidación', + 'budget.settlementInfo': + 'Haz clic en el avatar de un miembro en una partida del presupuesto para marcarlo en verde — esto significa que ha pagado. La liquidación muestra quién debe cuánto a quién.', + 'budget.netBalances': 'Saldos netos', + 'budget.categoriesLabel': 'categorías', +}; +export default budget; diff --git a/shared/src/i18n/es/categories.ts b/shared/src/i18n/es/categories.ts new file mode 100644 index 00000000..1363cb6e --- /dev/null +++ b/shared/src/i18n/es/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categorías', + 'categories.subtitle': 'Gestiona categorías para lugares', + 'categories.new': 'Nueva categoría', + 'categories.empty': 'Aún no hay categorías', + 'categories.namePlaceholder': 'Nombre de la categoría', + 'categories.icon': 'Icono', + 'categories.color': 'Color', + 'categories.customColor': 'Elegir color personalizado', + 'categories.preview': 'Vista previa', + 'categories.defaultName': 'Categoría', + 'categories.update': 'Actualizar', + 'categories.create': 'Crear', + 'categories.confirm.delete': + '¿Eliminar la categoría? Los lugares de esta categoría no se eliminarán.', + 'categories.toast.loadError': 'No se pudieron cargar las categorías', + 'categories.toast.nameRequired': 'Introduce un nombre', + 'categories.toast.updated': 'Categoría actualizada', + 'categories.toast.created': 'Categoría creada', + 'categories.toast.saveError': 'No se pudo guardar', + 'categories.toast.deleted': 'Categoría eliminada', + 'categories.toast.deleteError': 'No se pudo eliminar', +}; +export default categories; diff --git a/shared/src/i18n/es/collab.ts b/shared/src/i18n/es/collab.ts new file mode 100644 index 00000000..5d3ccc09 --- /dev/null +++ b/shared/src/i18n/es/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Mensajes', + 'collab.tabs.notes': 'Notas', + 'collab.tabs.polls': 'Encuestas', + 'collab.whatsNext.title': 'Qué viene ahora', + 'collab.whatsNext.today': 'Hoy', + 'collab.whatsNext.tomorrow': 'Mañana', + 'collab.whatsNext.empty': 'No hay actividades próximas', + 'collab.whatsNext.until': 'hasta', + 'collab.whatsNext.emptyHint': 'Las actividades con hora aparecerán aquí', + 'collab.chat.send': 'Enviar', + 'collab.chat.placeholder': 'Escribe un mensaje...', + 'collab.chat.empty': 'Empieza la conversación', + 'collab.chat.emptyHint': + 'Los mensajes se comparten con todos los miembros del viaje', + 'collab.chat.emptyDesc': + 'Comparte ideas, planes y novedades con tu grupo de viaje', + 'collab.chat.today': 'Hoy', + 'collab.chat.yesterday': 'Ayer', + 'collab.chat.deletedMessage': 'eliminó un mensaje', + 'collab.chat.reply': 'Responder', + 'collab.chat.loadMore': 'Cargar mensajes anteriores', + 'collab.chat.justNow': 'justo ahora', + 'collab.chat.minutesAgo': 'hace {n} min', + 'collab.chat.hoursAgo': 'hace {n} h', + 'collab.notes.title': 'Notas', + 'collab.notes.new': 'Nueva nota', + 'collab.notes.empty': 'Aún no hay notas', + 'collab.notes.emptyHint': 'Empieza a capturar ideas y planes', + 'collab.notes.all': 'Todas', + 'collab.notes.titlePlaceholder': 'Título de la nota', + 'collab.notes.contentPlaceholder': 'Escribe algo...', + 'collab.notes.categoryPlaceholder': 'Categoría', + 'collab.notes.newCategory': 'Nueva categoría...', + 'collab.notes.category': 'Categoría', + 'collab.notes.noCategory': 'Sin categoría', + 'collab.notes.color': 'Color', + 'collab.notes.save': 'Guardar', + 'collab.notes.cancel': 'Cancelar', + 'collab.notes.edit': 'Editar', + 'collab.notes.delete': 'Eliminar', + 'collab.notes.pin': 'Fijar', + 'collab.notes.unpin': 'Desfijar', + 'collab.notes.daysAgo': 'hace {n} d', + 'collab.notes.categorySettings': 'Gestionar categorías', + 'collab.notes.create': 'Crear', + 'collab.notes.website': 'Sitio web', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Adjuntar archivos', + 'collab.notes.noCategoriesYet': 'Aún no hay categorías', + 'collab.notes.emptyDesc': 'Crea una nota para empezar', + 'collab.polls.title': 'Encuestas', + 'collab.polls.new': 'Nueva encuesta', + 'collab.polls.empty': 'Aún no hay encuestas', + 'collab.polls.emptyHint': 'Pregunta al grupo y votad juntos', + 'collab.polls.question': 'Pregunta', + 'collab.polls.questionPlaceholder': '¿Qué deberíamos hacer?', + 'collab.polls.addOption': '+ Añadir opción', + 'collab.polls.optionPlaceholder': 'Opción {n}', + 'collab.polls.create': 'Crear encuesta', + 'collab.polls.close': 'Cerrar', + 'collab.polls.closed': 'Cerrada', + 'collab.polls.votes': '{n} votos', + 'collab.polls.vote': '{n} voto', + 'collab.polls.multipleChoice': 'Selección múltiple', + 'collab.polls.multiChoice': 'Selección múltiple', + 'collab.polls.deadline': 'Fecha límite', + 'collab.polls.option': 'Opción', + 'collab.polls.options': 'Opciones', + 'collab.polls.delete': 'Eliminar', + 'collab.polls.closedSection': 'Cerradas', +}; +export default collab; diff --git a/shared/src/i18n/es/common.ts b/shared/src/i18n/es/common.ts new file mode 100644 index 00000000..8a59385b --- /dev/null +++ b/shared/src/i18n/es/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Guardar', + 'common.showMore': 'Ver más', + 'common.showLess': 'Ver menos', + 'common.cancel': 'Cancelar', + 'common.clear': 'Borrar', + 'common.delete': 'Eliminar', + 'common.edit': 'Editar', + 'common.add': 'Añadir', + 'common.loading': 'Cargando...', + 'common.import': 'Importar', + 'common.select': 'Seleccionar', + 'common.selectAll': 'Seleccionar todo', + 'common.deselectAll': 'Deseleccionar todo', + 'common.error': 'Error', + 'common.unknownError': 'Error desconocido', + 'common.tooManyAttempts': + 'Demasiados intentos. Inténtelo de nuevo más tarde.', + 'common.back': 'Atrás', + 'common.all': 'Todo', + 'common.close': 'Cerrar', + 'common.open': 'Abrir', + 'common.upload': 'Subir', + 'common.search': 'Buscar', + 'common.confirm': 'Confirmar', + 'common.ok': 'Aceptar', + 'common.yes': 'Sí', + 'common.no': 'No', + 'common.or': 'o', + 'common.none': 'Ninguno', + 'common.date': 'Fecha', + 'common.rename': 'Renombrar', + 'common.discardChanges': 'Descartar cambios', + 'common.discard': 'Descartar', + 'common.name': 'Nombre', + 'common.email': 'Correo', + 'common.password': 'Contraseña', + 'common.saving': 'Guardando...', + 'common.saved': 'Guardado', + 'common.expand': 'Expandir', + 'common.collapse': 'Contraer', + 'common.update': 'Actualizar', + 'common.change': 'Cambiar', + 'common.uploading': 'Subiendo…', + 'common.backToPlanning': 'Volver a la planificación', + 'common.reset': 'Restablecer', + 'common.copy': 'Copiar', + 'common.copied': 'Copiado', + 'common.justNow': 'justo ahora', + 'common.hoursAgo': 'hace {count}h', + 'common.daysAgo': 'hace {count}d', +}; +export default common; diff --git a/shared/src/i18n/es/dashboard.ts b/shared/src/i18n/es/dashboard.ts new file mode 100644 index 00000000..9c870883 --- /dev/null +++ b/shared/src/i18n/es/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Mis viajes', + 'dashboard.subtitle.loading': 'Cargando viajes...', + 'dashboard.subtitle.trips': '{count} viajes ({archived} archivados)', + 'dashboard.subtitle.empty': 'Empieza tu primer viaje', + 'dashboard.subtitle.activeOne': '{count} viaje activo', + 'dashboard.subtitle.activeMany': '{count} viajes activos', + 'dashboard.subtitle.archivedSuffix': ' · {count} archivados', + 'dashboard.newTrip': 'Nuevo viaje', + 'dashboard.gridView': 'Vista de cuadrícula', + 'dashboard.listView': 'Vista de lista', + 'dashboard.currency': 'Divisa', + 'dashboard.timezone': 'Zonas horarias', + 'dashboard.localTime': 'Hora local', + 'dashboard.timezoneCustomTitle': 'Zona horaria personalizada', + 'dashboard.timezoneCustomLabelPlaceholder': 'Nombre (opcional)', + 'dashboard.timezoneCustomTzPlaceholder': 'ej. America/New_York', + 'dashboard.timezoneCustomAdd': 'Añadir', + 'dashboard.timezoneCustomErrorEmpty': 'Introduce una zona horaria', + 'dashboard.timezoneCustomErrorInvalid': + 'Zona horaria no válida. Usa formato como Europe/Madrid', + 'dashboard.timezoneCustomErrorDuplicate': 'Ya añadida', + 'dashboard.emptyTitle': 'Aún no hay viajes', + 'dashboard.emptyText': 'Crea tu primer viaje y empieza a planificar', + 'dashboard.emptyButton': 'Crear primer viaje', + 'dashboard.nextTrip': 'Próximo viaje', + 'dashboard.shared': 'Compartido', + 'dashboard.sharedBy': 'Compartido por {name}', + 'dashboard.days': 'Días', + 'dashboard.places': 'Lugares', + 'dashboard.members': 'Compañeros de viaje', + 'dashboard.archive': 'Archivar', + 'dashboard.copyTrip': 'Copiar', + 'dashboard.copySuffix': 'copia', + 'dashboard.restore': 'Restaurar', + 'dashboard.archived': 'Archivado', + 'dashboard.status.ongoing': 'En curso', + 'dashboard.status.today': 'Hoy', + 'dashboard.status.tomorrow': 'Mañana', + 'dashboard.status.past': 'Pasado', + 'dashboard.status.daysLeft': 'Quedan {count} días', + 'dashboard.toast.loadError': 'No se pudieron cargar los viajes', + 'dashboard.toast.created': '¡Viaje creado correctamente!', + 'dashboard.toast.createError': 'No se pudo crear el viaje', + 'dashboard.toast.updated': '¡Viaje actualizado!', + 'dashboard.toast.updateError': 'No se pudo actualizar el viaje', + 'dashboard.toast.deleted': 'Viaje eliminado', + 'dashboard.toast.deleteError': 'No se pudo eliminar el viaje', + 'dashboard.toast.archived': 'Viaje archivado', + 'dashboard.toast.archiveError': 'No se pudo archivar el viaje', + 'dashboard.toast.restored': 'Viaje restaurado', + 'dashboard.toast.restoreError': 'No se pudo restaurar el viaje', + 'dashboard.toast.copied': '¡Viaje copiado!', + 'dashboard.toast.copyError': 'No se pudo copiar el viaje', + 'dashboard.confirm.delete': + '¿Eliminar el viaje "{title}"? Todos los lugares y planes se borrarán permanentemente.', + 'dashboard.editTrip': 'Editar viaje', + 'dashboard.createTrip': 'Crear nuevo viaje', + 'dashboard.tripTitle': 'Título', + 'dashboard.tripTitlePlaceholder': 'p. ej. Verano en Japón', + 'dashboard.tripDescription': 'Descripción', + 'dashboard.tripDescriptionPlaceholder': '¿De qué trata este viaje?', + 'dashboard.startDate': 'Fecha de inicio', + 'dashboard.endDate': 'Fecha de fin', + 'dashboard.dayCount': 'Número de días', + 'dashboard.dayCountHint': + 'Cuántos días planificar cuando no se han establecido fechas de viaje.', + 'dashboard.noDateHint': + 'Sin fecha definida: se crearán 7 días por defecto. Puedes cambiarlo cuando quieras.', + 'dashboard.coverImage': 'Imagen de portada', + 'dashboard.addCoverImage': 'Añadir imagen de portada', + 'dashboard.addMembers': 'Compañeros de viaje', + 'dashboard.addMember': 'Añadir miembro', + 'dashboard.coverSaved': 'Imagen de portada guardada', + 'dashboard.coverUploadError': 'Error al subir la imagen', + 'dashboard.coverRemoveError': 'Error al eliminar la imagen', + 'dashboard.titleRequired': 'El título es obligatorio', + 'dashboard.endDateError': 'La fecha de fin debe ser posterior a la de inicio', + 'dashboard.greeting.morning': 'Buenos días,', + 'dashboard.greeting.afternoon': 'Buenas tardes,', + 'dashboard.greeting.evening': 'Buenas noches,', + 'dashboard.mobile.liveNow': 'En vivo ahora', + 'dashboard.mobile.tripProgress': 'Progreso del viaje', + 'dashboard.mobile.daysLeft': '{count} días restantes', + 'dashboard.mobile.places': 'Lugares', + 'dashboard.mobile.buddies': 'Compañeros', + 'dashboard.mobile.newTrip': 'Nuevo viaje', + 'dashboard.mobile.currency': 'Moneda', + 'dashboard.mobile.timezone': 'Zona horaria', + 'dashboard.mobile.upcomingTrips': 'Próximos viajes', + 'dashboard.mobile.yourTrips': 'Tus viajes', + 'dashboard.mobile.trips': 'viajes', + 'dashboard.mobile.starts': 'Comienza', + 'dashboard.mobile.duration': 'Duración', + 'dashboard.mobile.day': 'día', + 'dashboard.mobile.days': 'días', + 'dashboard.mobile.ongoing': 'En curso', + 'dashboard.mobile.startsToday': 'Comienza hoy', + 'dashboard.mobile.tomorrow': 'Mañana', + 'dashboard.mobile.inDays': 'En {count} días', + 'dashboard.mobile.inMonths': 'En {count} meses', + 'dashboard.mobile.completed': 'Completado', + 'dashboard.mobile.currencyConverter': 'Conversor de monedas', +}; +export default dashboard; diff --git a/shared/src/i18n/es/day.ts b/shared/src/i18n/es/day.ts new file mode 100644 index 00000000..08fe1832 --- /dev/null +++ b/shared/src/i18n/es/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Probabilidad de lluvia', + 'day.precipitation': 'Precipitación', + 'day.wind': 'Viento', + 'day.sunrise': 'Amanecer', + 'day.sunset': 'Atardecer', + 'day.hourlyForecast': 'Pronóstico por horas', + 'day.climateHint': + 'Promedios históricos: el pronóstico real está disponible dentro de los 16 días previos a la fecha.', + 'day.noWeather': + 'No hay datos meteorológicos disponibles. Añade un lugar con coordenadas.', + 'day.overview': 'Resumen diario', + 'day.accommodation': 'Alojamiento', + 'day.addAccommodation': 'Añadir alojamiento', + 'day.hotelDayRange': 'Aplicar a los días', + 'day.noPlacesForHotel': 'Añade primero lugares al viaje', + 'day.allDays': 'Todos', + 'day.checkIn': 'Registro de entrada', + 'day.checkInUntil': 'Hasta', + 'day.checkOut': 'Registro de salida', + 'day.confirmation': 'Confirmación', + 'day.editAccommodation': 'Editar alojamiento', + 'day.reservations': 'Reservas', +}; +export default day; diff --git a/shared/src/i18n/es/dayplan.ts b/shared/src/i18n/es/dayplan.ts new file mode 100644 index 00000000..309cdd9b --- /dev/null +++ b/shared/src/i18n/es/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Exportar calendario (ICS)', + 'dayplan.emptyDay': 'No hay lugares planificados para este día', + 'dayplan.addNote': 'Añadir nota', + 'dayplan.editNote': 'Editar nota', + 'dayplan.noteAdd': 'Añadir nota', + 'dayplan.noteEdit': 'Editar nota', + 'dayplan.noteTitle': 'Nota', + 'dayplan.noteSubtitle': 'Nota diaria', + 'dayplan.totalCost': 'Coste total', + 'dayplan.days': 'Días', + 'dayplan.dayN': 'Día {n}', + 'dayplan.calculating': 'Calculando...', + 'dayplan.route': 'Ruta', + 'dayplan.optimize': 'Optimizar', + 'dayplan.optimized': 'Ruta optimizada', + 'dayplan.routeError': 'No se pudo calcular la ruta', + 'dayplan.toast.needTwoPlaces': + 'Se necesitan al menos dos lugares para optimizar la ruta', + 'dayplan.toast.routeOptimized': 'Ruta optimizada', + 'dayplan.toast.noGeoPlaces': + 'No se encontraron lugares con coordenadas para calcular la ruta', + 'dayplan.confirmed': 'Confirmado', + 'dayplan.pendingRes': 'Pendiente', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Exportar plan diario como PDF', + 'dayplan.pdfError': 'No se pudo exportar el PDF', + 'dayplan.cannotReorderTransport': + 'Las reservas con hora fija no se pueden reordenar', + 'dayplan.confirmRemoveTimeTitle': '¿Eliminar hora?', + 'dayplan.confirmRemoveTimeBody': + 'Este lugar tiene una hora fija ({time}). Al moverlo se eliminará la hora y se permitirá el orden libre.', + 'dayplan.confirmRemoveTimeAction': 'Eliminar hora y mover', + 'dayplan.cannotDropOnTimed': + 'No se pueden colocar elementos entre entradas con hora fija', + 'dayplan.cannotBreakChronology': + 'Esto rompería el orden cronológico de los elementos y reservas programados', + 'dayplan.mobile.addPlace': 'Añadir lugar', + 'dayplan.mobile.searchPlaces': 'Buscar lugares...', + 'dayplan.mobile.allAssigned': 'Todos los lugares asignados', + 'dayplan.mobile.noMatch': 'Sin coincidencias', + 'dayplan.mobile.createNew': 'Crear nuevo lugar', +}; +export default dayplan; diff --git a/shared/src/i18n/es/externalNotifications.ts b/shared/src/i18n/es/externalNotifications.ts new file mode 100644 index 00000000..8672d847 --- /dev/null +++ b/shared/src/i18n/es/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const es: NotificationLocale = { + email: { + footer: + 'Recibiste esto porque tienes las notificaciones activadas en TREK.', + manage: 'Gestionar preferencias', + madeWith: 'Made with', + openTrek: 'Abrir TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Invitación a "${p.trip}"`, + body: `${p.actor} invitó a ${p.invitee || 'un miembro'} al viaje "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nueva reserva: ${p.booking}`, + body: `${p.actor} añadió una reserva "${p.booking}" (${p.type}) a "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Recordatorio: ${p.trip}`, + body: `¡Tu viaje "${p.trip}" se acerca!`, + }), + todo_due: (p) => ({ + title: `Tarea pendiente: ${p.todo}`, + body: `"${p.todo}" en "${p.trip}" vence el ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Invitación Vacay Fusion', + body: `${p.actor} te invitó a fusionar planes de vacaciones. Abre TREK para aceptar o rechazar.`, + }), + photos_shared: (p) => ({ + title: `${p.count} fotos compartidas`, + body: `${p.actor} compartió ${p.count} foto(s) en "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nuevo mensaje en "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Equipaje: ${p.category}`, + body: `${p.actor} te asignó a la categoría "${p.category}" en "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nueva versión de TREK disponible', + body: `TREK ${p.version} ya está disponible. Visita el panel de administración para actualizar.`, + }), + synology_session_cleared: () => ({ + title: 'Sesión de Synology cerrada', + body: 'Tu cuenta o URL de Synology ha cambiado. Has cerrado sesión en Synology Photos.', + }), + }, + passwordReset: { + subject: 'Restablecer tu contraseña', + greeting: 'Hola', + body: 'Recibimos una solicitud para restablecer la contraseña de tu cuenta de TREK. Haz clic en el botón de abajo para establecer una nueva contraseña.', + ctaIntro: 'Restablecer contraseña', + expiry: 'Este enlace caduca en 60 minutos.', + ignore: + 'Si no solicitaste esto, puedes ignorar este correo — tu contraseña no cambiará.', + }, +}; + +export default es; diff --git a/shared/src/i18n/es/files.ts b/shared/src/i18n/es/files.ts new file mode 100644 index 00000000..c0e59aa2 --- /dev/null +++ b/shared/src/i18n/es/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Archivos', + 'files.pageTitle': 'Archivos y documentos', + 'files.subtitle': '{count} archivos para {trip}', + 'files.download': 'Descargar', + 'files.openError': 'No se pudo abrir el archivo', + 'files.downloadPdf': 'Descargar PDF', + 'files.count': '{count} archivos', + 'files.countSingular': '1 archivo', + 'files.uploaded': '{count} archivos subidos', + 'files.uploadError': 'La subida falló', + 'files.dropzone': 'Arrastra aquí los archivos', + 'files.dropzoneHint': 'o haz clic para explorar', + 'files.allowedTypes': + 'Imágenes, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Máx. 50 MB', + 'files.uploading': 'Subiendo...', + 'files.filterAll': 'Todo', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Imágenes', + 'files.filterDocs': 'Documentos', + 'files.filterCollab': 'Notas de colaboración', + 'files.sourceCollab': 'Desde notas de colaboración', + 'files.empty': 'Aún no hay archivos', + 'files.emptyHint': 'Sube archivos para adjuntarlos a tu viaje', + 'files.openTab': 'Abrir en una pestaña nueva', + 'files.confirm.delete': '¿Seguro que quieres eliminar este archivo?', + 'files.toast.deleted': 'Archivo eliminado', + 'files.toast.deleteError': 'No se pudo eliminar el archivo', + 'files.sourcePlan': 'Plan diario', + 'files.sourceBooking': 'Reserva', + 'files.sourceTransport': 'Transporte', + 'files.attach': 'Adjuntar', + 'files.pasteHint': + 'También puedes pegar imágenes desde el portapapeles (Ctrl+V)', + 'files.trash': 'Papelera', + 'files.trashEmpty': 'La papelera está vacía', + 'files.emptyTrash': 'Vaciar papelera', + 'files.restore': 'Restaurar', + 'files.star': 'Destacar', + 'files.unstar': 'Quitar destacado', + 'files.assign': 'Asignar', + 'files.assignTitle': 'Asignar archivo', + 'files.assignPlace': 'Lugar', + 'files.assignBooking': 'Reserva', + 'files.assignTransport': 'Transporte', + 'files.unassigned': 'Sin asignar', + 'files.unlink': 'Eliminar vínculo', + 'files.noteLabel': 'Nota', + 'files.notePlaceholder': 'Añadir una nota...', + 'files.toast.trashed': 'Movido a la papelera', + 'files.toast.restored': 'Archivo restaurado', + 'files.toast.trashEmptied': 'Papelera vaciada', + 'files.toast.assigned': 'Archivo asignado', + 'files.toast.assignError': 'Error al asignar', + 'files.toast.restoreError': 'Error al restaurar', + 'files.confirm.permanentDelete': + 'Eliminar este archivo permanentemente? No se puede deshacer.', + 'files.confirm.emptyTrash': + 'Eliminar todos los archivos de la papelera? No se puede deshacer.', +}; +export default files; diff --git a/shared/src/i18n/es/index.ts b/shared/src/i18n/es/index.ts new file mode 100644 index 00000000..829620a8 --- /dev/null +++ b/shared/src/i18n/es/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...airport, + ...map, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/es/inspector.ts b/shared/src/i18n/es/inspector.ts new file mode 100644 index 00000000..386915e4 --- /dev/null +++ b/shared/src/i18n/es/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Abierto', + 'inspector.closed': 'Cerrado', + 'inspector.openingHours': 'Horario de apertura', + 'inspector.showHours': 'Mostrar horario', + 'inspector.files': 'Archivos', + 'inspector.filesCount': '{count} archivos', + 'inspector.removeFromDay': 'Quitar del día', + 'inspector.remove': 'Eliminar', + 'inspector.addToDay': 'Añadir al día', + 'inspector.confirmedRes': 'Reserva confirmada', + 'inspector.pendingRes': 'Reserva pendiente', + 'inspector.google': 'Abrir en Google Maps', + 'inspector.website': 'Abrir la web', + 'inspector.addRes': 'Reserva', + 'inspector.editRes': 'Editar reserva', + 'inspector.participants': 'Participantes', + 'inspector.trackStats': 'Datos de la ruta', +}; +export default inspector; diff --git a/shared/src/i18n/es/journey.ts b/shared/src/i18n/es/journey.ts new file mode 100644 index 00000000..f551161d --- /dev/null +++ b/shared/src/i18n/es/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Buscar viajes…', + 'journey.search.noResults': 'Ningún viaje coincide con "{query}"', + 'journey.title': 'Travesía', + 'journey.subtitle': 'Registra tus viajes en tiempo real', + 'journey.new': 'Nueva travesía', + 'journey.create': 'Crear', + 'journey.titlePlaceholder': '¿A dónde vas?', + 'journey.empty': 'Aún no hay travesías', + 'journey.emptyHint': 'Empieza a documentar tu próximo viaje', + 'journey.deleted': 'Travesía eliminada', + 'journey.createError': 'No se pudo crear la travesía', + 'journey.deleteError': 'No se pudo eliminar la travesía', + 'journey.deleteConfirmTitle': 'Eliminar', + 'journey.deleteConfirmMessage': + '¿Eliminar "{title}"? Esta acción no se puede deshacer.', + 'journey.deleteConfirmGeneric': '¿Estás seguro de que quieres eliminar esto?', + 'journey.notFound': 'Travesía no encontrada', + 'journey.photos': 'Fotos', + 'journey.timelineEmpty': 'Aún no hay paradas', + 'journey.timelineEmptyHint': + 'Añade un registro de ubicación o escribe una entrada de diario para empezar', + 'journey.status.draft': 'Borrador', + 'journey.status.active': 'Activa', + 'journey.status.completed': 'Completada', + 'journey.status.upcoming': 'Próxima', + 'journey.status.archived': 'Archivado', + 'journey.checkin.add': 'Registrar ubicación', + 'journey.checkin.namePlaceholder': 'Nombre del lugar', + 'journey.checkin.notesPlaceholder': 'Notas (opcional)', + 'journey.checkin.save': 'Guardar', + 'journey.checkin.error': 'No se pudo guardar el registro', + 'journey.entry.add': 'Diario', + 'journey.entry.edit': 'Editar entrada', + 'journey.entry.titlePlaceholder': 'Título (opcional)', + 'journey.entry.bodyPlaceholder': '¿Qué pasó hoy?', + 'journey.entry.save': 'Guardar', + 'journey.entry.error': 'No se pudo guardar la entrada', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Error al subir', + 'journey.share.share': 'Compartir', + 'journey.share.public': 'Público', + 'journey.share.linkCopied': 'Enlace público copiado', + 'journey.share.disabled': 'Compartir público desactivado', + 'journey.editor.titlePlaceholder': 'Dale un nombre a este momento...', + 'journey.editor.bodyPlaceholder': 'Cuenta la historia de este día...', + 'journey.editor.placePlaceholder': 'Ubicación (opcional)', + 'journey.editor.tagsPlaceholder': + 'Etiquetas: joya oculta, mejor comida, hay que volver...', + 'journey.visibility.private': 'Privado', + 'journey.visibility.shared': 'Compartido', + 'journey.visibility.public': 'Público', + 'journey.emptyState.title': 'Tu historia empieza aquí', + 'journey.emptyState.subtitle': + 'Registra una ubicación o escribe tu primera entrada de diario', + 'journey.frontpage.subtitle': + 'Convierte tus viajes en historias que nunca olvidarás', + 'journey.frontpage.createJourney': 'Crear travesía', + 'journey.frontpage.activeJourney': 'Travesía activa', + 'journey.frontpage.allJourneys': 'Todas las travesías', + 'journey.frontpage.journeys': 'travesías', + 'journey.frontpage.createNew': 'Crear una nueva travesía', + 'journey.frontpage.createNewSub': + 'Elige viajes, escribe historias, comparte tus aventuras', + 'journey.frontpage.live': 'En vivo', + 'journey.frontpage.synced': 'Sincronizado', + 'journey.frontpage.continueWriting': 'Seguir escribiendo', + 'journey.frontpage.updated': 'Actualizado {time}', + 'journey.frontpage.suggestionLabel': 'El viaje acaba de terminar', + 'journey.frontpage.suggestionText': + 'Convierte {title} en una travesía', + 'journey.frontpage.dismiss': 'Descartar', + 'journey.frontpage.journeyName': 'Nombre de la travesía', + 'journey.frontpage.namePlaceholder': 'p. ej. Sudeste Asiático 2026', + 'journey.frontpage.selectTrips': 'Seleccionar viajes', + 'journey.frontpage.tripsSelected': 'viajes seleccionados', + 'journey.frontpage.trips': 'viajes', + 'journey.frontpage.placesImported': 'lugares serán importados', + 'journey.frontpage.places': 'lugares', + 'journey.detail.backToJourney': 'Volver a la travesía', + 'journey.detail.syncedWithTrips': 'Sincronizado con viajes', + 'journey.detail.addEntry': 'Añadir entrada', + 'journey.detail.newEntry': 'Nueva entrada', + 'journey.detail.editEntry': 'Editar entrada', + 'journey.detail.noEntries': 'Aún no hay entradas', + 'journey.detail.noEntriesHint': + 'Añade un viaje para empezar con entradas preliminares', + 'journey.detail.noPhotos': 'Aún no hay fotos', + 'journey.detail.noPhotosHint': + 'Sube fotos a las entradas o explora tu biblioteca de Immich/Synology', + 'journey.detail.journeyStats': 'Estadísticas de la travesía', + 'journey.detail.syncedTrips': 'Viajes sincronizados', + 'journey.detail.noTripsLinked': 'Aún no hay viajes vinculados', + 'journey.detail.contributors': 'Colaboradores', + 'journey.detail.readMore': 'Leer más', + 'journey.detail.prosCons': 'Pros y contras', + 'journey.detail.photos': 'fotos', + 'journey.detail.day': 'Día {number}', + 'journey.detail.places': 'lugares', + 'journey.stats.days': 'Días', + 'journey.stats.cities': 'Ciudades', + 'journey.stats.entries': 'Entradas', + 'journey.stats.photos': 'Fotos', + 'journey.stats.places': 'Lugares', + 'journey.skeletons.show': 'Mostrar sugerencias', + 'journey.skeletons.hide': 'Ocultar sugerencias', + 'journey.verdict.lovedIt': 'Me encantó', + 'journey.verdict.couldBeBetter': 'Podría mejorar', + 'journey.synced.places': 'lugares', + 'journey.synced.synced': 'sincronizado', + 'journey.editor.discardChangesConfirm': + 'Tienes cambios sin guardar. ¿Descartarlos?', + 'journey.editor.uploadFailed': 'Error al subir fotos', + 'journey.editor.uploadPhotos': 'Subir fotos', + 'journey.editor.uploading': 'Subiendo...', + 'journey.editor.uploadingProgress': 'Subiendo {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} de {total} fotos fallaron — guarda de nuevo para reintentar', + 'journey.editor.fromGallery': 'Desde galería', + 'journey.editor.allPhotosAdded': 'Todas las fotos ya fueron añadidas', + 'journey.editor.writeStory': 'Escribe tu historia...', + 'journey.editor.prosCons': 'Pros y contras', + 'journey.editor.pros': 'Pros', + 'journey.editor.cons': 'Contras', + 'journey.editor.proPlaceholder': 'Algo genial...', + 'journey.editor.conPlaceholder': 'No tan genial...', + 'journey.editor.addAnother': 'Añadir otro', + 'journey.editor.date': 'Fecha', + 'journey.editor.location': 'Ubicación', + 'journey.editor.searchLocation': 'Buscar ubicación...', + 'journey.editor.mood': 'Estado de ánimo', + 'journey.editor.weather': 'Clima', + 'journey.editor.photoFirst': '1º', + 'journey.editor.makeFirst': 'Hacer 1º', + 'journey.editor.searching': 'Buscando...', + 'journey.mood.amazing': 'Increíble', + 'journey.mood.good': 'Bien', + 'journey.mood.neutral': 'Neutral', + 'journey.mood.rough': 'Difícil', + 'journey.weather.sunny': 'Soleado', + 'journey.weather.partly': 'Parcialmente nublado', + 'journey.weather.cloudy': 'Nublado', + 'journey.weather.rainy': 'Lluvioso', + 'journey.weather.stormy': 'Tormentoso', + 'journey.weather.cold': 'Nevado', + 'journey.trips.linkTrip': 'Vincular viaje', + 'journey.trips.searchTrip': 'Buscar viaje', + 'journey.trips.searchPlaceholder': 'Nombre del viaje o destino...', + 'journey.trips.noTripsAvailable': 'No hay viajes disponibles', + 'journey.trips.link': 'Vincular', + 'journey.trips.tripLinked': 'Viaje vinculado', + 'journey.trips.linkFailed': 'No se pudo vincular el viaje', + 'journey.trips.addTrip': 'Añadir viaje', + 'journey.trips.unlinkTrip': 'Desvincular viaje', + 'journey.trips.unlinkMessage': + '¿Desvincular "{title}"? Todas las entradas y fotos sincronizadas de este viaje se eliminarán permanentemente. Esta acción no se puede deshacer.', + 'journey.trips.unlink': 'Desvincular', + 'journey.trips.tripUnlinked': 'Viaje desvinculado', + 'journey.trips.unlinkFailed': 'No se pudo desvincular el viaje', + 'journey.trips.noTripsLinkedSettings': 'No hay viajes vinculados', + 'journey.contributors.invite': 'Invitar colaborador', + 'journey.contributors.searchUser': 'Buscar usuario', + 'journey.contributors.searchPlaceholder': 'Nombre de usuario o correo...', + 'journey.contributors.noUsers': 'No se encontraron usuarios', + 'journey.contributors.role': 'Rol', + 'journey.contributors.added': 'Colaborador añadido', + 'journey.contributors.addFailed': 'No se pudo añadir al colaborador', + 'journey.share.publicShare': 'Compartir público', + 'journey.share.createLink': 'Crear enlace para compartir', + 'journey.share.linkCreated': 'Enlace para compartir creado', + 'journey.share.createFailed': 'No se pudo crear el enlace', + 'journey.share.copy': 'Copiar', + 'journey.share.copied': '¡Copiado!', + 'journey.share.timeline': 'Cronología', + 'journey.share.gallery': 'Galería', + 'journey.share.map': 'Mapa', + 'journey.share.removeLink': 'Eliminar enlace para compartir', + 'journey.share.linkDeleted': 'Enlace para compartir eliminado', + 'journey.share.deleteFailed': 'No se pudo eliminar', + 'journey.share.updateFailed': 'No se pudo actualizar', + 'journey.invite.role': 'Rol', + 'journey.invite.viewer': 'Lector', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Invitar', + 'journey.invite.inviting': 'Invitando...', + 'journey.settings.title': 'Ajustes de la travesía', + 'journey.settings.coverImage': 'Imagen de portada', + 'journey.settings.changeCover': 'Cambiar portada', + 'journey.settings.addCover': 'Añadir imagen de portada', + 'journey.settings.name': 'Nombre', + 'journey.settings.subtitle': 'Subtítulo', + 'journey.settings.subtitlePlaceholder': 'p. ej. Tailandia, Vietnam y Camboya', + 'journey.settings.endJourney': 'Archivar viaje', + 'journey.settings.reopenJourney': 'Restaurar viaje', + 'journey.settings.archived': 'Viaje archivado', + 'journey.settings.reopened': 'Viaje reabierto', + 'journey.settings.endDescription': + 'Oculta la insignia En Vivo. Puedes reabrirlo en cualquier momento.', + 'journey.settings.delete': 'Eliminar', + 'journey.settings.deleteJourney': 'Eliminar travesía', + 'journey.settings.deleteMessage': + '¿Eliminar "{title}"? Todas las entradas y fotos se perderán.', + 'journey.settings.saved': 'Ajustes guardados', + 'journey.settings.saveFailed': 'No se pudo guardar', + 'journey.settings.coverUpdated': 'Portada actualizada', + 'journey.settings.coverFailed': 'Error al subir', + 'journey.settings.failedToDelete': 'Error al eliminar', + 'journey.entries.deleteTitle': 'Eliminar entrada', + 'journey.photosUploaded': '{count} fotos subidas', + 'journey.photosUploadFailed': 'Algunas fotos no se pudieron subir', + 'journey.photosAdded': '{count} fotos añadidas', + 'journey.public.notFound': 'No encontrado', + 'journey.public.notFoundMessage': + 'Esta travesía no existe o el enlace ha expirado.', + 'journey.public.readOnly': 'Solo lectura · Travesía pública', + 'journey.public.tagline': 'Kit de recursos y exploración de viajes', + 'journey.public.sharedVia': 'Compartido mediante', + 'journey.public.madeWith': 'Hecho con', + 'journey.pdf.journeyBook': 'Libro de travesía', + 'journey.pdf.madeWith': 'Hecho con TREK', + 'journey.pdf.day': 'Día', + 'journey.pdf.theEnd': 'Fin', + 'journey.pdf.saveAsPdf': 'Guardar como PDF', + 'journey.pdf.pages': 'páginas', + 'journey.picker.tripPeriod': 'Período del viaje', + 'journey.picker.dateRange': 'Rango de fechas', + 'journey.picker.allPhotos': 'Todas las fotos', + 'journey.picker.albums': 'Álbumes', + 'journey.picker.selected': 'seleccionados', + 'journey.picker.addTo': 'Añadir a', + 'journey.picker.newGallery': 'Nueva galería', + 'journey.picker.selectAll': 'Seleccionar todo', + 'journey.picker.deselectAll': 'Deseleccionar todo', + 'journey.picker.noAlbums': 'No se encontraron álbumes', + 'journey.picker.selectDate': 'Seleccionar fecha', + 'journey.picker.search': 'Buscar', +}; +export default journey; diff --git a/shared/src/i18n/es/login.ts b/shared/src/i18n/es/login.ts new file mode 100644 index 00000000..e73bed86 --- /dev/null +++ b/shared/src/i18n/es/login.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Inicio de sesión fallido. Revisa tus credenciales.', + 'login.tagline': 'Tus viajes.\nTu plan.', + 'login.description': + 'Planifica viajes en colaboración con mapas interactivos, presupuestos y sincronización en tiempo real.', + 'login.features.maps': 'Mapas interactivos', + 'login.features.mapsDesc': 'Google Places, rutas y agrupación', + 'login.features.realtime': 'Sincronización en tiempo real', + 'login.features.realtimeDesc': 'Planificad juntos mediante WebSocket', + 'login.features.budget': 'Control de presupuesto', + 'login.features.budgetDesc': 'Categorías, gráficos y costes por persona', + 'login.features.collab': 'Colaboración', + 'login.features.collabDesc': 'Multiusuario con viajes compartidos', + 'login.features.packing': 'Listas de equipaje', + 'login.features.packingDesc': 'Categorías, progreso y sugerencias', + 'login.features.bookings': 'Reservas', + 'login.features.bookingsDesc': 'Vuelos, hoteles, restaurantes y más', + 'login.features.files': 'Documentos', + 'login.features.filesDesc': 'Sube y gestiona documentos', + 'login.features.routes': 'Rutas inteligentes', + 'login.features.routesDesc': + 'Optimización automática y exportación a Google Maps', + 'login.selfHosted': + 'Autoalojado · Código abierto · Tus datos siguen siendo tuyos', + 'login.title': 'Iniciar sesión', + 'login.subtitle': 'Bienvenido de nuevo', + 'login.signingIn': 'Iniciando sesión…', + 'login.signIn': 'Entrar', + 'login.createAdmin': 'Crear cuenta de administrador', + 'login.createAdminHint': + 'Configura la primera cuenta administradora de TREK.', + 'login.setNewPassword': 'Establecer nueva contraseña', + 'login.setNewPasswordHint': 'Debe cambiar su contraseña antes de continuar.', + 'login.createAccount': 'Crear cuenta', + 'login.createAccountHint': 'Crea una cuenta nueva.', + 'login.creating': 'Creando…', + 'login.noAccount': '¿No tienes cuenta?', + 'login.hasAccount': '¿Ya tienes cuenta?', + 'login.register': 'Registrarse', + 'login.emailPlaceholder': 'tu@correo.com', + 'login.username': 'Usuario', + 'login.oidc.registrationDisabled': + 'El registro está desactivado. Contacta con tu administrador.', + 'login.oidc.noEmail': 'No se recibió ningún correo del proveedor.', + 'login.mfaTitle': 'Autenticación de dos factores', + 'login.mfaSubtitle': + 'Introduce el código de 6 dígitos de tu app de autenticación.', + 'login.mfaCodeLabel': 'Código de verificación', + 'login.mfaCodeRequired': 'Introduce el código de tu app de autenticación.', + 'login.mfaHint': 'Abre Google Authenticator, Authy u otra app TOTP.', + 'login.mfaBack': '← Volver al inicio de sesión', + 'login.mfaVerify': 'Verificar', + 'login.invalidInviteLink': 'Enlace de invitación inválido o expirado', + 'login.oidcFailed': 'Error de inicio de sesión OIDC', + 'login.usernameRequired': 'El nombre de usuario es obligatorio', + 'login.passwordMinLength': 'La contraseña debe tener al menos 8 caracteres', + 'login.forgotPassword': '¿Olvidaste tu contraseña?', + 'login.forgotPasswordTitle': 'Restablecer tu contraseña', + 'login.forgotPasswordBody': + 'Introduce la dirección de correo con la que te registraste. Si existe una cuenta, enviaremos un enlace.', + 'login.forgotPasswordSubmit': 'Enviar enlace', + 'login.forgotPasswordSentTitle': 'Revisa tu correo', + 'login.forgotPasswordSentBody': + 'Si existe una cuenta con ese correo, el enlace de restablecimiento está en camino. Caduca en 60 minutos.', + 'login.forgotPasswordSmtpHintOff': + 'Nota: tu administrador no ha configurado SMTP, así que el enlace de restablecimiento se escribirá en la consola del servidor en lugar de enviarse por correo.', + 'login.backToLogin': 'Volver al inicio de sesión', + 'login.newPassword': 'Nueva contraseña', + 'login.confirmPassword': 'Confirmar nueva contraseña', + 'login.passwordsDontMatch': 'Las contraseñas no coinciden', + 'login.mfaCode': 'Código 2FA', + 'login.resetPasswordTitle': 'Establecer una nueva contraseña', + 'login.resetPasswordBody': + 'Elige una contraseña segura que no hayas usado aquí antes. Mínimo 8 caracteres.', + 'login.resetPasswordMfaBody': + 'Introduce tu código 2FA o un código de respaldo para completar el restablecimiento.', + 'login.resetPasswordSubmit': 'Restablecer contraseña', + 'login.resetPasswordVerify': 'Verificar y restablecer', + 'login.resetPasswordSuccessTitle': 'Contraseña actualizada', + 'login.resetPasswordSuccessBody': + 'Ya puedes iniciar sesión con tu nueva contraseña.', + 'login.resetPasswordInvalidLink': 'Enlace de restablecimiento no válido', + 'login.resetPasswordInvalidLinkBody': + 'Este enlace falta o está roto. Solicita uno nuevo para continuar.', + 'login.resetPasswordFailed': + 'Restablecimiento fallido. El enlace puede haber caducado.', + 'login.oidc.tokenFailed': 'La autenticación falló.', + 'login.oidc.invalidState': 'Sesión no válida. Inténtalo de nuevo.', + 'login.demoFailed': 'Falló el acceso a la demo', + 'login.oidcSignIn': 'Entrar con {name}', + 'login.demoHint': 'Prueba la demo: no necesitas registrarte', + 'login.oidcOnly': + 'La autenticación por contraseña está desactivada. Por favor, inicia sesión con tu proveedor SSO.', + 'login.oidcLoggedOut': + 'Has cerrado sesión. Vuelve a iniciar sesión con tu proveedor SSO.', +}; +export default login; diff --git a/shared/src/i18n/es/map.ts b/shared/src/i18n/es/map.ts new file mode 100644 index 00000000..f34a146e --- /dev/null +++ b/shared/src/i18n/es/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Conexiones', + 'map.showConnections': 'Mostrar rutas de reservas', + 'map.hideConnections': 'Ocultar rutas de reservas', +}; +export default map; diff --git a/shared/src/i18n/es/members.ts b/shared/src/i18n/es/members.ts new file mode 100644 index 00000000..fe21ea22 --- /dev/null +++ b/shared/src/i18n/es/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Compartir viaje', + 'members.inviteUser': 'Invitar usuario', + 'members.selectUser': 'Seleccionar usuario…', + 'members.invite': 'Invitar', + 'members.allHaveAccess': 'Todos los usuarios ya tienen acceso.', + 'members.access': 'Acceso', + 'members.person': 'persona', + 'members.persons': 'personas', + 'members.you': 'tú', + 'members.owner': 'Propietario', + 'members.leaveTrip': 'Abandonar viaje', + 'members.removeAccess': 'Quitar acceso', + 'members.confirmLeave': '¿Abandonar el viaje? Perderás el acceso.', + 'members.confirmRemove': '¿Quitar el acceso de este usuario?', + 'members.loadError': 'No se pudieron cargar los miembros', + 'members.added': 'añadido', + 'members.addError': 'No se pudo añadir', + 'members.removed': 'Miembro eliminado', + 'members.removeError': 'No se pudo eliminar', +}; +export default members; diff --git a/shared/src/i18n/es/memories.ts b/shared/src/i18n/es/memories.ts new file mode 100644 index 00000000..683383e9 --- /dev/null +++ b/shared/src/i18n/es/memories.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Fotos', + 'memories.notConnected': 'Immich no conectado', + 'memories.notConnectedHint': + 'Conecta tu instancia de Immich en Ajustes para ver tus fotos de viaje aquí.', + 'memories.notConnectedMultipleHint': + 'Conecta alguno de estos proveedores de fotos: {provider_names} en Configuración para poder añadir fotos a este viaje.', + 'memories.noDates': 'Añade fechas a tu viaje para cargar fotos.', + 'memories.noPhotos': 'No se encontraron fotos', + 'memories.noPhotosHint': + 'No se encontraron fotos en Immich para el rango de fechas de este viaje.', + 'memories.photosFound': 'fotos', + 'memories.fromOthers': 'de otros', + 'memories.sharePhotos': 'Compartir fotos', + 'memories.sharing': 'Compartiendo', + 'memories.reviewTitle': 'Revisar tus fotos', + 'memories.reviewHint': 'Haz clic en las fotos para excluirlas de compartir.', + 'memories.shareCount': 'Compartir {count} fotos', + 'memories.providerUrl': 'URL del servidor', + 'memories.providerApiKey': 'Clave API', + 'memories.providerUsername': 'Nombre de usuario', + 'memories.providerPassword': 'Contraseña', + 'memories.providerOTP': 'Código MFA (si está habilitado)', + 'memories.skipSSLVerification': 'Omitir verificación del certificado SSL', + 'memories.immichAutoUpload': + 'Duplicar las fotos del journey en Immich al subirlas', + 'memories.providerUrlHintSynology': + 'Incluye la ruta de la aplicación Photos en la URL, p.ej. https://nas:5001/photo', + 'memories.testConnection': 'Probar conexión', + 'memories.testShort': 'Probar', + 'memories.testFirst': 'Probar conexión primero', + 'memories.connected': 'Conectado', + 'memories.disconnected': 'No conectado', + 'memories.connectionSuccess': 'Conectado a Immich', + 'memories.connectionError': 'No se pudo conectar a Immich', + 'memories.saved': 'Configuración de {provider_name} guardada', + 'memories.providerDisconnectedBanner': + 'Se perdió la conexión con {provider_name}. Vuelve a conectar en Configuración para ver las fotos.', + 'memories.saveError': 'No se pudieron guardar los ajustes de {provider_name}', + 'memories.saveRouteNotConfigured': + 'La ruta de guardado no está configurada para este proveedor', + 'memories.testRouteNotConfigured': + 'La ruta de prueba no está configurada para este proveedor', + 'memories.fillRequiredFields': + 'Por favor complete todos los campos requeridos', + 'memories.oldest': 'Más antiguas', + 'memories.newest': 'Más recientes', + 'memories.allLocations': 'Todas las ubicaciones', + 'memories.addPhotos': 'Añadir fotos', + 'memories.linkAlbum': 'Vincular álbum', + 'memories.selectAlbum': 'Seleccionar álbum de Immich', + 'memories.selectAlbumMultiple': 'Seleccionar álbum', + 'memories.noAlbums': 'No se encontraron álbumes', + 'memories.syncAlbum': 'Sincronizar álbum', + 'memories.unlinkAlbum': 'Desvincular', + 'memories.photos': 'fotos', + 'memories.selectPhotos': 'Seleccionar fotos de Immich', + 'memories.selectPhotosMultiple': 'Seleccionar fotos', + 'memories.selectHint': 'Toca las fotos para seleccionarlas.', + 'memories.selected': 'seleccionado(s)', + 'memories.addSelected': 'Añadir {count} fotos', + 'memories.alreadyAdded': 'Añadido', + 'memories.private': 'Privado', + 'memories.stopSharing': 'Dejar de compartir', + 'memories.tripDates': 'Fechas del viaje', + 'memories.allPhotos': 'Todas las fotos', + 'memories.confirmShareTitle': '¿Compartir con los miembros del viaje?', + 'memories.confirmShareHint': + '{count} fotos serán visibles para todos los miembros de este viaje. Puedes hacer fotos individuales privadas más tarde.', + 'memories.confirmShareButton': 'Compartir fotos', + 'memories.error.loadAlbums': 'Error al cargar los álbumes', + 'memories.error.linkAlbum': 'Error al vincular el álbum', + 'memories.error.unlinkAlbum': 'Error al desvincular el álbum', + 'memories.error.syncAlbum': 'Error al sincronizar el álbum', + 'memories.error.loadPhotos': 'Error al cargar las fotos', + 'memories.error.addPhotos': 'Error al agregar las fotos', + 'memories.error.removePhoto': 'Error al eliminar la foto', + 'memories.error.toggleSharing': 'Error al actualizar el uso compartido', +}; +export default memories; diff --git a/shared/src/i18n/es/nav.ts b/shared/src/i18n/es/nav.ts new file mode 100644 index 00000000..a6585e59 --- /dev/null +++ b/shared/src/i18n/es/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Viaje', + 'nav.share': 'Compartir', + 'nav.settings': 'Ajustes', + 'nav.admin': 'Administración', + 'nav.logout': 'Cerrar sesión', + 'nav.lightMode': 'Modo claro', + 'nav.darkMode': 'Modo oscuro', + 'nav.autoMode': 'Modo automático', + 'nav.administrator': 'Administrador', + 'nav.myTrips': 'Mis viajes', + 'nav.profile': 'Perfil', + 'nav.bottomSettings': 'Ajustes', + 'nav.bottomAdmin': 'Administración', + 'nav.bottomLogout': 'Cerrar sesión', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/es/notif.ts b/shared/src/i18n/es/notif.ts new file mode 100644 index 00000000..b42a9a6b --- /dev/null +++ b/shared/src/i18n/es/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notificación', + 'notif.test.simple.text': 'Esta es una notificación de prueba simple.', + 'notif.test.boolean.text': '¿Aceptas esta notificación de prueba?', + 'notif.test.navigate.text': 'Haz clic abajo para ir al panel de control.', + 'notif.trip_invite.title': 'Invitación al viaje', + 'notif.trip_invite.text': '{actor} te invitó a {trip}', + 'notif.booking_change.title': 'Reserva actualizada', + 'notif.booking_change.text': '{actor} actualizó una reserva en {trip}', + 'notif.trip_reminder.title': 'Recordatorio de viaje', + 'notif.trip_reminder.text': '¡Tu viaje {trip} se acerca!', + 'notif.todo_due.title': 'Tarea pendiente', + 'notif.todo_due.text': '{todo} en {trip} vence el {due}', + 'notif.vacay_invite.title': 'Invitación Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} te invitó a fusionar planes de vacaciones', + 'notif.photos_shared.title': 'Fotos compartidas', + 'notif.photos_shared.text': '{actor} compartió {count} foto(s) en {trip}', + 'notif.collab_message.title': 'Nuevo mensaje', + 'notif.collab_message.text': '{actor} envió un mensaje en {trip}', + 'notif.packing_tagged.title': 'Asignación de equipaje', + 'notif.packing_tagged.text': '{actor} te asignó a {category} en {trip}', + 'notif.version_available.title': 'Nueva versión disponible', + 'notif.version_available.text': 'TREK {version} ya está disponible', + 'notif.action.view_trip': 'Ver viaje', + 'notif.action.view_collab': 'Ver mensajes', + 'notif.action.view_packing': 'Ver equipaje', + 'notif.action.view_photos': 'Ver fotos', + 'notif.action.view_vacay': 'Ver Vacay', + 'notif.action.view_admin': 'Ir al admin', + 'notif.action.view': 'Ver', + 'notif.action.accept': 'Aceptar', + 'notif.action.decline': 'Rechazar', + 'notif.generic.title': 'Notificación', + 'notif.generic.text': 'Tienes una nueva notificación', + 'notif.dev.unknown_event.title': '[DEV] Evento desconocido', + 'notif.dev.unknown_event.text': + 'El tipo de evento "{event}" no está registrado en EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/es/notifications.ts b/shared/src/i18n/es/notifications.ts new file mode 100644 index 00000000..9ed8d954 --- /dev/null +++ b/shared/src/i18n/es/notifications.ts @@ -0,0 +1,38 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notificaciones', + 'notifications.markAllRead': 'Marcar todo como leído', + 'notifications.deleteAll': 'Eliminar todo', + 'notifications.showAll': 'Ver todas las notificaciones', + 'notifications.empty': 'Sin notificaciones', + 'notifications.emptyDescription': '¡Estás al día!', + 'notifications.all': 'Todas', + 'notifications.unreadOnly': 'No leídas', + 'notifications.markRead': 'Marcar como leída', + 'notifications.markUnread': 'Marcar como no leída', + 'notifications.delete': 'Eliminar', + 'notifications.system': 'Sistema', + 'notifications.synologySessionCleared.title': 'Synology Photos desconectado', + 'notifications.synologySessionCleared.text': + 'Tu servidor o cuenta ha cambiado — ve a Configuración para probar la conexión de nuevo.', + 'notifications.test.title': 'Notificación de prueba de {actor}', + 'notifications.test.text': 'Esta es una notificación de prueba simple.', + 'notifications.test.booleanTitle': '{actor} solicita tu aprobación', + 'notifications.test.booleanText': 'Notificación de prueba booleana.', + 'notifications.test.accept': 'Aprobar', + 'notifications.test.decline': 'Rechazar', + 'notifications.test.navigateTitle': 'Mira esto', + 'notifications.test.navigateText': 'Notificación de prueba de navegación.', + 'notifications.test.goThere': 'Ir allí', + 'notifications.test.adminTitle': 'Difusión de administrador', + 'notifications.test.adminText': + '{actor} envió una notificación de prueba a todos los administradores.', + 'notifications.test.tripTitle': '{actor} publicó en tu viaje', + 'notifications.test.tripText': + 'Notificación de prueba para el viaje "{trip}".', + 'notifications.versionAvailable.title': 'Actualización disponible', + 'notifications.versionAvailable.text': 'TREK {version} ya está disponible.', + 'notifications.versionAvailable.button': 'Ver detalles', +}; +export default notifications; diff --git a/shared/src/i18n/es/oauth.ts b/shared/src/i18n/es/oauth.ts new file mode 100644 index 00000000..3ae7d027 --- /dev/null +++ b/shared/src/i18n/es/oauth.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Viajes', + 'oauth.scope.group.places': 'Lugares', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Equipaje', + 'oauth.scope.group.todos': 'Tareas', + 'oauth.scope.group.budget': 'Presupuesto', + 'oauth.scope.group.reservations': 'Reservas', + 'oauth.scope.group.collab': 'Colaboración', + 'oauth.scope.group.notifications': 'Notificaciones', + 'oauth.scope.group.vacay': 'Vacaciones', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Clima', + 'oauth.scope.group.journey': 'Travesía', + 'oauth.scope.trips:read.label': 'Ver viajes e itinerarios', + 'oauth.scope.trips:read.description': 'Leer viajes, días, notas y miembros', + 'oauth.scope.trips:write.label': 'Editar viajes e itinerarios', + 'oauth.scope.trips:write.description': + 'Crear y actualizar viajes, días, notas y gestionar miembros', + 'oauth.scope.trips:delete.label': 'Eliminar viajes', + 'oauth.scope.trips:delete.description': + 'Eliminar viajes permanentemente — esta acción es irreversible', + 'oauth.scope.trips:share.label': 'Gestionar enlaces de compartir', + 'oauth.scope.trips:share.description': + 'Crear, actualizar y revocar enlaces públicos de viaje', + 'oauth.scope.places:read.label': 'Ver lugares y datos del mapa', + 'oauth.scope.places:read.description': + 'Leer lugares, asignaciones de días, etiquetas y categorías', + 'oauth.scope.places:write.label': 'Gestionar lugares', + 'oauth.scope.places:write.description': + 'Crear, actualizar y eliminar lugares, asignaciones y etiquetas', + 'oauth.scope.atlas:read.label': 'Ver Atlas', + 'oauth.scope.atlas:read.description': + 'Leer países visitados, regiones y lista de deseos', + 'oauth.scope.atlas:write.label': 'Gestionar Atlas', + 'oauth.scope.atlas:write.description': + 'Marcar países y regiones como visitados, gestionar lista de deseos', + 'oauth.scope.packing:read.label': 'Ver listas de equipaje', + 'oauth.scope.packing:read.description': + 'Leer artículos, maletas y responsables de categoría', + 'oauth.scope.packing:write.label': 'Gestionar listas de equipaje', + 'oauth.scope.packing:write.description': + 'Agregar, actualizar, eliminar, marcar y reordenar artículos y maletas', + 'oauth.scope.todos:read.label': 'Ver listas de tareas', + 'oauth.scope.todos:read.description': + 'Leer tareas del viaje y responsables de categoría', + 'oauth.scope.todos:write.label': 'Gestionar listas de tareas', + 'oauth.scope.todos:write.description': + 'Crear, actualizar, marcar, eliminar y reordenar tareas', + 'oauth.scope.budget:read.label': 'Ver presupuesto', + 'oauth.scope.budget:read.description': + 'Leer partidas de presupuesto y desglose de gastos', + 'oauth.scope.budget:write.label': 'Gestionar presupuesto', + 'oauth.scope.budget:write.description': + 'Crear, actualizar y eliminar partidas de presupuesto', + 'oauth.scope.reservations:read.label': 'Ver reservas', + 'oauth.scope.reservations:read.description': + 'Leer reservas y detalles de alojamiento', + 'oauth.scope.reservations:write.label': 'Gestionar reservas', + 'oauth.scope.reservations:write.description': + 'Crear, actualizar, eliminar y reordenar reservas', + 'oauth.scope.collab:read.label': 'Ver colaboración', + 'oauth.scope.collab:read.description': + 'Leer notas colaborativas, encuestas y mensajes', + 'oauth.scope.collab:write.label': 'Gestionar colaboración', + 'oauth.scope.collab:write.description': + 'Crear, actualizar y eliminar notas, encuestas y mensajes', + 'oauth.scope.notifications:read.label': 'Ver notificaciones', + 'oauth.scope.notifications:read.description': + 'Leer notificaciones y conteos no leídos', + 'oauth.scope.notifications:write.label': 'Gestionar notificaciones', + 'oauth.scope.notifications:write.description': + 'Marcar notificaciones como leídas y responderlas', + 'oauth.scope.vacay:read.label': 'Ver planes de vacaciones', + 'oauth.scope.vacay:read.description': + 'Leer datos de planificación, entradas y estadísticas de vacaciones', + 'oauth.scope.vacay:write.label': 'Gestionar planes de vacaciones', + 'oauth.scope.vacay:write.description': + 'Crear y gestionar entradas de vacaciones, festivos y planes de equipo', + 'oauth.scope.geo:read.label': 'Mapas y geocodificación', + 'oauth.scope.geo:read.description': + 'Buscar lugares, resolver URLs de mapa y geocodificar coordenadas', + 'oauth.scope.weather:read.label': 'Previsiones meteorológicas', + 'oauth.scope.weather:read.description': + 'Obtener previsiones meteorológicas para lugares y fechas del viaje', + 'oauth.scope.journey:read.label': 'Ver travesías', + 'oauth.scope.journey:read.description': + 'Leer travesías, entradas y lista de colaboradores', + 'oauth.scope.journey:write.label': 'Gestionar travesías', + 'oauth.scope.journey:write.description': + 'Crear, actualizar y eliminar travesías y sus entradas', + 'oauth.scope.journey:share.label': 'Gestionar enlaces de travesías', + 'oauth.scope.journey:share.description': + 'Crear, actualizar y revocar enlaces públicos de compartir para travesías', +}; +export default oauth; diff --git a/shared/src/i18n/es/packing.ts b/shared/src/i18n/es/packing.ts new file mode 100644 index 00000000..092d9a23 --- /dev/null +++ b/shared/src/i18n/es/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Lista de equipaje', + 'packing.empty': 'La lista de equipaje está vacía', + 'packing.import': 'Importar', + 'packing.importTitle': 'Importar lista de equipaje', + 'packing.importHint': + 'Un elemento por línea. Categoría y cantidad opcionales separadas por coma, punto y coma o tabulación: Nombre, Categoría, Cantidad', + 'packing.importPlaceholder': + 'Cepillo de dientes\nProtector solar, Higiene\nCamisetas, Ropa, 5\nPasaporte, Documentos', + 'packing.importCsv': 'Cargar CSV/TXT', + 'packing.importAction': 'Importar {count}', + 'packing.importSuccess': '{count} elementos importados', + 'packing.importError': 'Error al importar', + 'packing.importEmpty': 'Sin elementos para importar', + 'packing.progress': '{packed} de {total} preparados ({percent}%)', + 'packing.clearChecked': 'Eliminar {count} marcados', + 'packing.clearCheckedShort': 'Eliminar {count}', + 'packing.suggestions': 'Sugerencias', + 'packing.suggestionsTitle': 'Añadir sugerencias', + 'packing.allSuggested': 'Todas las sugerencias añadidas', + 'packing.allPacked': '¡Todo preparado!', + 'packing.addPlaceholder': 'Añadir nuevo elemento...', + 'packing.categoryPlaceholder': 'Categoría...', + 'packing.filterAll': 'Todo', + 'packing.filterOpen': 'Pendientes', + 'packing.filterDone': 'Hecho', + 'packing.emptyTitle': 'La lista de equipaje está vacía', + 'packing.emptyHint': 'Añade elementos o usa las sugerencias', + 'packing.emptyFiltered': 'Ningún elemento coincide con este filtro', + 'packing.menuRename': 'Renombrar', + 'packing.menuCheckAll': 'Marcar todo', + 'packing.menuUncheckAll': 'Desmarcar todo', + 'packing.menuDeleteCat': 'Eliminar categoría', + 'packing.addItem': 'Añadir artículo', + 'packing.addItemPlaceholder': 'Nombre del artículo...', + 'packing.addCategory': 'Añadir categoría', + 'packing.newCategoryPlaceholder': 'Nombre de categoría (ej. Ropa)', + 'packing.applyTemplate': 'Aplicar plantilla', + 'packing.template': 'Plantilla', + 'packing.templateApplied': '{count} artículos añadidos desde plantilla', + 'packing.templateError': 'Error al aplicar plantilla', + 'packing.saveAsTemplate': 'Guardar como plantilla', + 'packing.templateName': 'Nombre de la plantilla', + 'packing.templateSaved': 'Lista de equipaje guardada como plantilla', + 'packing.noMembers': 'Sin miembros', + 'packing.bags': 'Equipaje', + 'packing.noBag': 'Sin asignar', + 'packing.totalWeight': 'Peso total', + 'packing.bagName': 'Nombre...', + 'packing.addBag': 'Añadir equipaje', + 'packing.changeCategory': 'Cambiar categoría', + 'packing.confirm.clearChecked': + '¿Seguro que quieres eliminar {count} elementos marcados?', + 'packing.confirm.deleteCat': + '¿Seguro que quieres eliminar la categoría "{name}" con {count} elementos?', + 'packing.defaultCategory': 'Otros', + 'packing.toast.saveError': 'No se pudo guardar', + 'packing.toast.deleteError': 'No se pudo eliminar', + 'packing.toast.renameError': 'No se pudo renombrar', + 'packing.toast.addError': 'No se pudo añadir', + 'packing.suggestions.items': [ + { + name: 'Pasaporte', + category: 'Documentos', + }, + { + name: 'Documento de identidad', + category: 'Documentos', + }, + { + name: 'Seguro de viaje', + category: 'Documentos', + }, + { + name: 'Billetes de vuelo', + category: 'Documentos', + }, + { + name: 'Tarjeta de crédito', + category: 'Finanzas', + }, + { + name: 'Efectivo', + category: 'Finanzas', + }, + { + name: 'Visado', + category: 'Documentos', + }, + { + name: 'Camisetas', + category: 'Ropa', + }, + { + name: 'Pantalones', + category: 'Ropa', + }, + { + name: 'Ropa interior', + category: 'Ropa', + }, + { + name: 'Calcetines', + category: 'Ropa', + }, + { + name: 'Chaqueta', + category: 'Ropa', + }, + { + name: 'Pijama', + category: 'Ropa', + }, + { + name: 'Ropa de baño', + category: 'Ropa', + }, + { + name: 'Impermeable', + category: 'Ropa', + }, + { + name: 'Zapatos cómodos', + category: 'Ropa', + }, + { + name: 'Cepillo de dientes', + category: 'Aseo', + }, + { + name: 'Pasta de dientes', + category: 'Aseo', + }, + { + name: 'Champú', + category: 'Aseo', + }, + { + name: 'Desodorante', + category: 'Aseo', + }, + { + name: 'Protector solar', + category: 'Aseo', + }, + { + name: 'Maquinilla de afeitar', + category: 'Aseo', + }, + { + name: 'Cargador', + category: 'Electrónica', + }, + { + name: 'Batería externa', + category: 'Electrónica', + }, + { + name: 'Auriculares', + category: 'Electrónica', + }, + { + name: 'Adaptador de viaje', + category: 'Electrónica', + }, + { + name: 'Cámara', + category: 'Electrónica', + }, + { + name: 'Analgésicos', + category: 'Salud', + }, + { + name: 'Tiritas', + category: 'Salud', + }, + { + name: 'Desinfectante', + category: 'Salud', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/es/pdf.ts b/shared/src/i18n/es/pdf.ts new file mode 100644 index 00000000..10f5dd25 --- /dev/null +++ b/shared/src/i18n/es/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Plan de viaje', + 'pdf.planned': 'Planificado', + 'pdf.costLabel': 'Coste EUR', + 'pdf.preview': 'Vista previa PDF', + 'pdf.saveAsPdf': 'Guardar como PDF', +}; +export default pdf; diff --git a/shared/src/i18n/es/perm.ts b/shared/src/i18n/es/perm.ts new file mode 100644 index 00000000..ac280e84 --- /dev/null +++ b/shared/src/i18n/es/perm.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Configuración de permisos', + 'perm.subtitle': 'Controla quién puede realizar acciones en la aplicación', + 'perm.saved': 'Configuración de permisos guardada', + 'perm.resetDefaults': 'Restablecer valores predeterminados', + 'perm.customized': 'personalizado', + 'perm.level.admin': 'Solo administrador', + 'perm.level.tripOwner': 'Propietario del viaje', + 'perm.level.tripMember': 'Miembros del viaje', + 'perm.level.everybody': 'Todos', + 'perm.cat.trip': 'Gestión de viajes', + 'perm.cat.members': 'Gestión de miembros', + 'perm.cat.files': 'Archivos', + 'perm.cat.content': 'Contenido y horario', + 'perm.cat.extras': 'Presupuesto, equipaje y colaboración', + 'perm.action.trip_create': 'Crear viajes', + 'perm.action.trip_edit': 'Editar detalles del viaje', + 'perm.action.trip_delete': 'Eliminar viajes', + 'perm.action.trip_archive': 'Archivar / desarchivar viajes', + 'perm.action.trip_cover_upload': 'Subir imagen de portada', + 'perm.action.member_manage': 'Añadir / eliminar miembros', + 'perm.action.file_upload': 'Subir archivos', + 'perm.action.file_edit': 'Editar metadatos del archivo', + 'perm.action.file_delete': 'Eliminar archivos', + 'perm.action.place_edit': 'Añadir / editar / eliminar lugares', + 'perm.action.day_edit': 'Editar días, notas y asignaciones', + 'perm.action.reservation_edit': 'Gestionar reservas', + 'perm.action.budget_edit': 'Gestionar presupuesto', + 'perm.action.packing_edit': 'Gestionar listas de equipaje', + 'perm.action.collab_edit': 'Colaboración (notas, encuestas, chat)', + 'perm.action.share_manage': 'Gestionar enlaces compartidos', + 'perm.actionHint.trip_create': 'Quién puede crear nuevos viajes', + 'perm.actionHint.trip_edit': + 'Quién puede cambiar el nombre, fechas, descripción y moneda del viaje', + 'perm.actionHint.trip_delete': + 'Quién puede eliminar permanentemente un viaje', + 'perm.actionHint.trip_archive': 'Quién puede archivar o desarchivar un viaje', + 'perm.actionHint.trip_cover_upload': + 'Quién puede subir o cambiar la imagen de portada', + 'perm.actionHint.member_manage': + 'Quién puede invitar o eliminar miembros del viaje', + 'perm.actionHint.file_upload': 'Quién puede subir archivos a un viaje', + 'perm.actionHint.file_edit': + 'Quién puede editar descripciones y enlaces de archivos', + 'perm.actionHint.file_delete': + 'Quién puede mover archivos a la papelera o eliminarlos permanentemente', + 'perm.actionHint.place_edit': 'Quién puede añadir, editar o eliminar lugares', + 'perm.actionHint.day_edit': + 'Quién puede editar días, notas de días y asignaciones de lugares', + 'perm.actionHint.reservation_edit': + 'Quién puede crear, editar o eliminar reservas', + 'perm.actionHint.budget_edit': + 'Quién puede crear, editar o eliminar partidas del presupuesto', + 'perm.actionHint.packing_edit': + 'Quién puede gestionar artículos de equipaje y bolsas', + 'perm.actionHint.collab_edit': + 'Quién puede crear notas, encuestas y enviar mensajes', + 'perm.actionHint.share_manage': + 'Quién puede crear o eliminar enlaces compartidos públicos', +}; +export default perm; diff --git a/shared/src/i18n/es/photos.ts b/shared/src/i18n/es/photos.ts new file mode 100644 index 00000000..e8e19007 --- /dev/null +++ b/shared/src/i18n/es/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Fotos', + 'photos.subtitle': '{count} fotos para {trip}', + 'photos.dropHere': 'Suelta fotos aquí...', + 'photos.dropHereActive': 'Suelta fotos aquí', + 'photos.captionForAll': 'Leyenda (para todos)', + 'photos.captionPlaceholder': 'Leyenda opcional...', + 'photos.addCaption': 'Añadir leyenda...', + 'photos.allDays': 'Todos los días', + 'photos.noPhotos': 'Aún no hay fotos', + 'photos.uploadHint': 'Sube y organiza las fotos compartidas de este viaje', + 'photos.clickToSelect': 'o haz clic para seleccionar', + 'photos.linkPlace': 'Vincular lugar', + 'photos.noPlace': 'Sin lugar', + 'photos.uploadN': 'Subida de {n} foto(s)', + 'photos.linkDay': 'Vincular día', + 'photos.noDay': 'Ningún día', + 'photos.dayLabel': 'Día {number}', + 'photos.photoSelected': 'Foto seleccionada', + 'photos.photosSelected': 'Fotos seleccionadas', + 'photos.fileTypeHint': 'JPG, PNG, WebP · máx. 10 MB · hasta 30 fotos', +}; +export default photos; diff --git a/shared/src/i18n/es/places.ts b/shared/src/i18n/es/places.ts new file mode 100644 index 00000000..30477512 --- /dev/null +++ b/shared/src/i18n/es/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Añadir lugar/actividad', + 'places.importFile': 'Importar archivo', + 'places.sidebarDrop': 'Soltar para importar', + 'places.importFileHint': + 'Importa archivos .gpx, .kml o .kmz de herramientas como Google My Maps, Google Earth o un rastreador GPS.', + 'places.importFileDropHere': + 'Haz clic para seleccionar un archivo o arrástralo aquí', + 'places.importFileDropActive': 'Suelta el archivo para seleccionarlo', + 'places.importFileUnsupported': + 'Tipo de archivo no compatible. Usa .gpx, .kml o .kmz.', + 'places.importFileTooLarge': + 'El archivo es demasiado grande. El tamaño máximo de carga es {maxMb} MB.', + 'places.importFileError': 'Importación fallida', + 'places.importAllSkipped': 'Todos los lugares ya estaban en el viaje.', + 'places.gpxImported': '{count} lugares importados desde GPX', + 'places.gpxImportTypes': '¿Qué deseas importar?', + 'places.gpxImportWaypoints': 'Puntos de ruta', + 'places.gpxImportRoutes': 'Rutas', + 'places.gpxImportTracks': 'Tracks (con geometría de ruta)', + 'places.gpxImportNoneSelected': 'Selecciona al menos un tipo para importar.', + 'places.kmlImportTypes': '¿Qué deseas importar?', + 'places.kmlImportPoints': 'Puntos (Placemarks)', + 'places.kmlImportPaths': 'Rutas (LineStrings)', + 'places.kmlImportNoneSelected': 'Selecciona al menos un tipo.', + 'places.selectionCount': '{count} seleccionado(s)', + 'places.deleteSelected': 'Eliminar selección', + 'places.kmlKmzImported': '{count} lugares importados desde KMZ/KML', + 'places.urlResolved': 'Lugar importado desde URL', + 'places.importList': 'Importar lista', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importados: {created} • Omitidos: {skipped}', + 'places.importGoogleList': 'Lista Google', + 'places.importNaverList': 'Lista Naver', + 'places.googleListHint': + 'Pega un enlace compartido de una lista de Google Maps para importar todos los lugares.', + 'places.googleListImported': '{count} lugares importados de "{list}"', + 'places.googleListError': 'Error al importar la lista de Google Maps', + 'places.naverListHint': + 'Pega un enlace compartido de una lista de Naver Maps para importar todos los lugares.', + 'places.naverListImported': '{count} lugares importados de "{list}"', + 'places.naverListError': 'Error al importar la lista de Naver Maps', + 'places.viewDetails': 'Ver detalles', + 'places.assignToDay': '¿A qué día añadirlo?', + 'places.all': 'Todo', + 'places.unplanned': 'Sin planificar', + 'places.filterTracks': 'Rutas', + 'places.search': 'Buscar lugares...', + 'places.allCategories': 'Todas las categorías', + 'places.categoriesSelected': 'categorías', + 'places.clearFilter': 'Borrar filtro', + 'places.count': '{count} lugares', + 'places.countSingular': '1 lugar', + 'places.allPlanned': 'Todos los lugares están planificados', + 'places.noneFound': 'No se encontraron lugares', + 'places.editPlace': 'Editar lugar', + 'places.formName': 'Nombre', + 'places.formNamePlaceholder': 'p. ej. Torre Eiffel', + 'places.formDescription': 'Descripción', + 'places.formDescriptionPlaceholder': 'Descripción breve...', + 'places.formAddress': 'Dirección', + 'places.formAddressPlaceholder': 'Calle, ciudad, país', + 'places.formLat': 'Latitud (p. ej. 48.8566)', + 'places.formLng': 'Longitud (p. ej. 2.3522)', + 'places.formCategory': 'Categoría', + 'places.noCategory': 'Sin categoría', + 'places.categoryNamePlaceholder': 'Nombre de la categoría', + 'places.formTime': 'Hora', + 'places.startTime': 'Inicio', + 'places.endTime': 'Fin', + 'places.endTimeBeforeStart': 'La hora de fin es anterior a la de inicio', + 'places.timeCollision': 'Solapamiento horario con:', + 'places.formWebsite': 'Página web', + 'places.formNotes': 'Notas', + 'places.formNotesPlaceholder': 'Notas personales...', + 'places.formReservation': 'Reserva', + 'places.reservationNotesPlaceholder': + 'Notas de reserva, número de confirmación...', + 'places.mapsSearchPlaceholder': 'Buscar lugares...', + 'places.mapsSearchError': 'La búsqueda de lugares falló.', + 'places.loadingDetails': 'Cargando detalles del lugar…', + 'places.osmHint': + 'Usando búsqueda con OpenStreetMap (sin fotos, horarios ni valoraciones). Añade una clave API de Google en Ajustes para obtener todos los detalles.', + 'places.osmActive': + 'Búsqueda mediante OpenStreetMap (sin fotos, valoraciones ni horarios). Añade una clave API de Google en Ajustes para datos ampliados.', + 'places.categoryCreateError': 'No se pudo crear la categoría', + 'places.nameRequired': 'Introduce un nombre', + 'places.saveError': 'No se pudo guardar', +}; +export default places; diff --git a/shared/src/i18n/es/planner.ts b/shared/src/i18n/es/planner.ts new file mode 100644 index 00000000..d6d789a3 --- /dev/null +++ b/shared/src/i18n/es/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Lugares', + 'planner.bookings': 'Reservas', + 'planner.packingList': 'Lista de equipaje', + 'planner.documents': 'Documentos', + 'planner.dayPlan': 'Plan por días', + 'planner.reservations': 'Reservas', + 'planner.minTwoPlaces': 'Se necesitan al menos 2 lugares con coordenadas', + 'planner.noGeoPlaces': 'No hay lugares con coordenadas disponibles', + 'planner.routeCalculated': 'Ruta calculada', + 'planner.routeCalcFailed': 'No se pudo calcular la ruta', + 'planner.routeError': 'Error al calcular la ruta', + 'planner.icsExportFailed': 'Error al exportar ICS', + 'planner.routeOptimized': 'Ruta optimizada', + 'planner.reservationUpdated': 'Reserva actualizada', + 'planner.reservationAdded': 'Reserva añadida', + 'planner.confirmDeleteReservation': '¿Eliminar reserva?', + 'planner.reservationDeleted': 'Reserva eliminada', + 'planner.days': 'Días', + 'planner.allPlaces': 'Todos los lugares', + 'planner.totalPlaces': '{n} lugares en total', + 'planner.noDaysPlanned': 'Aún no hay días planificados', + 'planner.editTrip': 'Editar viaje →', + 'planner.placeOne': '1 lugar', + 'planner.placeN': '{n} lugares', + 'planner.addNote': 'Añadir nota', + 'planner.noEntries': 'No hay entradas para este día', + 'planner.addPlace': 'Añadir lugar/actividad', + 'planner.addPlaceShort': '+ Añadir lugar/actividad', + 'planner.resPending': 'Reserva pendiente · ', + 'planner.resConfirmed': 'Reserva confirmada · ', + 'planner.notePlaceholder': 'Nota…', + 'planner.noteTimePlaceholder': 'Hora (opcional)', + 'planner.noteExamplePlaceholder': + 'p. ej. S3 a las 14:30 desde la estación central, ferry desde el muelle 7, pausa para comer…', + 'planner.totalCost': 'Coste total', + 'planner.searchPlaces': 'Buscar lugares…', + 'planner.allCategories': 'Todas las categorías', + 'planner.noPlacesFound': 'No se encontraron lugares', + 'planner.addFirstPlace': 'Añadir el primer lugar', + 'planner.noReservations': 'Sin reservas', + 'planner.addFirstReservation': 'Añadir la primera reserva', + 'planner.new': 'Nuevo', + 'planner.addToDay': '+ Día', + 'planner.calculating': 'Calculando…', + 'planner.route': 'Ruta', + 'planner.optimize': 'Optimizar', + 'planner.openGoogleMaps': 'Abrir en Google Maps', + 'planner.selectDayHint': + 'Selecciona un día de la lista izquierda para ver su plan', + 'planner.noPlacesForDay': 'Aún no hay lugares para este día', + 'planner.addPlacesLink': 'Añadir lugares →', + 'planner.minTotal': 'min en total', + 'planner.noReservation': 'Sin reserva', + 'planner.removeFromDay': 'Quitar del día', + 'planner.addToThisDay': 'Añadir al día', + 'planner.overview': 'Vista general', + 'planner.noDays': 'No hay días todavía', + 'planner.editTripToAddDays': 'Edita el viaje para añadir días', + 'planner.dayCount': '{n} días', + 'planner.clickToUnlock': 'Haz clic para desbloquear', + 'planner.keepPosition': 'Mantener posición durante la optimización de ruta', + 'planner.dayDetails': 'Detalles del día', + 'planner.dayN': 'Día {n}', +}; +export default planner; diff --git a/shared/src/i18n/es/register.ts b/shared/src/i18n/es/register.ts new file mode 100644 index 00000000..8b18e1f2 --- /dev/null +++ b/shared/src/i18n/es/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Las contraseñas no coinciden', + 'register.passwordTooShort': 'La contraseña debe tener al menos 8 caracteres', + 'register.failed': 'Falló el registro', + 'register.getStarted': 'Empezar', + 'register.subtitle': 'Crea una cuenta y empieza a planificar tus viajes.', + 'register.feature1': 'Planes de viaje ilimitados', + 'register.feature2': 'Vista de mapa interactiva', + 'register.feature3': 'Gestiona lugares y categorías', + 'register.feature4': 'Haz seguimiento de las reservas', + 'register.feature5': 'Crea listas de equipaje', + 'register.feature6': 'Guarda fotos y archivos', + 'register.createAccount': 'Crear cuenta', + 'register.startPlanning': 'Empieza a planificar tu viaje', + 'register.minChars': 'Mín. 6 caracteres', + 'register.confirmPassword': 'Confirmar contraseña', + 'register.repeatPassword': 'Repetir contraseña', + 'register.registering': 'Registrando...', + 'register.register': 'Registrarse', + 'register.hasAccount': '¿Ya tienes cuenta?', + 'register.signIn': 'Iniciar sesión', +}; +export default register; diff --git a/shared/src/i18n/es/reservations.ts b/shared/src/i18n/es/reservations.ts new file mode 100644 index 00000000..ea5ef92b --- /dev/null +++ b/shared/src/i18n/es/reservations.ts @@ -0,0 +1,118 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Reservas', + 'reservations.empty': 'Aún no hay reservas', + 'reservations.emptyHint': 'Añade reservas de vuelos, hoteles y más', + 'reservations.add': 'Añadir reserva', + 'reservations.addManual': 'Reserva manual', + 'reservations.placeHint': + 'Consejo: es mejor crear las reservas directamente desde un lugar para vincularlas con el plan del día.', + 'reservations.confirmed': 'Confirmada', + 'reservations.pending': 'Pendiente', + 'reservations.summary': '{confirmed} confirmadas, {pending} pendientes', + 'reservations.fromPlan': 'Del plan', + 'reservations.showFiles': 'Mostrar archivos', + 'reservations.editTitle': 'Editar reserva', + 'reservations.status': 'Estado', + 'reservations.datetime': 'Fecha y hora', + 'reservations.startTime': 'Hora de inicio', + 'reservations.endTime': 'Hora de fin', + 'reservations.date': 'Fecha', + 'reservations.time': 'Hora', + 'reservations.timeAlt': 'Hora (alternativa, p. ej. 19:30)', + 'reservations.notes': 'Notas', + 'reservations.notesPlaceholder': 'Notas adicionales...', + 'reservations.type.flight': 'Vuelo', + 'reservations.type.hotel': 'Alojamiento', + 'reservations.type.restaurant': 'Restaurante', + 'reservations.type.train': 'Tren', + 'reservations.type.car': 'Coche', + 'reservations.type.cruise': 'Crucero', + 'reservations.type.event': 'Evento', + 'reservations.type.tour': 'Excursión', + 'reservations.type.other': 'Otro', + 'reservations.confirm.delete': + '¿Seguro que quieres eliminar la reserva "{name}"?', + 'reservations.confirm.deleteTitle': '¿Eliminar reserva?', + 'reservations.confirm.deleteBody': '« {name} » se eliminará permanentemente.', + 'reservations.toast.updated': 'Reserva actualizada', + 'reservations.toast.removed': 'Reserva eliminada', + 'reservations.toast.fileUploaded': 'Archivo subido', + 'reservations.toast.uploadError': 'No se pudo subir', + 'reservations.newTitle': 'Nueva reserva', + 'reservations.bookingType': 'Tipo de reserva', + 'reservations.titleLabel': 'Título', + 'reservations.titlePlaceholder': 'p. ej. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Ubicación / dirección', + 'reservations.locationPlaceholder': 'Dirección, aeropuerto, hotel...', + 'reservations.confirmationCode': 'Código de reserva', + 'reservations.confirmationPlaceholder': 'p. ej. ABC12345', + 'reservations.day': 'Día', + 'reservations.noDay': 'Sin día', + 'reservations.place': 'Lugar', + 'reservations.noPlace': 'Sin lugar', + 'reservations.pendingSave': 'se guardará…', + 'reservations.uploading': 'Subiendo...', + 'reservations.attachFile': 'Adjuntar archivo', + 'reservations.linkExisting': 'Vincular archivo existente', + 'reservations.toast.saveError': 'No se pudo guardar', + 'reservations.toast.updateError': 'No se pudo actualizar', + 'reservations.toast.deleteError': 'No se pudo eliminar', + 'reservations.confirm.remove': '¿Eliminar la reserva de "{name}"?', + 'reservations.linkAssignment': 'Vincular a una asignación del día', + 'reservations.pickAssignment': 'Selecciona una asignación de tu plan...', + 'reservations.noAssignment': 'Sin vínculo (independiente)', + 'reservations.price': 'Precio', + 'reservations.budgetCategory': 'Categoría de presupuesto', + 'reservations.budgetCategoryPlaceholder': 'ej. Transporte, Alojamiento', + 'reservations.budgetCategoryAuto': 'Automático (según tipo de reserva)', + 'reservations.budgetHint': + 'Se creará automáticamente una entrada presupuestaria al guardar.', + 'reservations.departureDate': 'Salida', + 'reservations.arrivalDate': 'Llegada', + 'reservations.departureTime': 'Hora salida', + 'reservations.arrivalTime': 'Hora llegada', + 'reservations.pickupDate': 'Recogida', + 'reservations.returnDate': 'Devolución', + 'reservations.pickupTime': 'Hora recogida', + 'reservations.returnTime': 'Hora devolución', + 'reservations.endDate': 'Fecha fin', + 'reservations.meta.departureTimezone': 'TZ salida', + 'reservations.meta.arrivalTimezone': 'TZ llegada', + 'reservations.span.departure': 'Salida', + 'reservations.span.arrival': 'Llegada', + 'reservations.span.inTransit': 'En tránsito', + 'reservations.span.pickup': 'Recogida', + 'reservations.span.return': 'Devolución', + 'reservations.span.active': 'Activo', + 'reservations.span.start': 'Inicio', + 'reservations.span.end': 'Fin', + 'reservations.span.ongoing': 'En curso', + 'reservations.validation.endBeforeStart': + 'La fecha/hora de fin debe ser posterior a la de inicio', + 'reservations.addBooking': 'Añadir reserva', + 'reservations.meta.airline': 'Aerolínea', + 'reservations.meta.flightNumber': 'N° de vuelo', + 'reservations.meta.from': 'Desde', + 'reservations.meta.to': 'Hasta', + 'reservations.needsReview': 'Revisar', + 'reservations.needsReviewHint': + 'No se pudo identificar el aeropuerto automáticamente — por favor confirma la ubicación.', + 'reservations.searchLocation': 'Buscar estación, puerto, dirección...', + 'reservations.meta.trainNumber': 'N° de tren', + 'reservations.meta.platform': 'Andén', + 'reservations.meta.seat': 'Asiento', + 'reservations.meta.checkIn': 'Registro de entrada', + 'reservations.meta.checkInUntil': 'Check-in hasta', + 'reservations.meta.checkOut': 'Registro de salida', + 'reservations.meta.linkAccommodation': 'Alojamiento', + 'reservations.meta.pickAccommodation': 'Vincular con alojamiento', + 'reservations.meta.noAccommodation': 'Ninguno', + 'reservations.meta.hotelPlace': 'Alojamiento', + 'reservations.meta.pickHotel': 'Seleccionar alojamiento', + 'reservations.meta.fromDay': 'Desde', + 'reservations.meta.toDay': 'Hasta', + 'reservations.meta.selectDay': 'Seleccionar día', +}; +export default reservations; diff --git a/shared/src/i18n/es/settings.ts b/shared/src/i18n/es/settings.ts new file mode 100644 index 00000000..23ead94d --- /dev/null +++ b/shared/src/i18n/es/settings.ts @@ -0,0 +1,299 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Ajustes', + 'settings.subtitle': 'Configura tus ajustes personales', + 'settings.tabs.display': 'Pantalla', + 'settings.tabs.map': 'Mapa', + 'settings.tabs.notifications': 'Notificaciones', + 'settings.tabs.integrations': 'Integraciones', + 'settings.tabs.account': 'Cuenta', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Acerca de', + 'settings.map': 'Mapa', + 'settings.mapTemplate': 'Plantilla del mapa', + 'settings.mapTemplatePlaceholder.select': 'Seleccionar plantilla...', + 'settings.mapDefaultHint': 'Déjalo vacío para OpenStreetMap (por defecto)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'Plantilla de URL para los mosaicos del mapa', + 'settings.mapProvider': 'Proveedor de mapa', + 'settings.mapProviderHint': + 'Afecta a los mapas de Trip Planner y Journey. Atlas siempre usa Leaflet.', + 'settings.mapLeafletSubtitle': 'Clásico 2D, cualquier mosaico raster', + 'settings.mapMapboxSubtitle': 'Mosaicos vectoriales, edificios 3D y terreno', + 'settings.mapExperimental': 'Experimental', + 'settings.mapMapboxToken': 'Token de acceso de Mapbox', + 'settings.mapMapboxTokenHint': 'Token público (pk.*) de', + 'settings.mapMapboxTokenLink': 'mapbox.com → Tokens de acceso', + 'settings.mapStyle': 'Estilo de mapa', + 'settings.mapStylePlaceholder': 'Seleccionar un estilo de Mapbox', + 'settings.mapStyleHint': 'Preset o tu propia URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': 'Edificios 3D y terreno', + 'settings.map3dHint': + 'Inclinación + extrusiones 3D reales de edificios — funciona con todos los estilos, incluyendo satélite.', + 'settings.mapHighQuality': 'Modo de alta calidad', + 'settings.mapHighQualityHint': + 'Antialiasing + proyección global para bordes más nítidos y una vista realista del mundo.', + 'settings.mapHighQualityWarning': + 'Puede afectar el rendimiento en dispositivos menos potentes.', + 'settings.mapTipLabel': 'Consejo:', + 'settings.mapTip': + 'Clic derecho y arrastrar para rotar/inclinar el mapa. Clic central para añadir un lugar (el clic derecho está reservado para la rotación).', + 'settings.latitude': 'Latitud', + 'settings.longitude': 'Longitud', + 'settings.saveMap': 'Guardar mapa', + 'settings.apiKeys': 'Claves API', + 'settings.mapsKey': 'Clave API de Google Maps', + 'settings.mapsKeyHint': + 'Necesaria para buscar lugares. Consíguela en console.cloud.google.com', + 'settings.weatherKey': 'Clave API de OpenWeatherMap', + 'settings.weatherKeyHint': + 'Para datos meteorológicos. Gratis en openweathermap.org/api', + 'settings.keyPlaceholder': 'Introduce la clave...', + 'settings.configured': 'Configurado', + 'settings.saveKeys': 'Guardar claves', + 'settings.display': 'Visualización', + 'settings.colorMode': 'Modo de color', + 'settings.light': 'Claro', + 'settings.dark': 'Oscuro', + 'settings.auto': 'Automático', + 'settings.language': 'Idioma', + 'settings.temperature': 'Unidad de temperatura', + 'settings.timeFormat': 'Formato de hora', + 'settings.blurBookingCodes': 'Difuminar códigos de reserva', + 'settings.notifications': 'Notificaciones', + 'settings.notifyTripInvite': 'Invitaciones de viaje', + 'settings.notifyBookingChange': 'Cambios en reservas', + 'settings.notifyTripReminder': 'Recordatorios de viaje', + 'settings.notifyTodoDue': 'Tarea próxima', + 'settings.notifyVacayInvite': 'Invitaciones de fusión Vacay', + 'settings.notifyPhotosShared': 'Fotos compartidas (Immich)', + 'settings.notifyCollabMessage': 'Mensajes de chat (Collab)', + 'settings.notifyPackingTagged': 'Lista de equipaje: asignaciones', + 'settings.notifyWebhook': 'Notificaciones webhook', + 'settings.notificationsDisabled': + 'Las notificaciones no están configuradas. Pida a un administrador que active las notificaciones por correo o webhook.', + 'settings.notificationsActive': 'Canal activo', + 'settings.notificationsManagedByAdmin': + 'Los eventos de notificación son configurados por el administrador.', + 'settings.on': 'Activado', + 'settings.off': 'Desactivado', + 'settings.mcp.title': 'Configuración MCP', + 'settings.mcp.endpoint': 'Endpoint MCP', + 'settings.mcp.clientConfig': 'Configuración del cliente', + 'settings.mcp.clientConfigHint': + 'Reemplaza con un token de la lista de abajo. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Reemplaza y con las credenciales del cliente OAuth 2.1 que creaste arriba. mcp-remote abrirá el navegador para completar la autorización la primera vez que te conectes. Es posible que debas ajustar la ruta de npx según tu sistema (p. ej. C:\\PROGRA~1\\nodejs\\npx.cmd en Windows).', + 'settings.mcp.copy': 'Copiar', + 'settings.mcp.copied': '¡Copiado!', + 'settings.mcp.apiTokens': 'Tokens de API', + 'settings.mcp.createToken': 'Crear nuevo token', + 'settings.mcp.noTokens': + 'Sin tokens aún. Crea uno para conectar clientes MCP.', + 'settings.mcp.tokenCreatedAt': 'Creado', + 'settings.mcp.tokenUsedAt': 'Usado', + 'settings.mcp.deleteTokenTitle': 'Eliminar token', + 'settings.mcp.deleteTokenMessage': + 'Este token dejará de funcionar de inmediato. Cualquier cliente MCP que lo use perderá el acceso.', + 'settings.mcp.modal.createTitle': 'Crear token de API', + 'settings.mcp.modal.tokenName': 'Nombre del token', + 'settings.mcp.modal.tokenNamePlaceholder': + 'p. ej. Claude Desktop, Portátil de trabajo', + 'settings.mcp.modal.creating': 'Creando…', + 'settings.mcp.modal.create': 'Crear token', + 'settings.mcp.modal.createdTitle': 'Token creado', + 'settings.mcp.modal.createdWarning': + 'Este token solo se mostrará una vez. Cópialo y guárdalo ahora — no se podrá recuperar.', + 'settings.mcp.modal.done': 'Listo', + 'settings.mcp.toast.created': 'Token creado', + 'settings.mcp.toast.createError': 'Error al crear el token', + 'settings.mcp.toast.deleted': 'Token eliminado', + 'settings.mcp.toast.deleteError': 'Error al eliminar el token', + 'settings.mcp.apiTokensDeprecated': + 'Los tokens de API están obsoletos y se eliminarán en una versión futura. Utilice los clientes OAuth 2.1 en su lugar.', + 'settings.oauth.clients': 'Clientes OAuth 2.1', + 'settings.oauth.clientsHint': + 'Registre clientes OAuth 2.1 para que las aplicaciones MCP de terceros (Claude Web, Cursor, etc.) puedan conectarse sin tokens estáticos.', + 'settings.oauth.createClient': 'Nuevo cliente', + 'settings.oauth.noClients': 'No hay clientes OAuth registrados.', + 'settings.oauth.clientId': 'ID de cliente', + 'settings.oauth.clientSecret': 'Secreto de cliente', + 'settings.oauth.deleteClient': 'Eliminar cliente', + 'settings.oauth.deleteClientMessage': + 'Este cliente y todas las sesiones activas se eliminarán permanentemente. Cualquier aplicación que lo use perderá el acceso inmediatamente.', + 'settings.oauth.rotateSecret': 'Renovar secreto', + 'settings.oauth.rotateSecretMessage': + 'Se generará un nuevo secreto de cliente y todas las sesiones existentes se invalidarán de inmediato. Actualice su aplicación antes de cerrar este diálogo.', + 'settings.oauth.rotateSecretConfirm': 'Renovar', + 'settings.oauth.rotateSecretConfirming': 'Renovando…', + 'settings.oauth.rotateSecretDoneTitle': 'Nuevo secreto generado', + 'settings.oauth.rotateSecretDoneWarning': + 'Este secreto solo se muestra una vez. Cópielo ahora y actualice su aplicación — todas las sesiones anteriores han sido invalidadas.', + 'settings.oauth.activeSessions': 'Sesiones OAuth activas', + 'settings.oauth.sessionScopes': 'Ámbitos', + 'settings.oauth.sessionExpires': 'Expira', + 'settings.oauth.revoke': 'Revocar', + 'settings.oauth.revokeSession': 'Revocar sesión', + 'settings.oauth.revokeSessionMessage': + 'Esto revocará inmediatamente el acceso de esta sesión OAuth.', + 'settings.oauth.modal.createTitle': 'Registrar cliente OAuth', + 'settings.oauth.modal.presets': 'Ajustes rápidos', + 'settings.oauth.modal.clientName': 'Nombre de la aplicación', + 'settings.oauth.modal.clientNamePlaceholder': 'ej. Claude Web, Mi app MCP', + 'settings.oauth.modal.redirectUris': 'URIs de redirección', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Un URI por línea. HTTPS obligatorio (localhost exento). Coincidencia exacta.', + 'settings.oauth.modal.scopes': 'Ámbitos permitidos', + 'settings.oauth.modal.scopesHint': + 'list_trips y get_trip_summary siempre están disponibles — sin ámbito requerido. Permiten a la IA descubrir los IDs de viaje necesarios.', + 'settings.oauth.modal.selectAll': 'Seleccionar todo', + 'settings.oauth.modal.deselectAll': 'Deseleccionar todo', + 'settings.oauth.modal.creating': 'Registrando…', + 'settings.oauth.modal.create': 'Registrar cliente', + 'settings.oauth.modal.createdTitle': 'Cliente registrado', + 'settings.oauth.modal.createdWarning': + 'El secreto del cliente solo se muestra una vez. Cópielo ahora — no se puede recuperar.', + 'settings.oauth.toast.createError': 'Error al registrar el cliente OAuth', + 'settings.oauth.toast.deleted': 'Cliente OAuth eliminado', + 'settings.oauth.toast.deleteError': 'Error al eliminar el cliente OAuth', + 'settings.oauth.toast.revoked': 'Sesión revocada', + 'settings.oauth.toast.revokeError': 'Error al revocar la sesión', + 'settings.oauth.toast.rotateError': 'Error al renovar el secreto del cliente', + 'settings.oauth.modal.machineClient': + 'Cliente de máquina (sin inicio de sesión en el navegador)', + 'settings.oauth.modal.machineClientHint': + 'Usa el grant client_credentials — sin URIs de redirección. El token se emite directamente vía client_id + client_secret y actúa como tú dentro de los alcances seleccionados.', + 'settings.oauth.modal.machineClientUsage': + 'Obtener token: POST /oauth/token con grant_type=client_credentials, client_id y client_secret. Sin navegador, sin token de actualización.', + 'settings.oauth.badge.machine': 'máquina', + 'settings.account': 'Cuenta', + 'settings.about': 'Acerca de', + 'settings.about.reportBug': 'Reportar un error', + 'settings.about.reportBugHint': 'Encontraste un problema? Avísanos', + 'settings.about.featureRequest': 'Solicitar función', + 'settings.about.featureRequestHint': 'Sugiere una nueva función', + 'settings.about.wikiHint': 'Documentación y guías', + 'settings.about.supporters.badge': 'Patrocinadores Mensuales', + 'settings.about.supporters.title': 'Compañía de viaje para TREK', + 'settings.about.supporters.subtitle': + 'Mientras planeas tu próxima ruta, estas personas ayudan a planear el futuro de TREK. Su aporte mensual va directo al desarrollo y a las horas reales invertidas — para que TREK siga siendo Open Source.', + 'settings.about.supporters.since': 'patrocinador desde {date}', + 'settings.about.supporters.tierEmpty': 'Sé el primero', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK es un planificador de viajes autoalojado que te ayuda a organizar tus viajes desde la primera idea hasta el último recuerdo. Planificación diaria, presupuesto, listas de equipaje, fotos y mucho más — todo en un solo lugar, en tu propio servidor.', + 'settings.about.madeWith': 'Hecho con', + 'settings.about.madeBy': + 'por Maurice y una creciente comunidad de código abierto.', + 'settings.username': 'Usuario', + 'settings.email': 'Correo', + 'settings.role': 'Rol', + 'settings.roleAdmin': 'Administrador', + 'settings.oidcLinked': 'Vinculado con', + 'settings.changePassword': 'Cambiar contraseña', + 'settings.mustChangePassword': + 'Debe cambiar su contraseña antes de continuar. Establezca una nueva contraseña a continuación.', + 'settings.currentPassword': 'Contraseña actual', + 'settings.newPassword': 'Nueva contraseña', + 'settings.confirmPassword': 'Confirmar nueva contraseña', + 'settings.updatePassword': 'Actualizar contraseña', + 'settings.passwordRequired': 'Introduce la contraseña actual y la nueva', + 'settings.passwordTooShort': 'La contraseña debe tener al menos 8 caracteres', + 'settings.passwordMismatch': 'Las contraseñas no coinciden', + 'settings.passwordChanged': 'Contraseña cambiada correctamente', + 'settings.deleteAccount': 'Eliminar cuenta', + 'settings.deleteAccountTitle': '¿Eliminar tu cuenta?', + 'settings.deleteAccountWarning': + 'Tu cuenta y todos tus viajes, lugares y archivos se eliminarán permanentemente. Esta acción no se puede deshacer.', + 'settings.deleteAccountConfirm': 'Eliminar permanentemente', + 'settings.deleteBlockedTitle': 'No es posible eliminarla', + 'settings.deleteBlockedMessage': + 'Eres el único administrador. Asciende a otro usuario a administrador antes de eliminar tu cuenta.', + 'settings.roleUser': 'Usuario', + 'settings.saveProfile': 'Guardar perfil', + 'settings.mfa.title': 'Autenticación de dos factores (2FA)', + 'settings.mfa.description': + 'Añade un segundo paso al iniciar sesión. Usa una app de autenticación (Google Authenticator, Authy, etc.).', + 'settings.mfa.requiredByPolicy': + 'Tu administrador exige autenticación en dos factores. Configura una app de autenticación abajo antes de continuar.', + 'settings.mfa.backupTitle': 'Códigos de respaldo', + 'settings.mfa.backupDescription': + 'Usa estos códigos de un solo uso si pierdes acceso a tu app autenticadora.', + 'settings.mfa.backupWarning': + 'Guárdalos ahora. Cada código solo se puede usar una vez.', + 'settings.mfa.backupCopy': 'Copiar códigos', + 'settings.mfa.backupDownload': 'Descargar TXT', + 'settings.mfa.backupPrint': 'Imprimir / PDF', + 'settings.mfa.backupCopied': 'Códigos de respaldo copiados', + 'settings.mfa.enabled': '2FA está activado en tu cuenta.', + 'settings.mfa.disabled': '2FA no está activado.', + 'settings.mfa.setup': 'Configurar autenticador', + 'settings.mfa.scanQr': + 'Escanea este código QR con tu app o introduce la clave manualmente.', + 'settings.mfa.secretLabel': 'Clave secreta (entrada manual)', + 'settings.mfa.codePlaceholder': 'Código de 6 dígitos', + 'settings.mfa.enable': 'Activar 2FA', + 'settings.mfa.cancelSetup': 'Cancelar', + 'settings.mfa.disableTitle': 'Desactivar 2FA', + 'settings.mfa.disableHint': + 'Introduce tu contraseña y un código actual de tu autenticador.', + 'settings.mfa.disable': 'Desactivar 2FA', + 'settings.mfa.toastEnabled': 'Autenticación de dos factores activada', + 'settings.mfa.toastDisabled': 'Autenticación de dos factores desactivada', + 'settings.mfa.demoBlocked': 'No disponible en modo demo', + 'settings.toast.mapSaved': 'Ajustes del mapa guardados', + 'settings.toast.keysSaved': 'Claves API guardadas', + 'settings.toast.displaySaved': 'Ajustes de visualización guardados', + 'settings.toast.profileSaved': 'Perfil guardado', + 'settings.uploadAvatar': 'Subir foto de perfil', + 'settings.removeAvatar': 'Eliminar foto de perfil', + 'settings.avatarUploaded': 'Foto de perfil actualizada', + 'settings.avatarRemoved': 'Foto de perfil eliminada', + 'settings.avatarError': 'Falló la subida', + 'settings.bookingLabels': 'Etiquetas de rutas de reservas', + 'settings.bookingLabelsHint': + 'Muestra nombres de estaciones / aeropuertos en el mapa. Desactivado, solo se muestra el icono.', + 'settings.currentPasswordRequired': 'La contraseña actual es obligatoria', + 'settings.passwordWeak': + 'La contraseña debe contener mayúsculas, minúsculas, números y un carácter especial', + 'settings.notifyVersionAvailable': 'Nueva versión disponible', + 'settings.notificationPreferences.noChannels': + 'No hay canales de notificación configurados. Pide a un administrador que configure notificaciones por correo o webhook.', + 'settings.webhookUrl.label': 'URL del webhook', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Introduce tu URL de webhook de Discord, Slack o personalizada para recibir notificaciones.', + 'settings.webhookUrl.saved': 'URL del webhook guardada', + 'settings.webhookUrl.test': 'Probar', + 'settings.webhookUrl.testSuccess': 'Webhook de prueba enviado correctamente', + 'settings.webhookUrl.testFailed': 'Error al enviar el webhook de prueba', + 'settings.ntfyUrl.topicLabel': 'Tema de Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL del servidor Ntfy (opcional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Introduce tu tema de Ntfy para recibir notificaciones push. Deja el servidor en blanco para usar el predeterminado configurado por tu administrador.', + 'settings.ntfyUrl.tokenLabel': 'Token de acceso (opcional)', + 'settings.ntfyUrl.tokenHint': + 'Requerido para temas protegidos con contraseña.', + 'settings.ntfyUrl.saved': 'Configuración de Ntfy guardada', + 'settings.ntfyUrl.test': 'Probar', + 'settings.ntfyUrl.testSuccess': + 'Notificación de prueba de Ntfy enviada correctamente', + 'settings.ntfyUrl.testFailed': 'Error en la notificación de prueba de Ntfy', + 'settings.ntfyUrl.tokenCleared': 'Token de acceso eliminado', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/es/share.ts b/shared/src/i18n/es/share.ts new file mode 100644 index 00000000..d63cbe3c --- /dev/null +++ b/shared/src/i18n/es/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Enlace público', + 'share.linkHint': + 'Crea un enlace que cualquiera puede usar para ver este viaje sin iniciar sesión. Solo lectura — no se puede editar.', + 'share.createLink': 'Crear enlace', + 'share.deleteLink': 'Eliminar enlace', + 'share.createError': 'No se pudo crear el enlace', + 'share.permMap': 'Mapa y plan', + 'share.permBookings': 'Reservas', + 'share.permPacking': 'Equipaje', + 'share.permBudget': 'Presupuesto', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/es/shared.ts b/shared/src/i18n/es/shared.ts new file mode 100644 index 00000000..ebfebeb5 --- /dev/null +++ b/shared/src/i18n/es/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Enlace expirado o inválido', + 'shared.expiredHint': 'Este enlace de viaje compartido ya no está activo.', + 'shared.readOnly': 'Vista de solo lectura', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Reservas', + 'shared.tabPacking': 'Equipaje', + 'shared.tabBudget': 'Presupuesto', + 'shared.tabChat': 'Chat', + 'shared.days': 'días', + 'shared.places': 'lugares', + 'shared.other': 'Otro', + 'shared.totalBudget': 'Presupuesto total', + 'shared.messages': 'mensajes', + 'shared.sharedVia': 'Compartido vía', + 'shared.confirmed': 'Confirmado', + 'shared.pending': 'Pendiente', +}; +export default shared; diff --git a/shared/src/i18n/es/stats.ts b/shared/src/i18n/es/stats.ts new file mode 100644 index 00000000..7eda33c6 --- /dev/null +++ b/shared/src/i18n/es/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Países', + 'stats.cities': 'Ciudades', + 'stats.trips': 'Viajes', + 'stats.places': 'Lugares', + 'stats.worldProgress': 'Progreso mundial', + 'stats.visited': 'visitados', + 'stats.remaining': 'restantes', + 'stats.visitedCountries': 'Países visitados', +}; +export default stats; diff --git a/shared/src/i18n/es/system_notice.ts b/shared/src/i18n/es/system_notice.ts new file mode 100644 index 00000000..7ae0c3d9 --- /dev/null +++ b/shared/src/i18n/es/system_notice.ts @@ -0,0 +1,64 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Bienvenido a TREK', + 'system_notice.welcome_v1.body': + 'Tu planificador de viajes todo en uno. Crea itinerarios, comparte viajes con amigos y mantente organizado, online o sin conexión.', + 'system_notice.welcome_v1.cta_label': 'Planificar un viaje', + 'system_notice.welcome_v1.hero_alt': + 'Destino de viaje pintoresco con la interfaz de TREK', + 'system_notice.welcome_v1.highlight_plan': + 'Itinerarios día a día para cualquier viaje', + 'system_notice.welcome_v1.highlight_share': + 'Colabora con tus compañeros de viaje', + 'system_notice.welcome_v1.highlight_offline': + 'Funciona sin conexión en móvil', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Aviso anterior', + 'system_notice.pager.next': 'Siguiente aviso', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Ir al aviso {n}', + 'system_notice.pager.position': 'Aviso {current} de {total}', + 'system_notice.v3_photos.title': 'Las fotos se han movido en 3.0', + 'system_notice.v3_photos.body': + '**Fotos** en el Planificador de Viajes han sido eliminadas. Tus fotos están a salvo — TREK nunca modificó tu biblioteca de Immich o Synology.\n\nLas fotos ahora viven en el addon **Journey**. Journey es opcional — si aún no está disponible, pide a tu admin que lo active en Admin → Complementos.', + 'system_notice.v3_journey.title': 'Conoce Journey — diario de viaje', + 'system_notice.v3_journey.body': + 'Documenta tus viajes como historias enriquecidas con cronologías, galerías de fotos y mapas interactivos.', + 'system_notice.v3_journey.cta_label': 'Abrir Journey', + 'system_notice.v3_journey.highlight_timeline': 'Cronología y galería por día', + 'system_notice.v3_journey.highlight_photos': + 'Importar desde Immich o Synology', + 'system_notice.v3_journey.highlight_share': + 'Compartir públicamente — sin inicio de sesión', + 'system_notice.v3_journey.highlight_export': + 'Exportar como libro de fotos PDF', + 'system_notice.v3_features.title': 'Más novedades en 3.0', + 'system_notice.v3_features.body': + 'Otras cosas que vale la pena conocer de esta versión.', + 'system_notice.v3_features.highlight_dashboard': + 'Rediseño del panel mobile-first', + 'system_notice.v3_features.highlight_offline': + 'Modo sin conexión completo como PWA', + 'system_notice.v3_features.highlight_search': + 'Autocompletado de lugares en tiempo real', + 'system_notice.v3_features.highlight_import': + 'Importar lugares desde archivos KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: actualización OAuth 2.1', + 'system_notice.v3_mcp.body': + 'La integración MCP ha sido completamente renovada. OAuth 2.1 es ahora el método de autenticación recomendado. Los tokens estáticos (trek_…) están obsoletos y se eliminarán en una versión futura.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recomendado (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 ámbitos de permisos granulares', + 'system_notice.v3_mcp.highlight_deprecated': + 'Tokens estáticos trek_ obsoletos', + 'system_notice.v3_mcp.highlight_tools': 'Herramientas y prompts ampliados', + 'system_notice.v3_thankyou.title': 'Una nota personal de mi parte', + 'system_notice.v3_thankyou.body': + 'Antes de seguir — quiero tomarme un momento.\n\nTREK empezó como un proyecto personal que construí para mis propios viajes. Nunca imaginé que crecería hasta convertirse en algo en lo que 4.000 de vosotros confían para planificar sus aventuras. Cada estrella, cada issue, cada solicitud de funcionalidad — los leo todos, y son lo que me mantiene en pie durante las noches largas entre un trabajo a jornada completa y la universidad.\n\nQuiero que sepáis: TREK siempre será open source, siempre self-hosted, siempre vuestro. Sin rastreo, sin suscripciones, sin letra pequeña. Solo una herramienta hecha por alguien que ama viajar tanto como vosotros.\n\nUn agradecimiento especial a [jubnl](https://github.com/jubnl) — te has convertido en un colaborador increíble. Mucho de lo que hace grande la versión 3.0 lleva tu huella. Gracias por creer en este proyecto cuando todavía era un borrador.\n\nY a cada uno de vosotros que reportó un bug, tradujo un texto, compartió TREK con un amigo o simplemente lo usó para planificar un viaje — **gracias**. Vosotros sois la razón de que esto exista.\n\nPor muchas más aventuras juntos.\n\n— Maurice\n\n---\n\n[Únete a la comunidad en Discord](https://discord.gg/7Q6M6jDwzf)\n\nSi TREK mejora tus viajes, un [pequeño café](https://ko-fi.com/mauriceboe) siempre mantiene las luces encendidas.', + 'system_notice.v3014_whitespace_collision.title': + 'Acción requerida: conflicto de cuenta de usuario', + 'system_notice.v3014_whitespace_collision.body': + 'La actualización 3.0.14 detectó uno o más conflictos de nombre de usuario o correo electrónico causados por espacios en blanco al inicio o al final de los valores almacenados. Las cuentas afectadas se renombraron automáticamente. Revisa los registros del servidor en busca de líneas que empiecen por **[migration] WHITESPACE COLLISION** para identificar qué cuentas necesitan revisión.', +}; +export default system_notice; diff --git a/shared/src/i18n/es/todo.ts b/shared/src/i18n/es/todo.ts new file mode 100644 index 00000000..13677a77 --- /dev/null +++ b/shared/src/i18n/es/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Lista de equipaje', + 'todo.subtab.todo': 'Por hacer', + 'todo.completed': 'completado(s)', + 'todo.filter.all': 'Todo', + 'todo.filter.open': 'Abierto', + 'todo.filter.done': 'Hecho', + 'todo.uncategorized': 'Sin categoría', + 'todo.namePlaceholder': 'Nombre de la tarea', + 'todo.descriptionPlaceholder': 'Descripción (opcional)', + 'todo.unassigned': 'Sin asignar', + 'todo.noCategory': 'Sin categoría', + 'todo.hasDescription': 'Con descripción', + 'todo.addItem': 'Nueva tarea', + 'todo.sidebar.sortBy': 'Ordenar por', + 'todo.priority': 'Prioridad', + 'todo.newCategoryLabel': 'nueva', + 'todo.newCategory': 'Nombre de la categoría', + 'todo.addCategory': 'Añadir categoría', + 'todo.newItem': 'Nueva tarea', + 'todo.empty': 'Aún no hay tareas. ¡Añade una tarea para empezar!', + 'todo.filter.my': 'Mis tareas', + 'todo.filter.overdue': 'Vencida', + 'todo.sidebar.tasks': 'Tareas', + 'todo.sidebar.categories': 'Categorías', + 'todo.detail.title': 'Tarea', + 'todo.detail.description': 'Descripción', + 'todo.detail.category': 'Categoría', + 'todo.detail.dueDate': 'Fecha límite', + 'todo.detail.assignedTo': 'Asignado a', + 'todo.detail.delete': 'Eliminar', + 'todo.detail.save': 'Guardar cambios', + 'todo.detail.create': 'Crear tarea', + 'todo.detail.priority': 'Prioridad', + 'todo.detail.noPriority': 'Ninguna', + 'todo.sortByPrio': 'Prioridad', +}; +export default todo; diff --git a/shared/src/i18n/es/transport.ts b/shared/src/i18n/es/transport.ts new file mode 100644 index 00000000..5a9a0369 --- /dev/null +++ b/shared/src/i18n/es/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Añadir transporte', + 'transport.modalTitle.create': 'Añadir transporte', + 'transport.modalTitle.edit': 'Editar transporte', + 'transport.title': 'Transportes', + 'transport.addManual': 'Transporte manual', +}; +export default transport; diff --git a/shared/src/i18n/es/trip.ts b/shared/src/i18n/es/trip.ts new file mode 100644 index 00000000..d21cccb4 --- /dev/null +++ b/shared/src/i18n/es/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transportes', + 'trip.tabs.reservations': 'Reservas', + 'trip.tabs.reservationsShort': 'Reservas', + 'trip.tabs.packing': 'Lista de equipaje', + 'trip.tabs.packingShort': 'Equipaje', + 'trip.tabs.lists': 'Listas', + 'trip.tabs.listsShort': 'Listas', + 'trip.tabs.budget': 'Presupuesto', + 'trip.tabs.files': 'Archivos', + 'trip.loading': 'Cargando viaje...', + 'trip.loadingPhotos': 'Cargando fotos de los lugares...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Lugares', + 'trip.toast.placeUpdated': 'Lugar actualizado', + 'trip.toast.placeAdded': 'Lugar añadido', + 'trip.toast.placeDeleted': 'Lugar eliminado', + 'trip.toast.selectDay': 'Selecciona primero un día', + 'trip.toast.assignedToDay': 'Lugar asignado al día', + 'trip.toast.reorderError': 'No se pudo reordenar', + 'trip.toast.reservationUpdated': 'Reserva actualizada', + 'trip.toast.reservationAdded': 'Reserva añadida', + 'trip.toast.deleted': 'Eliminado', + 'trip.confirm.deletePlace': '¿Seguro que quieres eliminar este lugar?', + 'trip.confirm.deletePlaces': '¿Eliminar {count} lugares?', + 'trip.toast.placesDeleted': '{count} lugares eliminados', +}; +export default trip; diff --git a/shared/src/i18n/es/trips.ts b/shared/src/i18n/es/trips.ts new file mode 100644 index 00000000..7e2a8733 --- /dev/null +++ b/shared/src/i18n/es/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.reminder': 'Recordatorio', + 'trips.reminderNone': 'Ninguno', + 'trips.reminderDay': 'día', + 'trips.reminderDays': 'días', + 'trips.reminderCustom': 'Personalizado', + 'trips.memberRemoved': '{username} eliminado', + 'trips.memberRemoveError': 'Error al eliminar', + 'trips.memberAdded': '{username} añadido', + 'trips.memberAddError': 'Error al añadir', + 'trips.reminderDaysBefore': 'días antes de la salida', + 'trips.reminderDisabledHint': + 'Los recordatorios de viaje están desactivados. Actívalos en Admin > Configuración > Notificaciones.', +}; +export default trips; diff --git a/shared/src/i18n/es/undo.ts b/shared/src/i18n/es/undo.ts new file mode 100644 index 00000000..0046d3b0 --- /dev/null +++ b/shared/src/i18n/es/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Deshacer', + 'undo.tooltip': 'Deshacer: {action}', + 'undo.assignPlace': 'Lugar asignado al día', + 'undo.removeAssignment': 'Lugar eliminado del día', + 'undo.reorder': 'Lugares reordenados', + 'undo.optimize': 'Ruta optimizada', + 'undo.deletePlace': 'Lugar eliminado', + 'undo.deletePlaces': 'Lugares eliminados', + 'undo.moveDay': 'Lugar movido a otro día', + 'undo.lock': 'Bloqueo de lugar activado/desactivado', + 'undo.importGpx': 'Importación GPX', + 'undo.importKeyholeMarkup': 'Importación KMZ/KML', + 'undo.importGoogleList': 'Importación de Google Maps', + 'undo.importNaverList': 'Importación de Naver Maps', + 'undo.addPlace': 'Lugar agregado', + 'undo.done': 'Deshecho: {action}', +}; +export default undo; diff --git a/shared/src/i18n/es/vacay.ts b/shared/src/i18n/es/vacay.ts new file mode 100644 index 00000000..0fa8a0d2 --- /dev/null +++ b/shared/src/i18n/es/vacay.ts @@ -0,0 +1,106 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Planifica y gestiona días de vacaciones', + 'vacay.settings': 'Ajustes', + 'vacay.year': 'Año', + 'vacay.addYear': 'Añadir año siguiente', + 'vacay.addPrevYear': 'Añadir año anterior', + 'vacay.removeYear': 'Eliminar año', + 'vacay.removeYearConfirm': '¿Eliminar {year}?', + 'vacay.removeYearHint': + 'Todas las vacaciones y festivos de empresa de este año se borrarán permanentemente.', + 'vacay.remove': 'Eliminar', + 'vacay.persons': 'Personas', + 'vacay.noPersons': 'No se han añadido personas', + 'vacay.addPerson': 'Añadir persona', + 'vacay.editPerson': 'Editar persona', + 'vacay.removePerson': 'Eliminar persona', + 'vacay.removePersonConfirm': '¿Eliminar a {name}?', + 'vacay.removePersonHint': + 'Todas las vacaciones de esta persona se borrarán permanentemente.', + 'vacay.personName': 'Nombre', + 'vacay.personNamePlaceholder': 'Introduce un nombre', + 'vacay.color': 'Color', + 'vacay.add': 'Añadir', + 'vacay.legend': 'Leyenda', + 'vacay.publicHoliday': 'Festivo', + 'vacay.companyHoliday': 'Festivo de empresa', + 'vacay.weekend': 'Fin de semana', + 'vacay.modeVacation': 'Vacaciones', + 'vacay.modeCompany': 'Festivo de empresa', + 'vacay.entitlement': 'Derecho', + 'vacay.entitlementDays': 'Días', + 'vacay.used': 'Usados', + 'vacay.remaining': 'Restantes', + 'vacay.carriedOver': 'de {year}', + 'vacay.blockWeekends': 'Bloquear fines de semana', + 'vacay.blockWeekendsHint': 'Impide marcar vacaciones en sábados y domingos', + 'vacay.weekendDays': 'Días de fin de semana', + 'vacay.mon': 'Lun', + 'vacay.tue': 'Mar', + 'vacay.wed': 'Mié', + 'vacay.thu': 'Jue', + 'vacay.fri': 'Vie', + 'vacay.sat': 'Sáb', + 'vacay.sun': 'Dom', + 'vacay.publicHolidays': 'Festivos', + 'vacay.publicHolidaysHint': 'Marcar festivos en el calendario', + 'vacay.selectCountry': 'Seleccionar país', + 'vacay.selectRegion': 'Seleccionar región (opcional)', + 'vacay.companyHolidays': 'Festivos de empresa', + 'vacay.companyHolidaysHint': + 'Permitir marcar días festivos comunes de la empresa', + 'vacay.companyHolidaysNoDeduct': + 'Los festivos de empresa no descuentan días de vacaciones.', + 'vacay.weekStart': 'La semana comienza el', + 'vacay.weekStartHint': 'Elige si la semana comienza el lunes o el domingo', + 'vacay.carryOver': 'Arrastrar saldo', + 'vacay.carryOverHint': + 'Trasladar automáticamente los días restantes al año siguiente', + 'vacay.sharing': 'Compartir', + 'vacay.sharingHint': + 'Comparte tu calendario de vacaciones con otros usuarios de TREK', + 'vacay.owner': 'Propietario', + 'vacay.shareEmailPlaceholder': 'Correo electrónico del usuario de TREK', + 'vacay.shareSuccess': 'Plan compartido correctamente', + 'vacay.shareError': 'No se pudo compartir el plan', + 'vacay.dissolve': 'Deshacer fusión', + 'vacay.dissolveHint': + 'Separar de nuevo los calendarios. Tus entradas se conservarán.', + 'vacay.dissolveAction': 'Disolver', + 'vacay.dissolved': 'Calendario separado', + 'vacay.fusedWith': 'Fusionado con', + 'vacay.you': 'tú', + 'vacay.noData': 'Sin datos', + 'vacay.changeColor': 'Cambiar color', + 'vacay.inviteUser': 'Invitar usuario', + 'vacay.inviteHint': + 'Invita a otro usuario de TREK a compartir un calendario combinado de vacaciones.', + 'vacay.selectUser': 'Seleccionar usuario', + 'vacay.sendInvite': 'Enviar invitación', + 'vacay.inviteSent': 'Invitación enviada', + 'vacay.inviteError': 'No se pudo enviar la invitación', + 'vacay.pending': 'pendiente', + 'vacay.noUsersAvailable': 'No hay usuarios disponibles', + 'vacay.accept': 'Aceptar', + 'vacay.decline': 'Rechazar', + 'vacay.acceptFusion': 'Aceptar y fusionar', + 'vacay.inviteTitle': 'Solicitud de fusión', + 'vacay.inviteWantsToFuse': + 'quiere compartir un calendario de vacaciones contigo.', + 'vacay.fuseInfo1': + 'Ambos veréis todas las entradas de vacaciones en un único calendario compartido.', + 'vacay.fuseInfo2': 'Ambas partes pueden crear y editar entradas mutuamente.', + 'vacay.fuseInfo3': + 'Ambas partes pueden borrar entradas y cambiar el número de días de vacaciones disponibles.', + 'vacay.fuseInfo4': + 'Ajustes como festivos y festivos de empresa se comparten.', + 'vacay.fuseInfo5': + 'La fusión puede disolverse en cualquier momento por cualquiera de las partes. Tus entradas se conservarán.', + 'vacay.addCalendar': 'Añadir calendario', + 'vacay.calendarColor': 'Color del calendario', + 'vacay.calendarLabel': 'Etiqueta', + 'vacay.noCalendars': 'Sin calendarios', +}; +export default vacay; diff --git a/shared/src/i18n/externalNotifications/index.ts b/shared/src/i18n/externalNotifications/index.ts new file mode 100644 index 00000000..d8ea58c6 --- /dev/null +++ b/shared/src/i18n/externalNotifications/index.ts @@ -0,0 +1,64 @@ +import ar from '../ar/externalNotifications'; +import br from '../br/externalNotifications'; +import cs from '../cs/externalNotifications'; +import de from '../de/externalNotifications'; +import en from '../en/externalNotifications'; +import es from '../es/externalNotifications'; +import fr from '../fr/externalNotifications'; +import hu from '../hu/externalNotifications'; +import id from '../id/externalNotifications'; +import it from '../it/externalNotifications'; +import ja from '../ja/externalNotifications'; +import ko from '../ko/externalNotifications'; +import nl from '../nl/externalNotifications'; +import pl from '../pl/externalNotifications'; +import ru from '../ru/externalNotifications'; +import tr from '../tr/externalNotifications'; +import uk from '../uk/externalNotifications'; +import zhTW from '../zh-TW/externalNotifications'; +import zh from '../zh/externalNotifications'; +import type { + NotificationLocale, + EmailStrings, + EventTextFn, + PasswordResetStrings, + NotificationEventKey, +} from './types'; + +export * from './types'; + +const LOCALES = { + en, + de, + fr, + es, + hu, + nl, + br, + cs, + pl, + ru, + zh, + 'zh-TW': zhTW, + it, + tr, + ar, + id, + ja, + ko, + uk, +} satisfies Record; + +export const EMAIL_I18N: Record = Object.fromEntries( + Object.entries(LOCALES).map(([k, v]) => [k, v.email]), +); + +export const EVENT_TEXTS: Record< + string, + Record +> = Object.fromEntries(Object.entries(LOCALES).map(([k, v]) => [k, v.events])); + +export const PASSWORD_RESET_I18N: Record = + Object.fromEntries( + Object.entries(LOCALES).map(([k, v]) => [k, v.passwordReset]), + ); diff --git a/shared/src/i18n/externalNotifications/types.ts b/shared/src/i18n/externalNotifications/types.ts new file mode 100644 index 00000000..a7a49bb9 --- /dev/null +++ b/shared/src/i18n/externalNotifications/types.ts @@ -0,0 +1,40 @@ +export interface EmailStrings { + footer: string; + manage: string; + madeWith: string; + openTrek: string; +} + +export interface EventText { + title: string; + body: string; +} + +export type EventTextFn = (params: Record) => EventText; + +export interface PasswordResetStrings { + subject: string; + greeting: string; + body: string; + ctaIntro: string; + expiry: string; + ignore: string; +} + +export type NotificationEventKey = + | 'trip_invite' + | 'booking_change' + | 'trip_reminder' + | 'todo_due' + | 'vacay_invite' + | 'photos_shared' + | 'collab_message' + | 'packing_tagged' + | 'version_available' + | 'synology_session_cleared'; + +export interface NotificationLocale { + email: EmailStrings; + events: Record; + passwordReset: PasswordResetStrings; +} diff --git a/shared/src/i18n/fr/admin.ts b/shared/src/i18n/fr/admin.ts new file mode 100644 index 00000000..616327a8 --- /dev/null +++ b/shared/src/i18n/fr/admin.ts @@ -0,0 +1,373 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notifications', + 'admin.notifications.hint': + 'Choisissez un canal de notification. Un seul peut être actif à la fois.', + 'admin.notifications.none': 'Désactivé', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Enregistrer les paramètres de notification', + 'admin.notifications.saved': 'Paramètres de notification enregistrés', + 'admin.notifications.testWebhook': 'Envoyer un webhook de test', + 'admin.notifications.testWebhookSuccess': + 'Webhook de test envoyé avec succès', + 'admin.notifications.testWebhookFailed': 'Échec du webhook de test', + 'admin.smtp.title': 'E-mail et notifications', + 'admin.smtp.hint': + "Configuration SMTP pour l'envoi des notifications par e-mail.", + 'admin.smtp.testButton': 'Envoyer un e-mail de test', + 'admin.webhook.hint': + 'Envoyer des notifications vers un webhook externe (Discord, Slack, etc.).', + 'admin.smtp.testSuccess': 'E-mail de test envoyé avec succès', + 'admin.smtp.testFailed': "Échec de l'e-mail de test", + 'admin.title': 'Administration', + 'admin.subtitle': 'Gestion des utilisateurs et paramètres système', + 'admin.tabs.users': 'Utilisateurs', + 'admin.tabs.categories': 'Catégories', + 'admin.tabs.backup': 'Sauvegarde', + 'admin.stats.users': 'Utilisateurs', + 'admin.stats.trips': 'Voyages', + 'admin.stats.places': 'Lieux', + 'admin.stats.photos': 'Photos', + 'admin.stats.files': 'Fichiers', + 'admin.table.user': 'Utilisateur', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Rôle', + 'admin.table.created': 'Créé le', + 'admin.table.lastLogin': 'Dernière connexion', + 'admin.table.actions': 'Actions', + 'admin.you': '(Vous)', + 'admin.editUser': "Modifier l'utilisateur", + 'admin.newPassword': 'Nouveau mot de passe', + 'admin.newPasswordHint': 'Laissez vide pour conserver le mot de passe actuel', + 'admin.deleteUser': + "Supprimer l'utilisateur « {name} » ? Tous les voyages seront définitivement supprimés.", + 'admin.deleteUserTitle': "Supprimer l'utilisateur", + 'admin.newPasswordPlaceholder': 'Saisir le nouveau mot de passe…', + 'admin.toast.loadError': "Impossible de charger les données d'administration", + 'admin.toast.userUpdated': 'Utilisateur mis à jour', + 'admin.toast.updateError': 'Échec de la mise à jour', + 'admin.toast.userDeleted': 'Utilisateur supprimé', + 'admin.toast.deleteError': 'Échec de la suppression', + 'admin.toast.cannotDeleteSelf': 'Impossible de supprimer votre propre compte', + 'admin.toast.userCreated': 'Utilisateur créé', + 'admin.toast.createError': "Échec de la création de l'utilisateur", + 'admin.toast.fieldsRequired': + "Le nom d'utilisateur, l'e-mail et le mot de passe sont requis", + 'admin.createUser': 'Créer un utilisateur', + 'admin.invite.title': "Liens d'invitation", + 'admin.invite.subtitle': "Créer des liens d'inscription à usage unique", + 'admin.invite.create': 'Créer un lien', + 'admin.invite.createAndCopy': 'Créer et copier', + 'admin.invite.empty': "Aucun lien d'invitation créé", + 'admin.invite.maxUses': 'Utilisations max.', + 'admin.invite.expiry': 'Expire après', + 'admin.invite.uses': 'utilisé(s)', + 'admin.invite.expiresAt': 'expire le', + 'admin.invite.createdBy': 'par', + 'admin.invite.active': 'Actif', + 'admin.invite.expired': 'Expiré', + 'admin.invite.usedUp': 'Épuisé', + 'admin.invite.copied': "Lien d'invitation copié", + 'admin.invite.copyLink': 'Copier le lien', + 'admin.invite.deleted': "Lien d'invitation supprimé", + 'admin.invite.createError': 'Erreur lors de la création du lien', + 'admin.invite.deleteError': 'Erreur lors de la suppression du lien', + 'admin.tabs.settings': 'Paramètres', + 'admin.allowRegistration': 'Autoriser les inscriptions', + 'admin.allowRegistrationHint': + "Les nouveaux utilisateurs peuvent s'inscrire eux-mêmes", + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': "Exiger l'authentification à deux facteurs (2FA)", + 'admin.requireMfaHint': + "Les utilisateurs sans 2FA doivent terminer la configuration dans Paramètres avant d'utiliser l'application.", + 'admin.apiKeys': 'Clés API', + 'admin.apiKeysHint': + 'Facultatif. Active les données de lieu étendues comme les photos et la météo.', + 'admin.mapsKey': 'Clé API Google Maps', + 'admin.mapsKeyHint': + 'Requise pour la recherche de lieux. Obtenez-la sur console.cloud.google.com', + 'admin.mapsKeyHintLong': + "Sans clé API, OpenStreetMap est utilisé pour la recherche de lieux. Avec une clé Google API, les photos, notes et horaires d'ouverture peuvent également être chargés. Obtenez-en une sur console.cloud.google.com.", + 'admin.recommended': 'Recommandé', + 'admin.weatherKey': 'Clé API OpenWeatherMap', + 'admin.weatherKeyHint': + 'Pour les données météo. Gratuit sur openweathermap.org', + 'admin.validateKey': 'Tester', + 'admin.keyValid': 'Connecté', + 'admin.keyInvalid': 'Invalide', + 'admin.keySaved': 'Clés API enregistrées', + 'admin.oidcTitle': 'Authentification unique (OIDC)', + 'admin.oidcSubtitle': + 'Autorisez la connexion via des fournisseurs externes comme Google, Apple, Authentik ou Keycloak.', + 'admin.oidcDisplayName': "Nom d'affichage", + 'admin.oidcIssuer': "URL de l'émetteur", + 'admin.oidcIssuerHint': + "L'URL de l'émetteur OpenID Connect du fournisseur. ex. https://accounts.google.com", + 'admin.oidcSaved': 'Configuration OIDC enregistrée', + 'admin.oidcOnlyMode': "Désactiver l'authentification par mot de passe", + 'admin.oidcOnlyModeHint': + "Lorsqu'activé, seule la connexion SSO est autorisée. La connexion et l'inscription par mot de passe sont bloquées.", + 'admin.fileTypes': 'Types de fichiers autorisés', + 'admin.fileTypesHint': + 'Configurez les types de fichiers que les utilisateurs peuvent importer.', + 'admin.fileTypesFormat': + 'Extensions séparées par des virgules (ex. jpg,png,pdf,doc). Utilisez * pour autoriser tous les types.', + 'admin.fileTypesSaved': 'Paramètres des types de fichiers enregistrés', + 'admin.placesPhotos.title': 'Photos de lieux', + 'admin.placesPhotos.subtitle': + "Récupère les photos depuis l'API Google Places. Désactivez pour économiser le quota API. Les photos Wikimedia ne sont pas affectées.", + 'admin.placesAutocomplete.title': 'Autocomplétion des lieux', + 'admin.placesAutocomplete.subtitle': + "Utilise l'API Google Places pour les suggestions de recherche. Désactivez pour économiser le quota API.", + 'admin.placesDetails.title': 'Détails du lieu', + 'admin.placesDetails.subtitle': + "Récupère les informations détaillées du lieu (horaires, note, site web) depuis l'API Google Places. Désactivez pour économiser le quota API.", + 'admin.bagTracking.title': 'Suivi des bagages', + 'admin.bagTracking.subtitle': + "Activer le poids et l'attribution de bagages pour les articles", + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': + 'Messagerie en temps réel pour la collaboration', + 'admin.collab.notes.title': 'Notes', + 'admin.collab.notes.subtitle': 'Notes et documents partagés', + 'admin.collab.polls.title': 'Sondages', + 'admin.collab.polls.subtitle': 'Sondages et votes de groupe', + 'admin.collab.whatsnext.title': 'Et ensuite', + 'admin.collab.whatsnext.subtitle': + "Suggestions d'activités et prochaines étapes", + 'admin.tabs.config': 'Personnalisation', + 'admin.tabs.defaults': 'Valeurs par défaut', + 'admin.defaultSettings.title': 'Paramètres utilisateur par défaut', + 'admin.defaultSettings.description': + "Définissez des valeurs par défaut pour toute l'instance. Les utilisateurs n'ayant pas modifié un paramètre verront ces valeurs. Leurs propres modifications ont toujours la priorité.", + 'admin.defaultSettings.saved': 'Valeur par défaut enregistrée', + 'admin.defaultSettings.reset': + 'Réinitialiser à la valeur par défaut intégrée', + 'admin.defaultSettings.resetToBuiltIn': 'réinitialiser', + 'admin.tabs.templates': 'Modèles de bagages', + 'admin.packingTemplates.title': 'Modèles de bagages', + 'admin.packingTemplates.subtitle': + 'Créer des listes de bagages réutilisables pour vos voyages', + 'admin.packingTemplates.create': 'Nouveau modèle', + 'admin.packingTemplates.namePlaceholder': + 'Nom du modèle (ex. Vacances à la plage)', + 'admin.packingTemplates.empty': 'Aucun modèle créé', + 'admin.packingTemplates.items': 'articles', + 'admin.packingTemplates.categories': 'catégories', + 'admin.packingTemplates.itemName': "Nom de l'article", + 'admin.packingTemplates.itemCategory': 'Catégorie', + 'admin.packingTemplates.categoryName': 'Nom de catégorie (ex. Vêtements)', + 'admin.packingTemplates.addCategory': 'Ajouter une catégorie', + 'admin.packingTemplates.created': 'Modèle créé', + 'admin.packingTemplates.deleted': 'Modèle supprimé', + 'admin.packingTemplates.loadError': 'Erreur de chargement des modèles', + 'admin.packingTemplates.createError': 'Erreur de création du modèle', + 'admin.packingTemplates.deleteError': 'Erreur de suppression du modèle', + 'admin.packingTemplates.saveError': 'Erreur de sauvegarde', + 'admin.tabs.addons': 'Extensions', + 'admin.addons.title': 'Extensions', + 'admin.addons.subtitle': + 'Activez ou désactivez des fonctionnalités pour personnaliser votre expérience TREK.', + 'admin.addons.catalog.memories.name': 'Photos (Immich)', + 'admin.addons.catalog.memories.description': + 'Partagez vos photos de voyage via votre instance Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + "Protocole de contexte de modèle pour l'intégration d'assistants IA", + 'admin.addons.catalog.packing.name': 'Listes', + 'admin.addons.catalog.packing.description': + 'Listes de bagages et tâches à faire pour vos voyages', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Suivez les dépenses et planifiez votre budget de voyage', + 'admin.addons.catalog.documents.name': 'Documents', + 'admin.addons.catalog.documents.description': + 'Stockez et gérez vos documents de voyage', + 'admin.addons.catalog.vacay.name': 'Vacances', + 'admin.addons.catalog.vacay.description': + 'Planificateur de vacances personnel avec vue calendrier', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Carte du monde avec pays visités et statistiques de voyage', + 'admin.addons.catalog.collab.name': 'Collaboration', + 'admin.addons.catalog.collab.description': + 'Notes en temps réel, sondages et chat pour la planification de voyage', + 'admin.addons.subtitleBefore': + 'Activez ou désactivez des fonctionnalités pour personnaliser votre expérience ', + 'admin.addons.subtitleAfter': '.', + 'admin.addons.enabled': 'Activé', + 'admin.addons.disabled': 'Désactivé', + 'admin.addons.type.trip': 'Voyage', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Intégration', + 'admin.addons.tripHint': 'Disponible comme onglet dans chaque voyage', + 'admin.addons.globalHint': + 'Disponible comme section autonome dans la navigation principale', + 'admin.addons.integrationHint': + 'Services backend et intégrations API sans page dédiée', + 'admin.addons.toast.updated': 'Extension mise à jour', + 'admin.addons.toast.error': "Échec de la mise à jour de l'extension", + 'admin.addons.noAddons': 'Aucune extension disponible', + 'admin.weather.title': 'Données météo', + 'admin.weather.badge': 'Depuis le 24 mars 2026', + 'admin.weather.description': + 'TREK utilise Open-Meteo comme source de données météo. Open-Meteo est un service météo gratuit et open source — aucune clé API requise.', + 'admin.weather.forecast': 'Prévisions sur 16 jours', + 'admin.weather.forecastDesc': 'Auparavant 5 jours (OpenWeatherMap)', + 'admin.weather.climate': 'Données climatiques historiques', + 'admin.weather.climateDesc': + 'Moyennes des 85 dernières années pour les jours au-delà des prévisions de 16 jours', + 'admin.weather.requests': '10 000 requêtes / jour', + 'admin.weather.requestsDesc': 'Gratuit, aucune clé API requise', + 'admin.weather.locationHint': + "La météo est basée sur le premier lieu avec des coordonnées de chaque jour. Si aucun lieu n'est attribué à un jour, un lieu de la liste est utilisé comme référence.", + 'admin.tabs.audit': 'Audit', + 'admin.audit.subtitle': + "Événements sensibles de sécurité et d'administration (sauvegardes, utilisateurs, 2FA, paramètres).", + 'admin.audit.empty': "Aucune entrée d'audit.", + 'admin.audit.refresh': 'Actualiser', + 'admin.audit.loadMore': 'Charger plus', + 'admin.audit.showing': '{count} chargées · {total} au total', + 'admin.audit.col.time': 'Heure', + 'admin.audit.col.user': 'Utilisateur', + 'admin.audit.col.action': 'Action', + 'admin.audit.col.resource': 'Ressource', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Détails', + 'admin.tabs.mcpTokens': 'Accès MCP', + 'admin.mcpTokens.title': 'Accès MCP', + 'admin.mcpTokens.subtitle': + 'Gérer les sessions OAuth et les tokens API de tous les utilisateurs', + 'admin.mcpTokens.sectionTitle': 'Tokens API', + 'admin.mcpTokens.owner': 'Propriétaire', + 'admin.mcpTokens.tokenName': 'Nom du token', + 'admin.mcpTokens.created': 'Créé', + 'admin.mcpTokens.lastUsed': 'Dernière utilisation', + 'admin.mcpTokens.never': 'Jamais', + 'admin.mcpTokens.empty': "Aucun token MCP n'a encore été créé", + 'admin.mcpTokens.deleteTitle': 'Supprimer le token', + 'admin.mcpTokens.deleteMessage': + "Ce token sera révoqué immédiatement. L'utilisateur perdra l'accès MCP via ce token.", + 'admin.mcpTokens.deleteSuccess': 'Token supprimé', + 'admin.mcpTokens.deleteError': 'Impossible de supprimer le token', + 'admin.mcpTokens.loadError': 'Impossible de charger les tokens', + 'admin.oauthSessions.sectionTitle': 'Sessions OAuth', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Propriétaire', + 'admin.oauthSessions.scopes': 'Portées', + 'admin.oauthSessions.created': 'Créé', + 'admin.oauthSessions.empty': 'Aucune session OAuth active', + 'admin.oauthSessions.revokeTitle': 'Révoquer la session', + 'admin.oauthSessions.revokeMessage': + "Cette session OAuth sera révoquée immédiatement. Le client perdra l'accès MCP.", + 'admin.oauthSessions.revokeSuccess': 'Session révoquée', + 'admin.oauthSessions.revokeError': 'Impossible de révoquer la session', + 'admin.oauthSessions.loadError': 'Impossible de charger les sessions OAuth', + 'admin.tabs.github': 'GitHub', + 'admin.github.title': 'Historique des versions', + 'admin.github.subtitle': 'Dernières mises à jour de {repo}', + 'admin.github.latest': 'Dernière', + 'admin.github.prerelease': 'Pré-version', + 'admin.github.showDetails': 'Afficher les détails', + 'admin.github.hideDetails': 'Masquer les détails', + 'admin.github.loadMore': 'Charger plus', + 'admin.github.loading': 'Chargement…', + 'admin.github.support': 'Aidez à poursuivre le développement de TREK', + 'admin.github.error': 'Impossible de charger les versions', + 'admin.github.by': 'par', + 'admin.update.available': 'Mise à jour disponible', + 'admin.update.text': + 'TREK {version} est disponible. Vous utilisez {current}.', + 'admin.update.button': 'Voir sur GitHub', + 'admin.update.install': 'Installer la mise à jour', + 'admin.update.confirmTitle': 'Installer la mise à jour ?', + 'admin.update.confirmText': + 'TREK sera mis à jour de {current} vers {version}. Le serveur redémarrera automatiquement ensuite.', + 'admin.update.dataInfo': + 'Toutes vos données (voyages, utilisateurs, clés API, importations, Vacances, Atlas, budgets) seront préservées.', + 'admin.update.warning': + "L'application sera brièvement indisponible pendant le redémarrage.", + 'admin.update.confirm': 'Mettre à jour maintenant', + 'admin.update.installing': 'Mise à jour…', + 'admin.update.success': 'Mise à jour installée ! Le serveur redémarre…', + 'admin.update.failed': 'Échec de la mise à jour', + 'admin.update.backupHint': + 'Nous recommandons de créer une sauvegarde avant la mise à jour.', + 'admin.update.backupLink': 'Aller aux sauvegardes', + 'admin.update.howTo': 'Comment mettre à jour', + 'admin.update.dockerText': + 'Votre instance TREK fonctionne dans Docker. Pour mettre à jour vers {version}, exécutez les commandes suivantes sur votre serveur :', + 'admin.update.reloadHint': + 'Veuillez recharger la page dans quelques secondes.', + 'admin.tabs.permissions': 'Permissions', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Les notifications in-app sont toujours actives et ne peuvent pas être désactivées globalement.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook admin', + 'admin.notifications.adminWebhookPanel.hint': + "Ce webhook est utilisé exclusivement pour les notifications admin (ex. alertes de version). Il est séparé des webhooks utilisateur et s'active automatiquement si une URL est configurée.", + 'admin.notifications.adminWebhookPanel.saved': + 'URL du webhook admin enregistrée', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Webhook de test envoyé avec succès', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Échec du webhook de test', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + "Le webhook admin s'active automatiquement si une URL est configurée", + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Permet aux utilisateurs de configurer leurs propres sujets ntfy pour les notifications push. Définissez le serveur par défaut ci-dessous pour pré-remplir les paramètres utilisateur.', + 'admin.notifications.testNtfy': 'Envoyer un Ntfy de test', + 'admin.notifications.testNtfySuccess': 'Ntfy de test envoyé avec succès', + 'admin.notifications.testNtfyFailed': "Échec de l'envoi du Ntfy de test", + 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', + 'admin.notifications.adminNtfyPanel.hint': + "Ce sujet Ntfy est utilisé exclusivement pour les notifications admin (ex. alertes de version). Il est séparé des sujets par utilisateur et s'active toujours lorsqu'il est configuré.", + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL du serveur Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Utilisé également comme serveur par défaut pour les notifications ntfy des utilisateurs. Laisser vide pour utiliser ntfy.sh. Les utilisateurs peuvent le modifier dans leurs propres paramètres.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Sujet admin', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': "Jeton d'accès (optionnel)", + 'admin.notifications.adminNtfyPanel.tokenCleared': + "Jeton d'accès admin effacé", + 'admin.notifications.adminNtfyPanel.saved': + 'Paramètres Ntfy admin enregistrés', + 'admin.notifications.adminNtfyPanel.test': 'Envoyer un Ntfy de test', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Ntfy de test envoyé avec succès', + 'admin.notifications.adminNtfyPanel.testFailed': + "Échec de l'envoi du Ntfy de test", + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + "Le Ntfy admin s'active toujours lorsqu'un sujet est configuré", + 'admin.notifications.adminNotificationsHint': + "Configurez quels canaux envoient les notifications admin (ex. alertes de version). Le webhook s'active automatiquement si une URL webhook admin est définie.", + 'admin.notifications.tripReminders.title': 'Rappels de voyage', + 'admin.notifications.tripReminders.hint': + "Envoie une notification de rappel avant le début d'un voyage (nécessite des jours de rappel définis sur le voyage).", + 'admin.notifications.tripReminders.enabled': 'Rappels de voyage activés', + 'admin.notifications.tripReminders.disabled': 'Rappels de voyage désactivés', + 'admin.tabs.notifications': 'Notifications', + 'admin.addons.catalog.journey.name': 'Journal de voyage', + 'admin.addons.catalog.journey.description': + 'Suivi de voyages et journal avec check-ins, photos et récits quotidiens', +}; +export default admin; diff --git a/shared/src/i18n/fr/airport.ts b/shared/src/i18n/fr/airport.ts new file mode 100644 index 00000000..45658027 --- /dev/null +++ b/shared/src/i18n/fr/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': "Code ou ville de l'aéroport (ex. FRA)", +}; +export default airport; diff --git a/shared/src/i18n/fr/atlas.ts b/shared/src/i18n/fr/atlas.ts new file mode 100644 index 00000000..c474ab3f --- /dev/null +++ b/shared/src/i18n/fr/atlas.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Votre empreinte de voyage à travers le monde', + 'atlas.countries': 'Pays', + 'atlas.trips': 'Voyages', + 'atlas.places': 'Lieux', + 'atlas.days': 'Jours', + 'atlas.visitedCountries': 'Pays visités', + 'atlas.cities': 'Villes', + 'atlas.noData': 'Aucune donnée de voyage', + 'atlas.noDataHint': + 'Créez un voyage et ajoutez des lieux pour voir votre carte du monde', + 'atlas.lastTrip': 'Dernier voyage', + 'atlas.nextTrip': 'Prochain voyage', + 'atlas.daysLeft': 'jours restants', + 'atlas.streak': 'Série', + 'atlas.year': 'an', + 'atlas.years': 'ans', + 'atlas.yearInRow': 'année consécutive', + 'atlas.yearsInRow': 'années consécutives', + 'atlas.tripIn': 'voyage en', + 'atlas.tripsIn': 'voyages en', + 'atlas.since': 'depuis', + 'atlas.europe': 'Europe', + 'atlas.asia': 'Asie', + 'atlas.northAmerica': 'Amérique du N.', + 'atlas.southAmerica': 'Amérique du S.', + 'atlas.africa': 'Afrique', + 'atlas.oceania': 'Océanie', + 'atlas.other': 'Autre', + 'atlas.firstVisit': 'Premier voyage', + 'atlas.lastVisitLabel': 'Dernier voyage', + 'atlas.tripSingular': 'Voyage', + 'atlas.tripPlural': 'Voyages', + 'atlas.placeVisited': 'Lieu visité', + 'atlas.placesVisited': 'Lieux visités', + 'atlas.statsTab': 'Statistiques', + 'atlas.bucketTab': 'Bucket List', + 'atlas.addBucket': 'Ajouter à la bucket list', + 'atlas.bucketNamePlaceholder': 'Lieu ou destination...', + 'atlas.bucketNotesPlaceholder': 'Notes (optionnel)', + 'atlas.bucketEmpty': 'Votre bucket list est vide', + 'atlas.bucketEmptyHint': 'Ajoutez des lieux que vous rêvez de visiter', + 'atlas.unmark': 'Retirer', + 'atlas.confirmMark': 'Marquer ce pays comme visité ?', + 'atlas.confirmUnmark': 'Retirer ce pays de votre liste ?', + 'atlas.confirmUnmarkRegion': 'Retirer cette région de votre liste ?', + 'atlas.markVisited': 'Marquer comme visité', + 'atlas.markVisitedHint': 'Ajouter ce pays à votre liste de visités', + 'atlas.markRegionVisitedHint': + 'Ajouter cette région à votre liste de visités', + 'atlas.addToBucket': 'Ajouter à la bucket list', + 'atlas.addPoi': 'Ajouter un lieu', + 'atlas.searchCountry': 'Rechercher un pays…', + 'atlas.month': 'Mois', + 'atlas.addToBucketHint': 'Sauvegarder comme lieu à visiter', + 'atlas.bucketWhen': "Quand prévoyez-vous d'y aller ?", +}; +export default atlas; diff --git a/shared/src/i18n/fr/backup.ts b/shared/src/i18n/fr/backup.ts new file mode 100644 index 00000000..dc2d212a --- /dev/null +++ b/shared/src/i18n/fr/backup.ts @@ -0,0 +1,79 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Sauvegarde des données', + 'backup.subtitle': 'Base de données et tous les fichiers importés', + 'backup.refresh': 'Actualiser', + 'backup.upload': 'Importer une sauvegarde', + 'backup.uploading': 'Importation…', + 'backup.create': 'Créer une sauvegarde', + 'backup.creating': 'Création…', + 'backup.empty': 'Aucune sauvegarde', + 'backup.createFirst': 'Créer la première sauvegarde', + 'backup.download': 'Télécharger', + 'backup.restore': 'Restaurer', + 'backup.confirm.restore': + 'Restaurer la sauvegarde « {name} » ?\n\nToutes les données actuelles seront remplacées par la sauvegarde.', + 'backup.confirm.uploadRestore': + 'Importer et restaurer le fichier de sauvegarde « {name} » ?\n\nToutes les données actuelles seront écrasées.', + 'backup.confirm.delete': 'Supprimer la sauvegarde « {name} » ?', + 'backup.toast.loadError': 'Impossible de charger les sauvegardes', + 'backup.toast.created': 'Sauvegarde créée avec succès', + 'backup.toast.createError': 'Impossible de créer la sauvegarde', + 'backup.toast.restored': 'Sauvegarde restaurée. La page va se recharger…', + 'backup.toast.restoreError': 'Échec de la restauration', + 'backup.toast.uploadError': "Échec de l'import", + 'backup.toast.deleted': 'Sauvegarde supprimée', + 'backup.toast.deleteError': 'Échec de la suppression', + 'backup.toast.downloadError': 'Échec du téléchargement', + 'backup.toast.settingsSaved': + 'Paramètres de sauvegarde automatique enregistrés', + 'backup.toast.settingsError': "Impossible d'enregistrer les paramètres", + 'backup.auto.title': 'Sauvegarde automatique', + 'backup.auto.subtitle': 'Sauvegarde automatique programmée', + 'backup.auto.enable': 'Activer la sauvegarde automatique', + 'backup.auto.enableHint': + 'Les sauvegardes seront créées automatiquement selon le calendrier choisi', + 'backup.auto.interval': 'Intervalle', + 'backup.auto.hour': "Exécuter à l'heure", + 'backup.auto.hourHint': 'Heure locale du serveur (format {format})', + 'backup.auto.dayOfWeek': 'Jour de la semaine', + 'backup.auto.dayOfMonth': 'Jour du mois', + 'backup.auto.dayOfMonthHint': + 'Limité à 1–28 pour la compatibilité avec tous les mois', + 'backup.auto.scheduleSummary': 'Planification', + 'backup.auto.summaryDaily': 'Tous les jours à {hour}h00', + 'backup.auto.summaryWeekly': 'Chaque {day} à {hour}h00', + 'backup.auto.summaryMonthly': 'Le {day} de chaque mois à {hour}h00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + "La sauvegarde automatique est configurée via les variables d'environnement Docker. Pour modifier ces paramètres, mettez à jour votre docker-compose.yml et redémarrez le conteneur.", + 'backup.auto.copyEnv': "Copier les variables d'env Docker", + 'backup.auto.envCopied': + "Variables d'env Docker copiées dans le presse-papiers", + 'backup.auto.keepLabel': 'Supprimer les anciennes sauvegardes après', + 'backup.dow.sunday': 'Dim', + 'backup.dow.monday': 'Lun', + 'backup.dow.tuesday': 'Mar', + 'backup.dow.wednesday': 'Mer', + 'backup.dow.thursday': 'Jeu', + 'backup.dow.friday': 'Ven', + 'backup.dow.saturday': 'Sam', + 'backup.interval.hourly': 'Toutes les heures', + 'backup.interval.daily': 'Quotidien', + 'backup.interval.weekly': 'Hebdomadaire', + 'backup.interval.monthly': 'Mensuel', + 'backup.keep.1day': '1 jour', + 'backup.keep.3days': '3 jours', + 'backup.keep.7days': '7 jours', + 'backup.keep.14days': '14 jours', + 'backup.keep.30days': '30 jours', + 'backup.keep.forever': 'Conserver indéfiniment', + 'backup.restoreConfirmTitle': 'Restaurer la sauvegarde ?', + 'backup.restoreWarning': + 'Toutes les données actuelles (voyages, lieux, utilisateurs, importations) seront définitivement remplacées par la sauvegarde. Cette action est irréversible.', + 'backup.restoreTip': + "Conseil : créez une sauvegarde de l'état actuel avant de restaurer.", + 'backup.restoreConfirm': 'Oui, restaurer', +}; +export default backup; diff --git a/shared/src/i18n/fr/budget.ts b/shared/src/i18n/fr/budget.ts new file mode 100644 index 00000000..1c4e474b --- /dev/null +++ b/shared/src/i18n/fr/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'Exporter CSV', + 'budget.emptyTitle': 'Aucun budget créé', + 'budget.emptyText': + 'Créez des catégories et des entrées pour planifier votre budget de voyage', + 'budget.emptyPlaceholder': 'Nom de la catégorie…', + 'budget.createCategory': 'Créer une catégorie', + 'budget.category': 'Catégorie', + 'budget.categoryName': 'Nom de la catégorie', + 'budget.table.name': 'Nom', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Personnes', + 'budget.table.days': 'Jours', + 'budget.table.perPerson': 'Par personne', + 'budget.table.perDay': 'Par jour', + 'budget.table.perPersonDay': 'P. p / Jour', + 'budget.table.note': 'Note', + 'budget.table.date': 'Date', + 'budget.newEntry': 'Nouvelle entrée', + 'budget.defaultEntry': 'Nouvelle entrée', + 'budget.defaultCategory': 'Nouvelle catégorie', + 'budget.total': 'Total', + 'budget.totalBudget': 'Budget total', + 'budget.byCategory': 'Par catégorie', + 'budget.editTooltip': 'Cliquez pour modifier', + 'budget.linkedToReservation': + 'Lié à une réservation — modifiez le nom depuis celle-ci', + 'budget.confirm.deleteCategory': + 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} entrées ?', + 'budget.deleteCategory': 'Supprimer la catégorie', + 'budget.perPerson': 'Par personne', + 'budget.paid': 'Payé', + 'budget.open': 'Ouvert', + 'budget.noMembers': 'Aucun membre assigné', + 'budget.settlement': 'Règlement', + 'budget.settlementInfo': + "Cliquez sur l'avatar d'un membre sur un poste budgétaire pour le marquer en vert — cela signifie qu'il a payé. Le règlement indique ensuite qui doit combien à qui.", + 'budget.netBalances': 'Soldes nets', + 'budget.categoriesLabel': 'catégories', +}; +export default budget; diff --git a/shared/src/i18n/fr/categories.ts b/shared/src/i18n/fr/categories.ts new file mode 100644 index 00000000..ee1b8f22 --- /dev/null +++ b/shared/src/i18n/fr/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Catégories', + 'categories.subtitle': 'Gérer les catégories de lieux', + 'categories.new': 'Nouvelle catégorie', + 'categories.empty': 'Aucune catégorie', + 'categories.namePlaceholder': 'Nom de la catégorie', + 'categories.icon': 'Icône', + 'categories.color': 'Couleur', + 'categories.customColor': 'Choisir une couleur personnalisée', + 'categories.preview': 'Aperçu', + 'categories.defaultName': 'Catégorie', + 'categories.update': 'Mettre à jour', + 'categories.create': 'Créer', + 'categories.confirm.delete': + 'Supprimer la catégorie ? Les lieux de cette catégorie ne seront pas supprimés.', + 'categories.toast.loadError': 'Impossible de charger les catégories', + 'categories.toast.nameRequired': 'Veuillez saisir un nom', + 'categories.toast.updated': 'Catégorie mise à jour', + 'categories.toast.created': 'Catégorie créée', + 'categories.toast.saveError': "Échec de l'enregistrement", + 'categories.toast.deleted': 'Catégorie supprimée', + 'categories.toast.deleteError': 'Échec de la suppression', +}; +export default categories; diff --git a/shared/src/i18n/fr/collab.ts b/shared/src/i18n/fr/collab.ts new file mode 100644 index 00000000..d771b15c --- /dev/null +++ b/shared/src/i18n/fr/collab.ts @@ -0,0 +1,76 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Discussion', + 'collab.tabs.notes': 'Notes', + 'collab.tabs.polls': 'Sondages', + 'collab.whatsNext.title': 'À venir', + 'collab.whatsNext.today': "Aujourd'hui", + 'collab.whatsNext.tomorrow': 'Demain', + 'collab.whatsNext.empty': 'Aucune activité à venir', + 'collab.whatsNext.until': 'à', + 'collab.whatsNext.emptyHint': + 'Les activités avec des horaires apparaîtront ici', + 'collab.chat.send': 'Envoyer', + 'collab.chat.placeholder': 'Écrire un message…', + 'collab.chat.empty': 'Commencez la conversation', + 'collab.chat.emptyHint': + 'Les messages sont partagés avec tous les membres du voyage', + 'collab.chat.emptyDesc': + 'Partagez des idées, des plans et des mises à jour avec votre groupe de voyage', + 'collab.chat.today': "Aujourd'hui", + 'collab.chat.yesterday': 'Hier', + 'collab.chat.deletedMessage': 'a supprimé un message', + 'collab.chat.reply': 'Répondre', + 'collab.chat.loadMore': 'Charger les messages précédents', + 'collab.chat.justNow': "à l'instant", + 'collab.chat.minutesAgo': 'il y a {n} min', + 'collab.chat.hoursAgo': 'il y a {n} h', + 'collab.notes.title': 'Notes', + 'collab.notes.new': 'Nouvelle note', + 'collab.notes.empty': 'Aucune note', + 'collab.notes.emptyHint': 'Commencez à capturer vos idées et plans', + 'collab.notes.all': 'Toutes', + 'collab.notes.titlePlaceholder': 'Titre de la note', + 'collab.notes.contentPlaceholder': 'Écrivez quelque chose…', + 'collab.notes.categoryPlaceholder': 'Catégorie', + 'collab.notes.newCategory': 'Nouvelle catégorie…', + 'collab.notes.category': 'Catégorie', + 'collab.notes.noCategory': 'Sans catégorie', + 'collab.notes.color': 'Couleur', + 'collab.notes.save': 'Enregistrer', + 'collab.notes.cancel': 'Annuler', + 'collab.notes.edit': 'Modifier', + 'collab.notes.delete': 'Supprimer', + 'collab.notes.pin': 'Épingler', + 'collab.notes.unpin': 'Désépingler', + 'collab.notes.daysAgo': 'il y a {n} j', + 'collab.notes.categorySettings': 'Gérer les catégories', + 'collab.notes.create': 'Créer', + 'collab.notes.website': 'Site web', + 'collab.notes.websitePlaceholder': 'https://…', + 'collab.notes.attachFiles': 'Joindre des fichiers', + 'collab.notes.noCategoriesYet': 'Aucune catégorie', + 'collab.notes.emptyDesc': 'Créez une note pour commencer', + 'collab.polls.title': 'Sondages', + 'collab.polls.new': 'Nouveau sondage', + 'collab.polls.empty': 'Aucun sondage', + 'collab.polls.emptyHint': 'Posez des questions au groupe et votez ensemble', + 'collab.polls.question': 'Question', + 'collab.polls.questionPlaceholder': 'Que devrait-on faire ?', + 'collab.polls.addOption': '+ Ajouter une option', + 'collab.polls.optionPlaceholder': 'Option {n}', + 'collab.polls.create': 'Créer le sondage', + 'collab.polls.close': 'Fermer', + 'collab.polls.closed': 'Fermé', + 'collab.polls.votes': '{n} votes', + 'collab.polls.vote': '{n} vote', + 'collab.polls.multipleChoice': 'Choix multiples', + 'collab.polls.multiChoice': 'Choix multiples', + 'collab.polls.deadline': 'Date limite', + 'collab.polls.option': 'Option', + 'collab.polls.options': 'Options', + 'collab.polls.delete': 'Supprimer', + 'collab.polls.closedSection': 'Fermés', +}; +export default collab; diff --git a/shared/src/i18n/fr/common.ts b/shared/src/i18n/fr/common.ts new file mode 100644 index 00000000..fb3d1ca7 --- /dev/null +++ b/shared/src/i18n/fr/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Enregistrer', + 'common.showMore': 'Voir plus', + 'common.showLess': 'Voir moins', + 'common.cancel': 'Annuler', + 'common.clear': 'Effacer', + 'common.delete': 'Supprimer', + 'common.edit': 'Modifier', + 'common.add': 'Ajouter', + 'common.loading': 'Chargement…', + 'common.import': 'Importer', + 'common.select': 'Sélectionner', + 'common.selectAll': 'Tout sélectionner', + 'common.deselectAll': 'Tout désélectionner', + 'common.error': 'Erreur', + 'common.unknownError': 'Erreur inconnue', + 'common.tooManyAttempts': 'Trop de tentatives. Veuillez réessayer plus tard.', + 'common.back': 'Retour', + 'common.all': 'Tout', + 'common.close': 'Fermer', + 'common.open': 'Ouvrir', + 'common.upload': 'Importer', + 'common.search': 'Rechercher', + 'common.confirm': 'Confirmer', + 'common.ok': 'OK', + 'common.yes': 'Oui', + 'common.no': 'Non', + 'common.or': 'ou', + 'common.none': 'Aucun', + 'common.date': 'Date', + 'common.rename': 'Renommer', + 'common.discardChanges': 'Ignorer les modifications', + 'common.discard': 'Ignorer', + 'common.name': 'Nom', + 'common.email': 'E-mail', + 'common.password': 'Mot de passe', + 'common.saving': 'Enregistrement…', + 'common.saved': 'Enregistré', + 'common.expand': 'Développer', + 'common.collapse': 'Réduire', + 'common.update': 'Mettre à jour', + 'common.change': 'Modifier', + 'common.uploading': 'Import en cours…', + 'common.backToPlanning': 'Retour à la planification', + 'common.reset': 'Réinitialiser', + 'common.copy': 'Copier', + 'common.copied': 'Copié', + 'common.justNow': "à l'instant", + 'common.hoursAgo': 'il y a {count}h', + 'common.daysAgo': 'il y a {count}j', +}; +export default common; diff --git a/shared/src/i18n/fr/dashboard.ts b/shared/src/i18n/fr/dashboard.ts new file mode 100644 index 00000000..8363f9ec --- /dev/null +++ b/shared/src/i18n/fr/dashboard.ts @@ -0,0 +1,110 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Mes voyages', + 'dashboard.subtitle.loading': 'Chargement des voyages…', + 'dashboard.subtitle.trips': '{count} voyages ({archived} archivés)', + 'dashboard.subtitle.empty': 'Commencez votre premier voyage', + 'dashboard.subtitle.activeOne': '{count} voyage actif', + 'dashboard.subtitle.activeMany': '{count} voyages actifs', + 'dashboard.subtitle.archivedSuffix': ' · {count} archivés', + 'dashboard.newTrip': 'Nouveau voyage', + 'dashboard.gridView': 'Vue en grille', + 'dashboard.listView': 'Vue en liste', + 'dashboard.currency': 'Devise', + 'dashboard.timezone': 'Fuseau horaire', + 'dashboard.localTime': 'Heure locale', + 'dashboard.timezoneCustomTitle': 'Fuseau horaire personnalisé', + 'dashboard.timezoneCustomLabelPlaceholder': 'Libellé (facultatif)', + 'dashboard.timezoneCustomTzPlaceholder': 'ex. America/New_York', + 'dashboard.timezoneCustomAdd': 'Ajouter', + 'dashboard.timezoneCustomErrorEmpty': + 'Saisissez un identifiant de fuseau horaire', + 'dashboard.timezoneCustomErrorInvalid': + 'Fuseau horaire invalide. Utilisez un format comme Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Déjà ajouté', + 'dashboard.emptyTitle': 'Aucun voyage', + 'dashboard.emptyText': + 'Créez votre premier voyage et commencez à planifier !', + 'dashboard.emptyButton': 'Créer un premier voyage', + 'dashboard.nextTrip': 'Prochain voyage', + 'dashboard.shared': 'Partagé', + 'dashboard.sharedBy': 'Partagé par {name}', + 'dashboard.days': 'Jours', + 'dashboard.places': 'Lieux', + 'dashboard.members': 'Compagnons de voyage', + 'dashboard.archive': 'Archiver', + 'dashboard.copyTrip': 'Copier', + 'dashboard.copySuffix': 'copie', + 'dashboard.restore': 'Restaurer', + 'dashboard.archived': 'Archivé', + 'dashboard.status.ongoing': 'En cours', + 'dashboard.status.today': "Aujourd'hui", + 'dashboard.status.tomorrow': 'Demain', + 'dashboard.status.past': 'Passé', + 'dashboard.status.daysLeft': '{count} jours restants', + 'dashboard.toast.loadError': 'Impossible de charger les voyages', + 'dashboard.toast.created': 'Voyage créé avec succès !', + 'dashboard.toast.createError': 'Impossible de créer le voyage', + 'dashboard.toast.updated': 'Voyage mis à jour !', + 'dashboard.toast.updateError': 'Impossible de mettre à jour le voyage', + 'dashboard.toast.deleted': 'Voyage supprimé', + 'dashboard.toast.deleteError': 'Impossible de supprimer le voyage', + 'dashboard.toast.archived': 'Voyage archivé', + 'dashboard.toast.archiveError': "Impossible d'archiver le voyage", + 'dashboard.toast.restored': 'Voyage restauré', + 'dashboard.toast.restoreError': 'Impossible de restaurer le voyage', + 'dashboard.toast.copied': 'Voyage copié !', + 'dashboard.toast.copyError': 'Impossible de copier le voyage', + 'dashboard.confirm.delete': + 'Supprimer le voyage « {title} » ? Tous les lieux et plans seront définitivement supprimés.', + 'dashboard.editTrip': 'Modifier le voyage', + 'dashboard.createTrip': 'Créer un nouveau voyage', + 'dashboard.tripTitle': 'Titre', + 'dashboard.tripTitlePlaceholder': 'ex. Été au Japon', + 'dashboard.tripDescription': 'Description', + 'dashboard.tripDescriptionPlaceholder': 'De quoi parle ce voyage ?', + 'dashboard.startDate': 'Date de début', + 'dashboard.endDate': 'Date de fin', + 'dashboard.dayCount': 'Nombre de jours', + 'dashboard.dayCountHint': + "Nombre de jours à planifier lorsqu'aucune date de voyage n'est définie.", + 'dashboard.noDateHint': + 'Aucune date définie — 7 jours par défaut seront créés. Vous pouvez modifier cela à tout moment.', + 'dashboard.coverImage': 'Image de couverture', + 'dashboard.addCoverImage': 'Ajouter une image de couverture', + 'dashboard.addMembers': 'Compagnons de voyage', + 'dashboard.addMember': 'Ajouter un membre', + 'dashboard.coverSaved': 'Image de couverture enregistrée', + 'dashboard.coverUploadError': "Échec de l'import", + 'dashboard.coverRemoveError': 'Échec de la suppression', + 'dashboard.titleRequired': 'Le titre est obligatoire', + 'dashboard.endDateError': + 'La date de fin doit être postérieure à la date de début', + 'dashboard.greeting.morning': 'Bonjour,', + 'dashboard.greeting.afternoon': 'Bon après-midi,', + 'dashboard.greeting.evening': 'Bonsoir,', + 'dashboard.mobile.liveNow': 'En direct', + 'dashboard.mobile.tripProgress': 'Progression du voyage', + 'dashboard.mobile.daysLeft': '{count} jours restants', + 'dashboard.mobile.places': 'Lieux', + 'dashboard.mobile.buddies': 'Compagnons', + 'dashboard.mobile.newTrip': 'Nouveau voyage', + 'dashboard.mobile.currency': 'Devise', + 'dashboard.mobile.timezone': 'Fuseau horaire', + 'dashboard.mobile.upcomingTrips': 'Voyages à venir', + 'dashboard.mobile.yourTrips': 'Vos voyages', + 'dashboard.mobile.trips': 'voyages', + 'dashboard.mobile.starts': 'Début', + 'dashboard.mobile.duration': 'Durée', + 'dashboard.mobile.day': 'jour', + 'dashboard.mobile.days': 'jours', + 'dashboard.mobile.ongoing': 'En cours', + 'dashboard.mobile.startsToday': "Commence aujourd'hui", + 'dashboard.mobile.tomorrow': 'Demain', + 'dashboard.mobile.inDays': 'Dans {count} jours', + 'dashboard.mobile.inMonths': 'Dans {count} mois', + 'dashboard.mobile.completed': 'Terminé', + 'dashboard.mobile.currencyConverter': 'Convertisseur de devises', +}; +export default dashboard; diff --git a/shared/src/i18n/fr/day.ts b/shared/src/i18n/fr/day.ts new file mode 100644 index 00000000..4680ecfd --- /dev/null +++ b/shared/src/i18n/fr/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Probabilité de pluie', + 'day.precipitation': 'Précipitations', + 'day.wind': 'Vent', + 'day.sunrise': 'Lever du soleil', + 'day.sunset': 'Coucher du soleil', + 'day.hourlyForecast': 'Prévisions horaires', + 'day.climateHint': + 'Moyennes historiques — prévisions réelles disponibles dans les 16 jours précédant cette date.', + 'day.noWeather': + 'Aucune donnée météo disponible. Ajoutez un lieu avec des coordonnées.', + 'day.overview': 'Aperçu du jour', + 'day.accommodation': 'Hébergement', + 'day.addAccommodation': 'Ajouter un hébergement', + 'day.hotelDayRange': 'Appliquer aux jours', + 'day.noPlacesForHotel': "Ajoutez d'abord des lieux à votre voyage", + 'day.allDays': 'Tous', + 'day.checkIn': 'Arrivée', + 'day.checkInUntil': "Jusqu'à", + 'day.checkOut': 'Départ', + 'day.confirmation': 'Confirmation', + 'day.editAccommodation': "Modifier l'hébergement", + 'day.reservations': 'Réservations', +}; +export default day; diff --git a/shared/src/i18n/fr/dayplan.ts b/shared/src/i18n/fr/dayplan.ts new file mode 100644 index 00000000..1a671207 --- /dev/null +++ b/shared/src/i18n/fr/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Exporter le calendrier (ICS)', + 'dayplan.emptyDay': 'Aucun lieu prévu pour ce jour', + 'dayplan.addNote': 'Ajouter une note', + 'dayplan.editNote': 'Modifier la note', + 'dayplan.noteAdd': 'Ajouter une note', + 'dayplan.noteEdit': 'Modifier la note', + 'dayplan.noteTitle': 'Note', + 'dayplan.noteSubtitle': 'Note du jour', + 'dayplan.totalCost': 'Coût total', + 'dayplan.days': 'Jours', + 'dayplan.dayN': 'Jour {n}', + 'dayplan.calculating': 'Calcul en cours…', + 'dayplan.route': 'Itinéraire', + 'dayplan.optimize': 'Optimiser', + 'dayplan.optimized': 'Itinéraire optimisé', + 'dayplan.routeError': "Impossible de calculer l'itinéraire", + 'dayplan.toast.needTwoPlaces': + "Au moins deux lieux nécessaires pour optimiser l'itinéraire", + 'dayplan.toast.routeOptimized': 'Itinéraire optimisé', + 'dayplan.toast.noGeoPlaces': + "Aucun lieu avec des coordonnées trouvé pour le calcul d'itinéraire", + 'dayplan.confirmed': 'Confirmé', + 'dayplan.pendingRes': 'En attente', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Exporter le plan du jour en PDF', + 'dayplan.pdfError': "Échec de l'export PDF", + 'dayplan.cannotReorderTransport': + 'Les réservations avec une heure fixe ne peuvent pas être réorganisées', + 'dayplan.confirmRemoveTimeTitle': "Supprimer l'heure ?", + 'dayplan.confirmRemoveTimeBody': + "Ce lieu a une heure fixe ({time}). Le déplacer supprimera l'heure et permettra un tri libre.", + 'dayplan.confirmRemoveTimeAction': "Supprimer l'heure et déplacer", + 'dayplan.cannotDropOnTimed': + 'Les éléments ne peuvent pas être placés entre des entrées à heure fixe', + 'dayplan.cannotBreakChronology': + "Cela briserait l'ordre chronologique des éléments et réservations planifiés", + 'dayplan.mobile.addPlace': 'Ajouter un lieu', + 'dayplan.mobile.searchPlaces': 'Rechercher des lieux...', + 'dayplan.mobile.allAssigned': 'Tous les lieux attribués', + 'dayplan.mobile.noMatch': 'Aucun résultat', + 'dayplan.mobile.createNew': 'Créer un nouveau lieu', +}; +export default dayplan; diff --git a/shared/src/i18n/fr/externalNotifications.ts b/shared/src/i18n/fr/externalNotifications.ts new file mode 100644 index 00000000..558a9ab5 --- /dev/null +++ b/shared/src/i18n/fr/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const fr: NotificationLocale = { + email: { + footer: + 'Vous recevez cet e-mail car les notifications sont activées dans TREK.', + manage: 'Gérer les préférences', + madeWith: 'Made with', + openTrek: 'Ouvrir TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Invitation à "${p.trip}"`, + body: `${p.actor} a invité ${p.invitee || 'un membre'} au voyage "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nouvelle réservation : ${p.booking}`, + body: `${p.actor} a ajouté une réservation "${p.booking}" (${p.type}) à "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Rappel de voyage : ${p.trip}`, + body: `Votre voyage "${p.trip}" approche !`, + }), + todo_due: (p) => ({ + title: `Tâche à échéance : ${p.todo}`, + body: `"${p.todo}" dans "${p.trip}" est due le ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Invitation Vacay Fusion', + body: `${p.actor} vous invite à fusionner les plans de vacances. Ouvrez TREK pour accepter ou refuser.`, + }), + photos_shared: (p) => ({ + title: `${p.count} photos partagées`, + body: `${p.actor} a partagé ${p.count} photo(s) dans "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nouveau message dans "${p.trip}"`, + body: `${p.actor} : ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Bagages : ${p.category}`, + body: `${p.actor} vous a assigné à la catégorie "${p.category}" dans "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nouvelle version TREK disponible', + body: `TREK ${p.version} est maintenant disponible. Rendez-vous dans le panneau d'administration pour mettre à jour.`, + }), + synology_session_cleared: () => ({ + title: 'Session Synology effacée', + body: 'Votre compte ou URL Synology a changé. Vous avez été déconnecté de Synology Photos.', + }), + }, + passwordReset: { + subject: 'Réinitialisez votre mot de passe', + greeting: 'Bonjour', + body: 'Nous avons reçu une demande de réinitialisation du mot de passe de votre compte TREK. Cliquez sur le bouton ci-dessous pour définir un nouveau mot de passe.', + ctaIntro: 'Réinitialiser le mot de passe', + expiry: 'Ce lien expire dans 60 minutes.', + ignore: + "Si vous n'êtes pas à l'origine de cette demande, ignorez cet e-mail — votre mot de passe ne changera pas.", + }, +}; + +export default fr; diff --git a/shared/src/i18n/fr/files.ts b/shared/src/i18n/fr/files.ts new file mode 100644 index 00000000..ff6fc8ed --- /dev/null +++ b/shared/src/i18n/fr/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Fichiers', + 'files.pageTitle': 'Fichiers et documents', + 'files.subtitle': '{count} fichiers pour {trip}', + 'files.download': 'Télécharger', + 'files.openError': "Impossible d'ouvrir le fichier", + 'files.downloadPdf': 'Télécharger le PDF', + 'files.count': '{count} fichiers', + 'files.countSingular': '1 fichier', + 'files.uploaded': '{count} importés', + 'files.uploadError': "Échec de l'import", + 'files.dropzone': 'Déposez les fichiers ici', + 'files.dropzoneHint': 'ou cliquez pour parcourir', + 'files.allowedTypes': + 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 Mo', + 'files.uploading': 'Importation…', + 'files.filterAll': 'Tous', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Images', + 'files.filterDocs': 'Documents', + 'files.filterCollab': 'Notes Collab', + 'files.sourceCollab': 'Depuis les notes Collab', + 'files.empty': 'Aucun fichier', + 'files.emptyHint': 'Importez des fichiers pour les joindre à votre voyage', + 'files.openTab': 'Ouvrir dans un nouvel onglet', + 'files.confirm.delete': 'Voulez-vous vraiment supprimer ce fichier ?', + 'files.toast.deleted': 'Fichier supprimé', + 'files.toast.deleteError': 'Impossible de supprimer le fichier', + 'files.sourcePlan': 'Plan du jour', + 'files.sourceBooking': 'Réservation', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Joindre', + 'files.pasteHint': + 'Vous pouvez aussi coller des images depuis le presse-papiers (Ctrl+V)', + 'files.trash': 'Corbeille', + 'files.trashEmpty': 'La corbeille est vide', + 'files.emptyTrash': 'Vider la corbeille', + 'files.restore': 'Restaurer', + 'files.star': 'Favori', + 'files.unstar': 'Retirer des favoris', + 'files.assign': 'Assigner', + 'files.assignTitle': 'Assigner le fichier', + 'files.assignPlace': 'Lieu', + 'files.assignBooking': 'Réservation', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Non attribué', + 'files.unlink': 'Supprimer le lien', + 'files.toast.trashed': 'Déplacé dans la corbeille', + 'files.toast.restored': 'Fichier restauré', + 'files.toast.trashEmptied': 'Corbeille vidée', + 'files.toast.assigned': 'Fichier attribué', + 'files.toast.assignError': "Échec de l'assignation", + 'files.toast.restoreError': 'Échec de la restauration', + 'files.confirm.permanentDelete': + 'Supprimer définitivement ce fichier ? Cette action est irréversible.', + 'files.confirm.emptyTrash': + 'Supprimer définitivement tous les fichiers de la corbeille ? Cette action est irréversible.', + 'files.noteLabel': 'Note', + 'files.notePlaceholder': 'Ajouter une note…', +}; +export default files; diff --git a/shared/src/i18n/fr/index.ts b/shared/src/i18n/fr/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/fr/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/fr/inspector.ts b/shared/src/i18n/fr/inspector.ts new file mode 100644 index 00000000..2d6d35ca --- /dev/null +++ b/shared/src/i18n/fr/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Ouvert', + 'inspector.closed': 'Fermé', + 'inspector.openingHours': "Horaires d'ouverture", + 'inspector.showHours': 'Afficher les horaires', + 'inspector.files': 'Fichiers', + 'inspector.filesCount': '{count} fichiers', + 'inspector.removeFromDay': 'Retirer du jour', + 'inspector.remove': 'Supprimer', + 'inspector.addToDay': 'Ajouter au jour', + 'inspector.confirmedRes': 'Réservation confirmée', + 'inspector.pendingRes': 'Réservation en attente', + 'inspector.google': 'Ouvrir dans Google Maps', + 'inspector.website': 'Ouvrir le site web', + 'inspector.addRes': 'Réservation', + 'inspector.editRes': 'Modifier la réservation', + 'inspector.participants': 'Participants', + 'inspector.trackStats': 'Données du parcours', +}; +export default inspector; diff --git a/shared/src/i18n/fr/journey.ts b/shared/src/i18n/fr/journey.ts new file mode 100644 index 00000000..1074e77f --- /dev/null +++ b/shared/src/i18n/fr/journey.ts @@ -0,0 +1,241 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Rechercher des journaux…', + 'journey.search.noResults': 'Aucun journal ne correspond à « {query} »', + 'journey.title': 'Journal de voyage', + 'journey.subtitle': 'Suivez vos voyages en temps réel', + 'journey.new': 'Nouveau journal', + 'journey.create': 'Créer', + 'journey.titlePlaceholder': 'Où allez-vous ?', + 'journey.empty': 'Aucun journal pour le moment', + 'journey.emptyHint': 'Commencez à documenter votre prochain voyage', + 'journey.deleted': 'Journal supprimé', + 'journey.createError': 'Impossible de créer le journal', + 'journey.deleteError': 'Impossible de supprimer le journal', + 'journey.deleteConfirmTitle': 'Supprimer', + 'journey.deleteConfirmMessage': + 'Supprimer « {title} » ? Cette action est irréversible.', + 'journey.deleteConfirmGeneric': 'Êtes-vous sûr de vouloir supprimer ceci ?', + 'journey.notFound': 'Journal introuvable', + 'journey.photos': 'Photos', + 'journey.timelineEmpty': 'Aucune étape pour le moment', + 'journey.timelineEmptyHint': + 'Ajoutez un check-in ou écrivez une entrée de journal pour commencer', + 'journey.status.draft': 'Brouillon', + 'journey.status.active': 'Actif', + 'journey.status.completed': 'Terminé', + 'journey.status.upcoming': 'À venir', + 'journey.status.archived': 'Archivé', + 'journey.checkin.add': 'Check-in', + 'journey.checkin.namePlaceholder': 'Nom du lieu', + 'journey.checkin.notesPlaceholder': 'Notes (facultatif)', + 'journey.checkin.save': 'Enregistrer', + 'journey.checkin.error': "Impossible d'enregistrer le check-in", + 'journey.entry.add': 'Journal', + 'journey.entry.edit': "Modifier l'entrée", + 'journey.entry.titlePlaceholder': 'Titre (facultatif)', + 'journey.entry.bodyPlaceholder': "Que s'est-il passé aujourd'hui ?", + 'journey.entry.save': 'Enregistrer', + 'journey.entry.error': "Impossible d'enregistrer l'entrée", + 'journey.photo.add': 'Photo', + 'journey.photo.uploadError': 'Échec du téléversement', + 'journey.share.share': 'Partager', + 'journey.share.public': 'Public', + 'journey.share.linkCopied': 'Lien public copié', + 'journey.share.disabled': 'Partage public désactivé', + 'journey.editor.titlePlaceholder': 'Donnez un nom à ce moment...', + 'journey.editor.bodyPlaceholder': "Racontez l'histoire de cette journée...", + 'journey.editor.placePlaceholder': 'Lieu (facultatif)', + 'journey.editor.tagsPlaceholder': + 'Tags : pépite cachée, meilleur repas, à revisiter...', + 'journey.visibility.private': 'Privé', + 'journey.visibility.shared': 'Partagé', + 'journey.visibility.public': 'Public', + 'journey.emptyState.title': 'Votre histoire commence ici', + 'journey.emptyState.subtitle': + 'Faites un check-in ou écrivez votre première entrée de journal', + 'journey.frontpage.subtitle': + 'Transformez vos voyages en histoires inoubliables', + 'journey.frontpage.createJourney': 'Créer un journal', + 'journey.frontpage.activeJourney': 'Journal actif', + 'journey.frontpage.allJourneys': 'Tous les journaux', + 'journey.frontpage.journeys': 'journaux', + 'journey.frontpage.createNew': 'Créer un nouveau journal', + 'journey.frontpage.createNewSub': + 'Choisissez des voyages, écrivez des récits, partagez vos aventures', + 'journey.frontpage.live': 'En direct', + 'journey.frontpage.synced': 'Synchronisé', + 'journey.frontpage.continueWriting': 'Continuer à écrire', + 'journey.frontpage.updated': 'Mis à jour {time}', + 'journey.frontpage.suggestionLabel': 'Voyage terminé récemment', + 'journey.frontpage.suggestionText': + 'Transformez {title} en journal de voyage', + 'journey.frontpage.dismiss': 'Ignorer', + 'journey.frontpage.journeyName': 'Nom du journal', + 'journey.frontpage.namePlaceholder': 'ex. Asie du Sud-Est 2026', + 'journey.frontpage.selectTrips': 'Sélectionner des voyages', + 'journey.frontpage.tripsSelected': 'voyages sélectionnés', + 'journey.frontpage.trips': 'voyages', + 'journey.frontpage.placesImported': 'lieux seront importés', + 'journey.frontpage.places': 'lieux', + 'journey.detail.backToJourney': 'Retour au journal', + 'journey.detail.syncedWithTrips': 'Synchronisé avec les voyages', + 'journey.detail.addEntry': 'Ajouter une entrée', + 'journey.detail.newEntry': 'Nouvelle entrée', + 'journey.detail.editEntry': "Modifier l'entrée", + 'journey.detail.noEntries': 'Aucune entrée pour le moment', + 'journey.detail.noEntriesHint': + 'Ajoutez un voyage pour commencer avec des entrées préremplies', + 'journey.detail.noPhotos': 'Aucune photo pour le moment', + 'journey.detail.noPhotosHint': + 'Téléversez des photos dans les entrées ou parcourez votre bibliothèque Immich/Synology', + 'journey.detail.journeyStats': 'Statistiques du journal', + 'journey.detail.syncedTrips': 'Voyages synchronisés', + 'journey.detail.noTripsLinked': 'Aucun voyage lié pour le moment', + 'journey.detail.contributors': 'Contributeurs', + 'journey.detail.readMore': 'Lire la suite', + 'journey.detail.prosCons': 'Pour et contre', + 'journey.detail.photos': 'photos', + 'journey.detail.day': 'Jour {number}', + 'journey.detail.places': 'lieux', + 'journey.stats.days': 'Jours', + 'journey.stats.cities': 'Villes', + 'journey.stats.entries': 'Entrées', + 'journey.stats.photos': 'Photos', + 'journey.stats.places': 'Lieux', + 'journey.skeletons.show': 'Afficher les suggestions', + 'journey.skeletons.hide': 'Masquer les suggestions', + 'journey.verdict.lovedIt': 'Adoré', + 'journey.verdict.couldBeBetter': 'Pourrait être mieux', + 'journey.synced.places': 'lieux', + 'journey.synced.synced': 'synchronisé', + 'journey.editor.discardChangesConfirm': + 'Vous avez des modifications non enregistrées. Les ignorer ?', + 'journey.editor.uploadFailed': 'Échec du téléversement des photos', + 'journey.editor.uploadPhotos': 'Téléverser des photos', + 'journey.editor.uploading': 'Envoi...', + 'journey.editor.uploadingProgress': 'Téléversement {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} sur {total} photos ont échoué — sauvegardez à nouveau pour réessayer', + 'journey.editor.fromGallery': 'Depuis la galerie', + 'journey.editor.allPhotosAdded': 'Toutes les photos ont déjà été ajoutées', + 'journey.editor.writeStory': 'Écrivez votre histoire...', + 'journey.editor.prosCons': 'Pour et contre', + 'journey.editor.pros': 'Pour', + 'journey.editor.cons': 'Contre', + 'journey.editor.proPlaceholder': 'Quelque chose de génial...', + 'journey.editor.conPlaceholder': 'Pas si génial...', + 'journey.editor.addAnother': 'Ajouter un autre', + 'journey.editor.date': 'Date', + 'journey.editor.location': 'Lieu', + 'journey.editor.searchLocation': 'Rechercher un lieu...', + 'journey.editor.mood': 'Humeur', + 'journey.editor.weather': 'Météo', + 'journey.editor.photoFirst': '1er', + 'journey.editor.makeFirst': 'Mettre en 1er', + 'journey.editor.searching': 'Recherche...', + 'journey.mood.amazing': 'Incroyable', + 'journey.mood.good': 'Bien', + 'journey.mood.neutral': 'Neutre', + 'journey.mood.rough': 'Difficile', + 'journey.weather.sunny': 'Ensoleillé', + 'journey.weather.partly': 'Partiellement nuageux', + 'journey.weather.cloudy': 'Nuageux', + 'journey.weather.rainy': 'Pluvieux', + 'journey.weather.stormy': 'Orageux', + 'journey.weather.cold': 'Neigeux', + 'journey.trips.linkTrip': 'Lier un voyage', + 'journey.trips.searchTrip': 'Rechercher un voyage', + 'journey.trips.searchPlaceholder': 'Nom du voyage ou destination...', + 'journey.trips.noTripsAvailable': 'Aucun voyage disponible', + 'journey.trips.link': 'Lier', + 'journey.trips.tripLinked': 'Voyage lié', + 'journey.trips.linkFailed': 'Échec de la liaison du voyage', + 'journey.trips.addTrip': 'Ajouter un voyage', + 'journey.trips.unlinkTrip': 'Délier le voyage', + 'journey.trips.unlinkMessage': + 'Délier « {title} » ? Toutes les entrées et photos synchronisées de ce voyage seront définitivement supprimées. Cette action est irréversible.', + 'journey.trips.unlink': 'Délier', + 'journey.trips.tripUnlinked': 'Voyage délié', + 'journey.trips.unlinkFailed': 'Échec de la suppression du lien', + 'journey.trips.noTripsLinkedSettings': 'Aucun voyage lié', + 'journey.contributors.invite': 'Inviter un contributeur', + 'journey.contributors.searchUser': 'Rechercher un utilisateur', + 'journey.contributors.searchPlaceholder': "Nom d'utilisateur ou e-mail...", + 'journey.contributors.noUsers': 'Aucun utilisateur trouvé', + 'journey.contributors.role': 'Rôle', + 'journey.contributors.added': 'Contributeur ajouté', + 'journey.contributors.addFailed': "Échec de l'ajout du contributeur", + 'journey.share.publicShare': 'Partage public', + 'journey.share.createLink': 'Créer un lien de partage', + 'journey.share.linkCreated': 'Lien de partage créé', + 'journey.share.createFailed': 'Échec de la création du lien', + 'journey.share.copy': 'Copier', + 'journey.share.copied': 'Copié !', + 'journey.share.timeline': 'Chronologie', + 'journey.share.gallery': 'Galerie', + 'journey.share.map': 'Carte', + 'journey.share.removeLink': 'Supprimer le lien de partage', + 'journey.share.linkDeleted': 'Lien de partage supprimé', + 'journey.share.deleteFailed': 'Échec de la suppression', + 'journey.share.updateFailed': 'Échec de la mise à jour', + 'journey.invite.role': 'Rôle', + 'journey.invite.viewer': 'Lecteur', + 'journey.invite.editor': 'Éditeur', + 'journey.invite.invite': 'Inviter', + 'journey.invite.inviting': 'Invitation...', + 'journey.settings.title': 'Paramètres du journal', + 'journey.settings.coverImage': 'Image de couverture', + 'journey.settings.changeCover': 'Changer la couverture', + 'journey.settings.addCover': 'Ajouter une image de couverture', + 'journey.settings.name': 'Nom', + 'journey.settings.subtitle': 'Sous-titre', + 'journey.settings.subtitlePlaceholder': 'ex. Thaïlande, Vietnam et Cambodge', + 'journey.settings.endJourney': 'Archiver le journal', + 'journey.settings.reopenJourney': 'Restaurer le journal', + 'journey.settings.archived': 'Journal archivé', + 'journey.settings.reopened': 'Journal rouvert', + 'journey.settings.endDescription': + "Masque l'indicateur En direct. Vous pouvez rouvrir à tout moment.", + 'journey.settings.delete': 'Supprimer', + 'journey.settings.deleteJourney': 'Supprimer le journal', + 'journey.settings.deleteMessage': + 'Supprimer « {title} » ? Toutes les entrées et photos seront perdues.', + 'journey.settings.saved': 'Paramètres enregistrés', + 'journey.settings.saveFailed': "Échec de l'enregistrement", + 'journey.settings.coverUpdated': 'Couverture mise à jour', + 'journey.settings.coverFailed': 'Échec du téléversement', + 'journey.settings.failedToDelete': 'Échec de la suppression', + 'journey.entries.deleteTitle': "Supprimer l'entrée", + 'journey.photosUploaded': '{count} photos téléversées', + 'journey.photosUploadFailed': + "Certaines photos n'ont pas pu être téléversées", + 'journey.photosAdded': '{count} photos ajoutées', + 'journey.public.notFound': 'Introuvable', + 'journey.public.notFoundMessage': + "Ce journal n'existe pas ou le lien a expiré.", + 'journey.public.readOnly': 'Lecture seule · Journal public', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Partagé via', + 'journey.public.madeWith': 'Créé avec', + 'journey.pdf.journeyBook': 'Carnet de voyage', + 'journey.pdf.madeWith': 'Créé avec TREK', + 'journey.pdf.day': 'Jour', + 'journey.pdf.theEnd': 'Fin', + 'journey.pdf.saveAsPdf': 'Enregistrer en PDF', + 'journey.pdf.pages': 'pages', + 'journey.picker.tripPeriod': 'Période du voyage', + 'journey.picker.dateRange': 'Plage de dates', + 'journey.picker.allPhotos': 'Toutes les photos', + 'journey.picker.albums': 'Albums', + 'journey.picker.selected': 'sélectionnés', + 'journey.picker.addTo': 'Ajouter à', + 'journey.picker.newGallery': 'Nouvelle galerie', + 'journey.picker.selectAll': 'Tout sélectionner', + 'journey.picker.deselectAll': 'Tout désélectionner', + 'journey.picker.noAlbums': 'Aucun album trouvé', + 'journey.picker.selectDate': 'Sélectionner une date', + 'journey.picker.search': 'Rechercher', +}; +export default journey; diff --git a/shared/src/i18n/fr/login.ts b/shared/src/i18n/fr/login.ts new file mode 100644 index 00000000..e6e9d87e --- /dev/null +++ b/shared/src/i18n/fr/login.ts @@ -0,0 +1,102 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Échec de la connexion. Veuillez vérifier vos identifiants.', + 'login.tagline': 'Vos voyages.\nVotre organisation.', + 'login.description': + 'Planifiez vos voyages en collaboration avec des cartes interactives, des budgets et la synchronisation en temps réel.', + 'login.features.maps': 'Cartes interactives', + 'login.features.mapsDesc': 'Google Places, itinéraires et regroupement', + 'login.features.realtime': 'Synchronisation en temps réel', + 'login.features.realtimeDesc': 'Planifiez ensemble via WebSocket', + 'login.features.budget': 'Suivi du budget', + 'login.features.budgetDesc': 'Catégories, graphiques et coûts par personne', + 'login.features.collab': 'Collaboration', + 'login.features.collabDesc': 'Multi-utilisateurs avec voyages partagés', + 'login.features.packing': 'Listes de bagages', + 'login.features.packingDesc': 'Catégories, progression et suggestions', + 'login.features.bookings': 'Réservations', + 'login.features.bookingsDesc': 'Vols, hôtels, restaurants et plus', + 'login.features.files': 'Documents', + 'login.features.filesDesc': 'Importez et gérez vos documents', + 'login.features.routes': 'Itinéraires intelligents', + 'login.features.routesDesc': 'Optimisation automatique et export Google Maps', + 'login.selfHosted': + 'Auto-hébergé · Open Source · Vos données restent les vôtres', + 'login.title': 'Connexion', + 'login.subtitle': 'Bon retour', + 'login.signingIn': 'Connexion en cours…', + 'login.signIn': 'Se connecter', + 'login.createAdmin': 'Créer un compte administrateur', + 'login.createAdminHint': + 'Configurez le premier compte administrateur pour TREK.', + 'login.setNewPassword': 'Définir un nouveau mot de passe', + 'login.setNewPasswordHint': + 'Vous devez changer votre mot de passe avant de continuer.', + 'login.createAccount': 'Créer un compte', + 'login.createAccountHint': 'Créez un nouveau compte.', + 'login.creating': 'Création…', + 'login.noAccount': 'Pas encore de compte ?', + 'login.hasAccount': 'Vous avez déjà un compte ?', + 'login.register': "S'inscrire", + 'login.emailPlaceholder': 'votre@email.com', + 'login.username': "Nom d'utilisateur", + 'login.oidc.registrationDisabled': + 'Les inscriptions sont désactivées. Contactez votre administrateur.', + 'login.oidc.noEmail': 'Aucun e-mail reçu du fournisseur.', + 'login.mfaTitle': 'Authentification à deux facteurs', + 'login.mfaSubtitle': + "Entrez le code à 6 chiffres de votre application d'authentification.", + 'login.mfaCodeLabel': 'Code de vérification', + 'login.mfaCodeRequired': + "Entrez le code de votre application d'authentification.", + 'login.mfaHint': + 'Ouvrez Google Authenticator, Authy ou une autre application TOTP.', + 'login.mfaBack': '← Retour à la connexion', + 'login.mfaVerify': 'Vérifier', + 'login.invalidInviteLink': "Lien d'invitation invalide ou expiré", + 'login.oidcFailed': 'Échec de connexion OIDC', + 'login.usernameRequired': "Le nom d'utilisateur est obligatoire", + 'login.passwordMinLength': + 'Le mot de passe doit comporter au moins 8 caractères', + 'login.forgotPassword': 'Mot de passe oublié ?', + 'login.forgotPasswordTitle': 'Réinitialiser votre mot de passe', + 'login.forgotPasswordBody': + "Entrez l'adresse e-mail associée à votre compte. Si un compte existe, nous enverrons un lien de réinitialisation.", + 'login.forgotPasswordSubmit': 'Envoyer le lien', + 'login.forgotPasswordSentTitle': 'Vérifiez vos e-mails', + 'login.forgotPasswordSentBody': + 'Si un compte existe pour cette adresse, un lien de réinitialisation est en route. Il expire dans 60 minutes.', + 'login.forgotPasswordSmtpHintOff': + "Remarque : votre administrateur n'a pas configuré SMTP. Le lien de réinitialisation sera écrit dans la console du serveur au lieu d'être envoyé par e-mail.", + 'login.backToLogin': 'Retour à la connexion', + 'login.newPassword': 'Nouveau mot de passe', + 'login.confirmPassword': 'Confirmer le nouveau mot de passe', + 'login.passwordsDontMatch': 'Les mots de passe ne correspondent pas', + 'login.mfaCode': 'Code 2FA', + 'login.resetPasswordTitle': 'Définir un nouveau mot de passe', + 'login.resetPasswordBody': + "Choisissez un mot de passe fort que vous n'avez pas encore utilisé ici. 8 caractères minimum.", + 'login.resetPasswordMfaBody': + 'Entrez votre code 2FA ou un code de secours pour finaliser la réinitialisation.', + 'login.resetPasswordSubmit': 'Réinitialiser', + 'login.resetPasswordVerify': 'Vérifier et réinitialiser', + 'login.resetPasswordSuccessTitle': 'Mot de passe mis à jour', + 'login.resetPasswordSuccessBody': + 'Vous pouvez maintenant vous connecter avec votre nouveau mot de passe.', + 'login.resetPasswordInvalidLink': 'Lien de réinitialisation invalide', + 'login.resetPasswordInvalidLinkBody': + 'Ce lien est manquant ou invalide. Demandez-en un nouveau pour continuer.', + 'login.resetPasswordFailed': + 'Échec de la réinitialisation. Le lien a peut-être expiré.', + 'login.oidc.tokenFailed': "L'authentification a échoué.", + 'login.oidc.invalidState': 'Session invalide. Veuillez réessayer.', + 'login.demoFailed': 'Échec de la connexion démo', + 'login.oidcSignIn': 'Se connecter avec {name}', + 'login.oidcOnly': + "L'authentification par mot de passe est désactivée. Veuillez vous connecter via votre fournisseur SSO.", + 'login.oidcLoggedOut': + 'Vous avez été déconnecté. Reconnectez-vous via votre fournisseur SSO.', + 'login.demoHint': 'Essayez la démo — aucune inscription nécessaire', +}; +export default login; diff --git a/shared/src/i18n/fr/map.ts b/shared/src/i18n/fr/map.ts new file mode 100644 index 00000000..5c7a3560 --- /dev/null +++ b/shared/src/i18n/fr/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Connexions', + 'map.showConnections': 'Afficher les itinéraires', + 'map.hideConnections': 'Masquer les itinéraires', +}; +export default map; diff --git a/shared/src/i18n/fr/members.ts b/shared/src/i18n/fr/members.ts new file mode 100644 index 00000000..46f1078c --- /dev/null +++ b/shared/src/i18n/fr/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Partager le voyage', + 'members.inviteUser': 'Inviter un utilisateur', + 'members.selectUser': 'Sélectionner un utilisateur…', + 'members.invite': 'Inviter', + 'members.allHaveAccess': 'Tous les utilisateurs ont déjà accès.', + 'members.access': 'Accès', + 'members.person': 'personne', + 'members.persons': 'personnes', + 'members.you': 'vous', + 'members.owner': 'Propriétaire', + 'members.leaveTrip': 'Quitter le voyage', + 'members.removeAccess': "Retirer l'accès", + 'members.confirmLeave': "Quitter le voyage ? Vous perdrez l'accès.", + 'members.confirmRemove': "Retirer l'accès à cet utilisateur ?", + 'members.loadError': 'Impossible de charger les membres', + 'members.added': 'ajouté', + 'members.addError': "Échec de l'ajout", + 'members.removed': 'Membre retiré', + 'members.removeError': 'Échec de la suppression', +}; +export default members; diff --git a/shared/src/i18n/fr/memories.ts b/shared/src/i18n/fr/memories.ts new file mode 100644 index 00000000..8659cab5 --- /dev/null +++ b/shared/src/i18n/fr/memories.ts @@ -0,0 +1,84 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Photos', + 'memories.notConnected': 'Immich non connecté', + 'memories.notConnectedHint': + 'Connectez votre instance Immich dans les paramètres pour voir vos photos de voyage ici.', + 'memories.notConnectedMultipleHint': + 'Connectez un de ces fournisseurs de photos : {provider_names} dans les Paramètres pour pouvoir ajouter des photos à ce voyage.', + 'memories.noDates': + 'Ajoutez des dates à votre voyage pour charger les photos.', + 'memories.noPhotos': 'Aucune photo trouvée', + 'memories.noPhotosHint': + 'Aucune photo trouvée dans Immich pour la période de ce voyage.', + 'memories.photosFound': 'photos', + 'memories.fromOthers': "d'autres", + 'memories.sharePhotos': 'Partager les photos', + 'memories.sharing': 'Partagé', + 'memories.reviewTitle': 'Vérifier vos photos', + 'memories.reviewHint': 'Cliquez sur les photos pour les exclure du partage.', + 'memories.shareCount': 'Partager {count} photos', + 'memories.providerUrl': 'URL du serveur', + 'memories.providerApiKey': 'Clé API', + 'memories.providerUsername': "Nom d'utilisateur", + 'memories.providerPassword': 'Mot de passe', + 'memories.providerOTP': 'Code MFA (si activé)', + 'memories.skipSSLVerification': 'Ignorer la vérification du certificat SSL', + 'memories.immichAutoUpload': + 'Répliquer les photos du journey vers Immich au téléversement', + 'memories.providerUrlHintSynology': + "Incluez le chemin de l'application Photos dans l'URL, ex. https://nas:5001/photo", + 'memories.testConnection': 'Tester la connexion', + 'memories.testShort': 'Tester', + 'memories.testFirst': 'Testez la connexion avant de sauvegarder', + 'memories.connected': 'Connecté', + 'memories.disconnected': 'Non connecté', + 'memories.connectionSuccess': 'Connecté à Immich', + 'memories.connectionError': 'Impossible de se connecter à Immich', + 'memories.saved': 'Paramètres {provider_name} enregistrés', + 'memories.providerDisconnectedBanner': + 'Votre connexion {provider_name} est perdue. Reconnectez-vous dans les Paramètres pour voir les photos.', + 'memories.saveError': + "Impossible d'enregistrer les paramètres de {provider_name}", + 'memories.saveRouteNotConfigured': + "La route de sauvegarde n'est pas configurée pour ce fournisseur", + 'memories.testRouteNotConfigured': + "La route de test n'est pas configurée pour ce fournisseur", + 'memories.fillRequiredFields': + 'Veuillez remplir tous les champs obligatoires', + 'memories.oldest': 'Plus anciennes', + 'memories.newest': 'Plus récentes', + 'memories.allLocations': 'Tous les lieux', + 'memories.addPhotos': 'Ajouter des photos', + 'memories.linkAlbum': 'Lier un album', + 'memories.selectAlbum': 'Choisir un album Immich', + 'memories.selectAlbumMultiple': 'Sélectionner un album', + 'memories.noAlbums': 'Aucun album trouvé', + 'memories.syncAlbum': 'Synchroniser', + 'memories.unlinkAlbum': 'Délier', + 'memories.photos': 'photos', + 'memories.selectPhotos': 'Sélectionner des photos depuis Immich', + 'memories.selectPhotosMultiple': 'Sélectionner des photos', + 'memories.selectHint': 'Appuyez sur les photos pour les sélectionner.', + 'memories.selected': 'sélectionné(s)', + 'memories.addSelected': 'Ajouter {count} photos', + 'memories.alreadyAdded': 'Ajouté', + 'memories.private': 'Privé', + 'memories.stopSharing': 'Arrêter le partage', + 'memories.tripDates': 'Dates du voyage', + 'memories.allPhotos': 'Toutes les photos', + 'memories.confirmShareTitle': 'Partager avec les membres du voyage ?', + 'memories.confirmShareHint': + '{count} photos seront visibles par tous les membres de ce voyage. Vous pourrez rendre des photos individuelles privées plus tard.', + 'memories.confirmShareButton': 'Partager les photos', + 'memories.error.loadAlbums': 'Impossible de charger les albums', + 'memories.error.linkAlbum': "Impossible de lier l'album", + 'memories.error.unlinkAlbum': "Impossible de dissocier l'album", + 'memories.error.syncAlbum': "Impossible de synchroniser l'album", + 'memories.error.loadPhotos': 'Impossible de charger les photos', + 'memories.error.addPhotos': "Impossible d'ajouter les photos", + 'memories.error.removePhoto': 'Impossible de supprimer la photo', + 'memories.error.toggleSharing': 'Impossible de mettre à jour le partage', +}; +export default memories; diff --git a/shared/src/i18n/fr/nav.ts b/shared/src/i18n/fr/nav.ts new file mode 100644 index 00000000..f41a37b4 --- /dev/null +++ b/shared/src/i18n/fr/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Voyage', + 'nav.share': 'Partager', + 'nav.settings': 'Paramètres', + 'nav.admin': 'Admin', + 'nav.logout': 'Déconnexion', + 'nav.lightMode': 'Mode clair', + 'nav.darkMode': 'Mode sombre', + 'nav.autoMode': 'Mode auto', + 'nav.administrator': 'Administrateur', + 'nav.myTrips': 'Mes voyages', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Paramètres', + 'nav.bottomAdmin': 'Administration', + 'nav.bottomLogout': 'Déconnexion', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/fr/notif.ts b/shared/src/i18n/fr/notif.ts new file mode 100644 index 00000000..9793c453 --- /dev/null +++ b/shared/src/i18n/fr/notif.ts @@ -0,0 +1,45 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notification', + 'notif.test.simple.text': 'Ceci est une simple notification de test.', + 'notif.test.boolean.text': 'Acceptez-vous cette notification de test ?', + 'notif.test.navigate.text': + 'Cliquez ci-dessous pour accéder au tableau de bord.', + 'notif.trip_invite.title': 'Invitation au voyage', + 'notif.trip_invite.text': '{actor} vous a invité à {trip}', + 'notif.booking_change.title': 'Réservation mise à jour', + 'notif.booking_change.text': + '{actor} a mis à jour une réservation dans {trip}', + 'notif.trip_reminder.title': 'Rappel de voyage', + 'notif.trip_reminder.text': 'Votre voyage {trip} approche !', + 'notif.todo_due.title': 'Tâche à échéance', + 'notif.todo_due.text': '{todo} dans {trip} est due le {due}', + 'notif.vacay_invite.title': 'Invitation Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} vous invite à fusionner les plans de vacances', + 'notif.photos_shared.title': 'Photos partagées', + 'notif.photos_shared.text': '{actor} a partagé {count} photo(s) dans {trip}', + 'notif.collab_message.title': 'Nouveau message', + 'notif.collab_message.text': '{actor} a envoyé un message dans {trip}', + 'notif.packing_tagged.title': 'Affectation bagages', + 'notif.packing_tagged.text': + '{actor} vous a assigné à {category} dans {trip}', + 'notif.version_available.title': 'Nouvelle version disponible', + 'notif.version_available.text': 'TREK {version} est maintenant disponible', + 'notif.action.view_trip': 'Voir le voyage', + 'notif.action.view_collab': 'Voir les messages', + 'notif.action.view_packing': 'Voir les bagages', + 'notif.action.view_photos': 'Voir les photos', + 'notif.action.view_vacay': 'Voir Vacay', + 'notif.action.view_admin': "Aller à l'admin", + 'notif.action.view': 'Voir', + 'notif.action.accept': 'Accepter', + 'notif.action.decline': 'Refuser', + 'notif.generic.title': 'Notification', + 'notif.generic.text': 'Vous avez une nouvelle notification', + 'notif.dev.unknown_event.title': '[DEV] Événement inconnu', + 'notif.dev.unknown_event.text': + 'Le type d\'événement "{event}" n\'est pas enregistré dans EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/fr/notifications.ts b/shared/src/i18n/fr/notifications.ts new file mode 100644 index 00000000..fec4b096 --- /dev/null +++ b/shared/src/i18n/fr/notifications.ts @@ -0,0 +1,39 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notifications', + 'notifications.markAllRead': 'Tout marquer comme lu', + 'notifications.deleteAll': 'Tout supprimer', + 'notifications.showAll': 'Voir toutes les notifications', + 'notifications.empty': 'Aucune notification', + 'notifications.emptyDescription': 'Vous êtes à jour !', + 'notifications.all': 'Toutes', + 'notifications.unreadOnly': 'Non lues', + 'notifications.markRead': 'Marquer comme lu', + 'notifications.markUnread': 'Marquer comme non lu', + 'notifications.delete': 'Supprimer', + 'notifications.system': 'Système', + 'notifications.synologySessionCleared.title': 'Synology Photos déconnecté', + 'notifications.synologySessionCleared.text': + 'Votre serveur ou compte a changé — allez dans Paramètres pour tester à nouveau votre connexion.', + 'notifications.test.title': 'Notification test de {actor}', + 'notifications.test.text': 'Ceci est une simple notification de test.', + 'notifications.test.booleanTitle': '{actor} demande votre approbation', + 'notifications.test.booleanText': 'Notification de test booléenne.', + 'notifications.test.accept': 'Approuver', + 'notifications.test.decline': 'Refuser', + 'notifications.test.navigateTitle': 'Allez voir quelque chose', + 'notifications.test.navigateText': 'Notification de test de navigation.', + 'notifications.test.goThere': 'Y aller', + 'notifications.test.adminTitle': 'Diffusion admin', + 'notifications.test.adminText': + '{actor} a envoyé une notification de test à tous les admins.', + 'notifications.test.tripTitle': '{actor} a publié dans votre voyage', + 'notifications.test.tripText': + 'Notification de test pour le voyage "{trip}".', + 'notifications.versionAvailable.title': 'Mise à jour disponible', + 'notifications.versionAvailable.text': + 'TREK {version} est maintenant disponible.', + 'notifications.versionAvailable.button': 'Voir les détails', +}; +export default notifications; diff --git a/shared/src/i18n/fr/oauth.ts b/shared/src/i18n/fr/oauth.ts new file mode 100644 index 00000000..1c81d941 --- /dev/null +++ b/shared/src/i18n/fr/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Voyages', + 'oauth.scope.group.places': 'Lieux', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Bagages', + 'oauth.scope.group.todos': 'Tâches', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Réservations', + 'oauth.scope.group.collab': 'Collaboration', + 'oauth.scope.group.notifications': 'Notifications', + 'oauth.scope.group.vacay': 'Congés', + 'oauth.scope.group.geo': 'Géo', + 'oauth.scope.group.weather': 'Météo', + 'oauth.scope.group.journey': 'Journal de voyage', + 'oauth.scope.trips:read.label': 'Voir les voyages et itinéraires', + 'oauth.scope.trips:read.description': + 'Lire les voyages, jours, notes et membres', + 'oauth.scope.trips:write.label': 'Modifier les voyages et itinéraires', + 'oauth.scope.trips:write.description': + 'Créer et mettre à jour les voyages, jours, notes et gérer les membres', + 'oauth.scope.trips:delete.label': 'Supprimer des voyages', + 'oauth.scope.trips:delete.description': + 'Supprimer définitivement des voyages entiers — cette action est irréversible', + 'oauth.scope.trips:share.label': 'Gérer les liens de partage', + 'oauth.scope.trips:share.description': + 'Créer, modifier et révoquer des liens de partage publics', + 'oauth.scope.places:read.label': 'Voir les lieux et données cartographiques', + 'oauth.scope.places:read.description': + 'Lire les lieux, affectations de jours, étiquettes et catégories', + 'oauth.scope.places:write.label': 'Gérer les lieux', + 'oauth.scope.places:write.description': + 'Créer, modifier et supprimer des lieux, affectations et étiquettes', + 'oauth.scope.atlas:read.label': "Voir l'Atlas", + 'oauth.scope.atlas:read.description': + 'Lire les pays visités, régions et liste de souhaits', + 'oauth.scope.atlas:write.label': "Gérer l'Atlas", + 'oauth.scope.atlas:write.description': + 'Marquer des pays et régions visités, gérer la liste de souhaits', + 'oauth.scope.packing:read.label': 'Voir les listes de bagages', + 'oauth.scope.packing:read.description': + 'Lire les articles, sacs et assignations de catégories', + 'oauth.scope.packing:write.label': 'Gérer les listes de bagages', + 'oauth.scope.packing:write.description': + 'Ajouter, modifier, supprimer, cocher et réordonner les articles et sacs', + 'oauth.scope.todos:read.label': 'Voir les listes de tâches', + 'oauth.scope.todos:read.description': + 'Lire les tâches et assignations de catégories', + 'oauth.scope.todos:write.label': 'Gérer les listes de tâches', + 'oauth.scope.todos:write.description': + 'Créer, modifier, cocher, supprimer et réordonner les tâches', + 'oauth.scope.budget:read.label': 'Voir le budget', + 'oauth.scope.budget:read.description': + 'Lire les dépenses et la répartition du budget', + 'oauth.scope.budget:write.label': 'Gérer le budget', + 'oauth.scope.budget:write.description': + 'Créer, modifier et supprimer des dépenses', + 'oauth.scope.reservations:read.label': 'Voir les réservations', + 'oauth.scope.reservations:read.description': + "Lire les réservations et détails d'hébergement", + 'oauth.scope.reservations:write.label': 'Gérer les réservations', + 'oauth.scope.reservations:write.description': + 'Créer, modifier, supprimer et réordonner les réservations', + 'oauth.scope.collab:read.label': 'Voir la collaboration', + 'oauth.scope.collab:read.description': + 'Lire les notes, sondages et messages collaboratifs', + 'oauth.scope.collab:write.label': 'Gérer la collaboration', + 'oauth.scope.collab:write.description': + 'Créer, modifier et supprimer des notes, sondages et messages', + 'oauth.scope.notifications:read.label': 'Voir les notifications', + 'oauth.scope.notifications:read.description': + 'Lire les notifications et le nombre de non-lus', + 'oauth.scope.notifications:write.label': 'Gérer les notifications', + 'oauth.scope.notifications:write.description': + 'Marquer les notifications comme lues et y répondre', + 'oauth.scope.vacay:read.label': 'Voir les plans de congés', + 'oauth.scope.vacay:read.description': + 'Lire les données, entrées et statistiques de congés', + 'oauth.scope.vacay:write.label': 'Gérer les plans de congés', + 'oauth.scope.vacay:write.description': + "Créer et gérer les entrées de congés, jours fériés et plans d'équipe", + 'oauth.scope.geo:read.label': 'Cartes et géocodage', + 'oauth.scope.geo:read.description': + 'Chercher des lieux, résoudre des URL cartographiques et géocoder des coordonnées', + 'oauth.scope.weather:read.label': 'Prévisions météo', + 'oauth.scope.weather:read.description': + 'Obtenir les prévisions météo pour les lieux et dates de voyage', + 'oauth.scope.journey:read.label': 'Voir les journaux de voyage', + 'oauth.scope.journey:read.description': + 'Lire les journaux de voyage, les entrées et la liste des contributeurs', + 'oauth.scope.journey:write.label': 'Gérer les journaux de voyage', + 'oauth.scope.journey:write.description': + 'Créer, modifier et supprimer les journaux de voyage et leurs entrées', + 'oauth.scope.journey:share.label': 'Gérer les liens de journaux de voyage', + 'oauth.scope.journey:share.description': + 'Créer, modifier et révoquer des liens de partage publics pour les journaux de voyage', +}; +export default oauth; diff --git a/shared/src/i18n/fr/packing.ts b/shared/src/i18n/fr/packing.ts new file mode 100644 index 00000000..dded6afe --- /dev/null +++ b/shared/src/i18n/fr/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Liste de bagages', + 'packing.empty': 'La liste de bagages est vide', + 'packing.import': 'Importer', + 'packing.importTitle': 'Importer la liste', + 'packing.importHint': + 'Un élément par ligne. Catégorie et quantité optionnelles séparées par virgule, point-virgule ou tabulation : Nom, Catégorie, Quantité', + 'packing.importPlaceholder': + 'Brosse à dents\nCrème solaire, Hygiène\nT-Shirts, Vêtements, 5\nPasseport, Documents', + 'packing.importCsv': 'Charger CSV/TXT', + 'packing.importAction': 'Importer {count}', + 'packing.importSuccess': '{count} éléments importés', + 'packing.importError': "Échec de l'import", + 'packing.importEmpty': 'Aucun élément à importer', + 'packing.progress': '{packed} sur {total} emballés ({percent} %)', + 'packing.clearChecked': 'Supprimer {count} cochés', + 'packing.clearCheckedShort': 'Supprimer {count}', + 'packing.suggestions': 'Suggestions', + 'packing.suggestionsTitle': 'Ajouter des suggestions', + 'packing.allSuggested': 'Toutes les suggestions ajoutées', + 'packing.allPacked': 'Tout est emballé !', + 'packing.addPlaceholder': 'Ajouter un nouvel article…', + 'packing.categoryPlaceholder': 'Catégorie…', + 'packing.filterAll': 'Tous', + 'packing.filterOpen': 'À faire', + 'packing.filterDone': 'Fait', + 'packing.emptyTitle': 'La liste de bagages est vide', + 'packing.emptyHint': 'Ajoutez des articles ou utilisez les suggestions', + 'packing.emptyFiltered': 'Aucun article ne correspond à ce filtre', + 'packing.menuRename': 'Renommer', + 'packing.menuCheckAll': 'Tout cocher', + 'packing.menuUncheckAll': 'Tout décocher', + 'packing.menuDeleteCat': 'Supprimer la catégorie', + 'packing.addItem': 'Ajouter un article', + 'packing.addItemPlaceholder': "Nom de l'article...", + 'packing.addCategory': 'Ajouter une catégorie', + 'packing.newCategoryPlaceholder': 'Nom de catégorie (ex. Vêtements)', + 'packing.applyTemplate': 'Appliquer un modèle', + 'packing.template': 'Modèle', + 'packing.templateApplied': '{count} articles ajoutés depuis le modèle', + 'packing.templateError': "Erreur lors de l'application du modèle", + 'packing.saveAsTemplate': 'Enregistrer comme modèle', + 'packing.templateName': 'Nom du modèle', + 'packing.templateSaved': 'Liste de voyage enregistrée comme modèle', + 'packing.noMembers': 'Aucun membre', + 'packing.bags': 'Bagages', + 'packing.noBag': 'Non assigné', + 'packing.totalWeight': 'Poids total', + 'packing.bagName': 'Nom...', + 'packing.addBag': 'Ajouter un bagage', + 'packing.changeCategory': 'Changer de catégorie', + 'packing.confirm.clearChecked': + 'Voulez-vous vraiment supprimer {count} articles cochés ?', + 'packing.confirm.deleteCat': + 'Voulez-vous vraiment supprimer la catégorie « {name} » avec {count} articles ?', + 'packing.defaultCategory': 'Autre', + 'packing.toast.saveError': "Échec de l'enregistrement", + 'packing.toast.deleteError': 'Échec de la suppression', + 'packing.toast.renameError': 'Échec du renommage', + 'packing.toast.addError': "Échec de l'ajout", + 'packing.suggestions.items': [ + { + name: 'Passeport', + category: 'Documents', + }, + { + name: "Carte d'identité", + category: 'Documents', + }, + { + name: 'Assurance voyage', + category: 'Documents', + }, + { + name: "Billets d'avion", + category: 'Documents', + }, + { + name: 'Carte bancaire', + category: 'Finances', + }, + { + name: 'Espèces', + category: 'Finances', + }, + { + name: 'Visa', + category: 'Documents', + }, + { + name: 'T-shirts', + category: 'Vêtements', + }, + { + name: 'Pantalons', + category: 'Vêtements', + }, + { + name: 'Sous-vêtements', + category: 'Vêtements', + }, + { + name: 'Chaussettes', + category: 'Vêtements', + }, + { + name: 'Veste', + category: 'Vêtements', + }, + { + name: 'Pyjama', + category: 'Vêtements', + }, + { + name: 'Maillot de bain', + category: 'Vêtements', + }, + { + name: 'Imperméable', + category: 'Vêtements', + }, + { + name: 'Chaussures confortables', + category: 'Vêtements', + }, + { + name: 'Brosse à dents', + category: 'Hygiène', + }, + { + name: 'Dentifrice', + category: 'Hygiène', + }, + { + name: 'Shampooing', + category: 'Hygiène', + }, + { + name: 'Déodorant', + category: 'Hygiène', + }, + { + name: 'Crème solaire', + category: 'Hygiène', + }, + { + name: 'Rasoir', + category: 'Hygiène', + }, + { + name: 'Chargeur', + category: 'Électronique', + }, + { + name: 'Batterie externe', + category: 'Électronique', + }, + { + name: 'Écouteurs', + category: 'Électronique', + }, + { + name: 'Adaptateur de voyage', + category: 'Électronique', + }, + { + name: 'Appareil photo', + category: 'Électronique', + }, + { + name: 'Antidouleurs', + category: 'Santé', + }, + { + name: 'Pansements', + category: 'Santé', + }, + { + name: 'Désinfectant', + category: 'Santé', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/fr/pdf.ts b/shared/src/i18n/fr/pdf.ts new file mode 100644 index 00000000..c4a493ee --- /dev/null +++ b/shared/src/i18n/fr/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Plan de voyage', + 'pdf.planned': 'Planifié', + 'pdf.costLabel': 'Coût EUR', + 'pdf.preview': 'Aperçu PDF', + 'pdf.saveAsPdf': 'Enregistrer en PDF', +}; +export default pdf; diff --git a/shared/src/i18n/fr/perm.ts b/shared/src/i18n/fr/perm.ts new file mode 100644 index 00000000..cff49329 --- /dev/null +++ b/shared/src/i18n/fr/perm.ts @@ -0,0 +1,65 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Paramètres des permissions', + 'perm.subtitle': + "Contrôlez qui peut effectuer des actions dans l'application", + 'perm.saved': 'Paramètres des permissions enregistrés', + 'perm.resetDefaults': 'Réinitialiser par défaut', + 'perm.customized': 'personnalisé', + 'perm.level.admin': 'Administrateur uniquement', + 'perm.level.tripOwner': 'Propriétaire du voyage', + 'perm.level.tripMember': 'Membres du voyage', + 'perm.level.everybody': 'Tout le monde', + 'perm.cat.trip': 'Gestion des voyages', + 'perm.cat.members': 'Gestion des membres', + 'perm.cat.files': 'Fichiers', + 'perm.cat.content': 'Contenu et planning', + 'perm.cat.extras': 'Budget, bagages et collaboration', + 'perm.action.trip_create': 'Créer des voyages', + 'perm.action.trip_edit': 'Modifier les détails du voyage', + 'perm.action.trip_delete': 'Supprimer des voyages', + 'perm.action.trip_archive': 'Archiver / désarchiver des voyages', + 'perm.action.trip_cover_upload': "Télécharger l'image de couverture", + 'perm.action.member_manage': 'Ajouter / supprimer des membres', + 'perm.action.file_upload': 'Télécharger des fichiers', + 'perm.action.file_edit': 'Modifier les métadonnées des fichiers', + 'perm.action.file_delete': 'Supprimer des fichiers', + 'perm.action.place_edit': 'Ajouter / modifier / supprimer des lieux', + 'perm.action.day_edit': 'Modifier les jours, notes et affectations', + 'perm.action.reservation_edit': 'Gérer les réservations', + 'perm.action.budget_edit': 'Gérer le budget', + 'perm.action.packing_edit': 'Gérer les listes de bagages', + 'perm.action.collab_edit': 'Collaboration (notes, sondages, chat)', + 'perm.action.share_manage': 'Gérer les liens de partage', + 'perm.actionHint.trip_create': 'Qui peut créer de nouveaux voyages', + 'perm.actionHint.trip_edit': + 'Qui peut modifier le nom, les dates, la description et la devise du voyage', + 'perm.actionHint.trip_delete': 'Qui peut supprimer définitivement un voyage', + 'perm.actionHint.trip_archive': 'Qui peut archiver ou désarchiver un voyage', + 'perm.actionHint.trip_cover_upload': + "Qui peut télécharger ou modifier l'image de couverture", + 'perm.actionHint.member_manage': + 'Qui peut inviter ou supprimer des membres du voyage', + 'perm.actionHint.file_upload': + 'Qui peut télécharger des fichiers vers un voyage', + 'perm.actionHint.file_edit': + 'Qui peut modifier les descriptions et liens des fichiers', + 'perm.actionHint.file_delete': + 'Qui peut déplacer des fichiers vers la corbeille ou les supprimer définitivement', + 'perm.actionHint.place_edit': + 'Qui peut ajouter, modifier ou supprimer des lieux', + 'perm.actionHint.day_edit': + 'Qui peut modifier les jours, notes de jours et affectations de lieux', + 'perm.actionHint.reservation_edit': + 'Qui peut créer, modifier ou supprimer des réservations', + 'perm.actionHint.budget_edit': + 'Qui peut créer, modifier ou supprimer des éléments de budget', + 'perm.actionHint.packing_edit': + 'Qui peut gérer les articles de bagages et les sacs', + 'perm.actionHint.collab_edit': + 'Qui peut créer des notes, des sondages et envoyer des messages', + 'perm.actionHint.share_manage': + 'Qui peut créer ou supprimer des liens de partage publics', +}; +export default perm; diff --git a/shared/src/i18n/fr/photos.ts b/shared/src/i18n/fr/photos.ts new file mode 100644 index 00000000..eca8367c --- /dev/null +++ b/shared/src/i18n/fr/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Photos', + 'photos.subtitle': '{count} photos pour {trip}', + 'photos.dropHere': 'Déposez des photos ici...', + 'photos.dropHereActive': 'Déposez des photos ici', + 'photos.captionForAll': 'Légende (pour tous)', + 'photos.captionPlaceholder': 'Légende optionnelle...', + 'photos.addCaption': 'Ajouter une légende...', + 'photos.allDays': 'Tous les jours', + 'photos.noPhotos': 'Aucune photo', + 'photos.uploadHint': 'Importez vos photos de voyage', + 'photos.clickToSelect': 'ou cliquez pour sélectionner', + 'photos.linkPlace': 'Lier au lieu', + 'photos.noPlace': 'Aucun lieu', + 'photos.uploadN': '{n} photo(s) importée(s)', + 'photos.linkDay': 'Lier le jour', + 'photos.noDay': 'Aucun jour', + 'photos.dayLabel': 'Jour {number}', + 'photos.photoSelected': 'Photo sélectionnée', + 'photos.photosSelected': 'Photos sélectionnées', + 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 Mo · jusqu'à 30 photos", +}; +export default photos; diff --git a/shared/src/i18n/fr/places.ts b/shared/src/i18n/fr/places.ts new file mode 100644 index 00000000..8be11565 --- /dev/null +++ b/shared/src/i18n/fr/places.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Ajouter un lieu/activité', + 'places.importFile': 'Importer un fichier', + 'places.sidebarDrop': 'Déposer pour importer', + 'places.importFileHint': + 'Importez des fichiers .gpx, .kml ou .kmz depuis des outils comme Google My Maps, Google Earth ou un traceur GPS.', + 'places.importFileDropHere': + 'Cliquez pour sélectionner un fichier ou glissez-déposez ici', + 'places.importFileDropActive': 'Déposez le fichier pour le sélectionner', + 'places.importFileUnsupported': + 'Type de fichier non pris en charge. Utilisez .gpx, .kml ou .kmz.', + 'places.importFileTooLarge': + 'Le fichier est trop volumineux. La taille maximale est de {maxMb} MB.', + 'places.importFileError': 'Importation échouée', + 'places.importAllSkipped': 'Tous les lieux étaient déjà dans le voyage.', + 'places.gpxImported': '{count} lieux importés depuis GPX', + 'places.gpxImportTypes': 'Que voulez-vous importer?', + 'places.gpxImportWaypoints': 'Points de passage', + 'places.gpxImportRoutes': 'Itinéraires', + 'places.gpxImportTracks': 'Traces (avec géométrie)', + 'places.gpxImportNoneSelected': 'Sélectionnez au moins un type à importer.', + 'places.kmlImportTypes': 'Que souhaitez-vous importer ?', + 'places.kmlImportPoints': 'Points (Placemarks)', + 'places.kmlImportPaths': 'Chemins (LineStrings)', + 'places.kmlImportNoneSelected': 'Sélectionnez au moins un type.', + 'places.selectionCount': '{count} sélectionné(s)', + 'places.deleteSelected': 'Supprimer la sélection', + 'places.kmlKmzImported': '{count} lieux importés depuis KMZ/KML', + 'places.urlResolved': "Lieu importé depuis l'URL", + 'places.importList': 'Import de liste', + 'places.kmlKmzSummaryValues': + 'Placemarks : {total} • Importés : {created} • Ignorés : {skipped}', + 'places.importGoogleList': 'Liste Google', + 'places.importNaverList': 'Liste Naver', + 'places.googleListHint': + 'Collez un lien de liste Google Maps partagée pour importer tous les lieux.', + 'places.googleListImported': '{count} lieux importés depuis "{list}"', + 'places.googleListError': "Impossible d'importer la liste Google Maps", + 'places.naverListHint': + 'Collez un lien de liste Naver Maps partagée pour importer tous les lieux.', + 'places.naverListImported': '{count} lieux importés depuis "{list}"', + 'places.naverListError': "Impossible d'importer la liste Naver Maps", + 'places.viewDetails': 'Voir les détails', + 'places.assignToDay': 'Ajouter à quel jour ?', + 'places.all': 'Tous', + 'places.unplanned': 'Non planifiés', + 'places.filterTracks': 'Traces', + 'places.search': 'Rechercher des lieux…', + 'places.allCategories': 'Toutes les catégories', + 'places.categoriesSelected': 'catégories', + 'places.clearFilter': 'Effacer le filtre', + 'places.count': '{count} lieux', + 'places.countSingular': '1 lieu', + 'places.allPlanned': 'Tous les lieux sont planifiés', + 'places.noneFound': 'Aucun lieu trouvé', + 'places.editPlace': 'Modifier le lieu', + 'places.formName': 'Nom', + 'places.formNamePlaceholder': 'ex. Tour Eiffel', + 'places.formDescription': 'Description', + 'places.formDescriptionPlaceholder': 'Brève description…', + 'places.formAddress': 'Adresse', + 'places.formAddressPlaceholder': 'Rue, ville, pays', + 'places.formLat': 'Latitude (ex. 48.8566)', + 'places.formLng': 'Longitude (ex. 2.3522)', + 'places.formCategory': 'Catégorie', + 'places.noCategory': 'Sans catégorie', + 'places.categoryNamePlaceholder': 'Nom de la catégorie', + 'places.formTime': 'Heure', + 'places.startTime': 'Début', + 'places.endTime': 'Fin', + 'places.endTimeBeforeStart': + "L'heure de fin est antérieure à l'heure de début", + 'places.timeCollision': 'Chevauchement horaire avec :', + 'places.formWebsite': 'Site web', + 'places.formNotes': 'Notes', + 'places.formNotesPlaceholder': 'Notes personnelles…', + 'places.formReservation': 'Réservation', + 'places.reservationNotesPlaceholder': + 'Notes de réservation, numéro de confirmation…', + 'places.mapsSearchPlaceholder': 'Rechercher des lieux…', + 'places.mapsSearchError': 'La recherche de lieu a échoué.', + 'places.loadingDetails': 'Chargement des détails du lieu…', + 'places.osmHint': + 'Recherche via OpenStreetMap (pas de photos, horaires ni notes). Ajoutez une clé API Google dans les paramètres pour plus de détails.', + 'places.osmActive': + 'Recherche via OpenStreetMap (pas de photos, notes ni horaires). Ajoutez une clé API Google dans les paramètres pour des données enrichies.', + 'places.categoryCreateError': 'Impossible de créer la catégorie', + 'places.nameRequired': 'Veuillez saisir un nom', + 'places.saveError': "Échec de l'enregistrement", +}; +export default places; diff --git a/shared/src/i18n/fr/planner.ts b/shared/src/i18n/fr/planner.ts new file mode 100644 index 00000000..9e3576b5 --- /dev/null +++ b/shared/src/i18n/fr/planner.ts @@ -0,0 +1,69 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Lieux', + 'planner.bookings': 'Réservations', + 'planner.packingList': 'Liste de bagages', + 'planner.documents': 'Documents', + 'planner.dayPlan': 'Plan du jour', + 'planner.reservations': 'Réservations', + 'planner.minTwoPlaces': 'Au moins 2 lieux avec coordonnées nécessaires', + 'planner.noGeoPlaces': 'Aucun lieu avec coordonnées disponible', + 'planner.routeCalculated': 'Itinéraire calculé', + 'planner.routeCalcFailed': "L'itinéraire n'a pas pu être calculé", + 'planner.routeError': "Erreur lors du calcul de l'itinéraire", + 'planner.icsExportFailed': "Échec de l'export ICS", + 'planner.routeOptimized': 'Itinéraire optimisé', + 'planner.reservationUpdated': 'Réservation mise à jour', + 'planner.reservationAdded': 'Réservation ajoutée', + 'planner.confirmDeleteReservation': 'Supprimer la réservation ?', + 'planner.reservationDeleted': 'Réservation supprimée', + 'planner.days': 'Jours', + 'planner.allPlaces': 'Tous les lieux', + 'planner.totalPlaces': '{n} lieux au total', + 'planner.noDaysPlanned': 'Aucun jour planifié', + 'planner.editTrip': 'Modifier le voyage →', + 'planner.placeOne': '1 lieu', + 'planner.placeN': '{n} lieux', + 'planner.addNote': 'Ajouter une note', + 'planner.noEntries': 'Aucune entrée pour ce jour', + 'planner.addPlace': 'Ajouter un lieu ou une activité', + 'planner.addPlaceShort': '+ Ajouter un lieu ou une activité', + 'planner.resPending': 'Réservation en attente · ', + 'planner.resConfirmed': 'Réservation confirmée · ', + 'planner.notePlaceholder': 'Note…', + 'planner.noteTimePlaceholder': 'Heure (facultatif)', + 'planner.noteExamplePlaceholder': + 'ex. S3 à 14h30 depuis la gare centrale, ferry depuis le quai 7, pause déjeuner…', + 'planner.totalCost': 'Coût total', + 'planner.searchPlaces': 'Rechercher des lieux…', + 'planner.allCategories': 'Toutes les catégories', + 'planner.noPlacesFound': 'Aucun lieu trouvé', + 'planner.addFirstPlace': 'Ajouter un premier lieu', + 'planner.noReservations': 'Aucune réservation', + 'planner.addFirstReservation': 'Ajouter une première réservation', + 'planner.new': 'Nouveau', + 'planner.addToDay': '+ Jour', + 'planner.calculating': 'Calcul…', + 'planner.route': 'Itinéraire', + 'planner.optimize': 'Optimiser', + 'planner.openGoogleMaps': 'Ouvrir dans Google Maps', + 'planner.selectDayHint': + 'Sélectionnez un jour dans la liste de gauche pour voir le plan du jour', + 'planner.noPlacesForDay': 'Aucun lieu pour ce jour', + 'planner.addPlacesLink': 'Ajouter des lieux →', + 'planner.minTotal': 'min. total', + 'planner.noReservation': 'Pas de réservation', + 'planner.removeFromDay': 'Retirer du jour', + 'planner.addToThisDay': 'Ajouter au jour', + 'planner.overview': 'Aperçu', + 'planner.noDays': 'Aucun jour', + 'planner.editTripToAddDays': 'Modifiez le voyage pour ajouter des jours', + 'planner.dayCount': '{n} jours', + 'planner.clickToUnlock': 'Cliquez pour déverrouiller', + 'planner.keepPosition': + "Maintenir la position lors de l'optimisation de l'itinéraire", + 'planner.dayDetails': 'Détails du jour', + 'planner.dayN': 'Jour {n}', +}; +export default planner; diff --git a/shared/src/i18n/fr/register.ts b/shared/src/i18n/fr/register.ts new file mode 100644 index 00000000..03aba507 --- /dev/null +++ b/shared/src/i18n/fr/register.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Les mots de passe ne correspondent pas', + 'register.passwordTooShort': + 'Le mot de passe doit comporter au moins 8 caractères', + 'register.failed': "Échec de l'inscription", + 'register.getStarted': 'Commencer', + 'register.subtitle': + 'Créez un compte et commencez à planifier vos voyages de rêve.', + 'register.feature1': 'Plans de voyage illimités', + 'register.feature2': 'Vue carte interactive', + 'register.feature3': 'Gérez les lieux et catégories', + 'register.feature4': 'Suivez les réservations', + 'register.feature5': 'Créez des listes de bagages', + 'register.feature6': 'Stockez photos et fichiers', + 'register.createAccount': 'Créer un compte', + 'register.startPlanning': 'Commencez à planifier vos voyages', + 'register.minChars': 'Min. 6 caractères', + 'register.confirmPassword': 'Confirmer le mot de passe', + 'register.repeatPassword': 'Répéter le mot de passe', + 'register.registering': 'Inscription en cours…', + 'register.register': "S'inscrire", + 'register.hasAccount': 'Vous avez déjà un compte ?', + 'register.signIn': 'Se connecter', +}; +export default register; diff --git a/shared/src/i18n/fr/reservations.ts b/shared/src/i18n/fr/reservations.ts new file mode 100644 index 00000000..9b1a6b3b --- /dev/null +++ b/shared/src/i18n/fr/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Réservations', + 'reservations.empty': 'Aucune réservation', + 'reservations.emptyHint': + 'Ajoutez des réservations pour les vols, hôtels et plus', + 'reservations.add': 'Ajouter une réservation', + 'reservations.addManual': 'Réservation manuelle', + 'reservations.placeHint': + 'Conseil : les réservations sont mieux créées directement depuis un lieu pour les lier à votre plan du jour.', + 'reservations.confirmed': 'Confirmée', + 'reservations.pending': 'En attente', + 'reservations.summary': '{confirmed} confirmées, {pending} en attente', + 'reservations.fromPlan': 'Du plan', + 'reservations.showFiles': 'Afficher les fichiers', + 'reservations.editTitle': 'Modifier la réservation', + 'reservations.status': 'Statut', + 'reservations.datetime': 'Date et heure', + 'reservations.startTime': 'Heure de début', + 'reservations.endTime': 'Heure de fin', + 'reservations.date': 'Date', + 'reservations.time': 'Heure', + 'reservations.timeAlt': 'Heure (alternative, ex. 19h30)', + 'reservations.notes': 'Notes', + 'reservations.notesPlaceholder': 'Notes supplémentaires…', + 'reservations.meta.airline': 'Compagnie aérienne', + 'reservations.meta.flightNumber': 'N° de vol', + 'reservations.meta.from': 'De', + 'reservations.meta.to': 'À', + 'reservations.needsReview': 'Vérifier', + 'reservations.needsReviewHint': + "L'aéroport n'a pas pu être identifié automatiquement — veuillez confirmer l'emplacement.", + 'reservations.searchLocation': 'Rechercher une gare, un port, une adresse…', + 'reservations.meta.trainNumber': 'N° de train', + 'reservations.meta.platform': 'Quai', + 'reservations.meta.seat': 'Place', + 'reservations.meta.checkIn': 'Arrivée', + 'reservations.meta.checkInUntil': "Check-in jusqu'à", + 'reservations.meta.checkOut': 'Départ', + 'reservations.meta.linkAccommodation': 'Hébergement', + 'reservations.meta.pickAccommodation': 'Lier à un hébergement', + 'reservations.meta.noAccommodation': 'Aucun', + 'reservations.meta.hotelPlace': 'Hébergement', + 'reservations.meta.pickHotel': 'Sélectionner un hébergement', + 'reservations.meta.fromDay': 'Du', + 'reservations.meta.toDay': 'Au', + 'reservations.meta.selectDay': 'Sélectionner un jour', + 'reservations.type.flight': 'Vol', + 'reservations.type.hotel': 'Hébergement', + 'reservations.type.restaurant': 'Restaurant', + 'reservations.type.train': 'Train', + 'reservations.type.car': 'Voiture', + 'reservations.type.cruise': 'Croisière', + 'reservations.type.event': 'Événement', + 'reservations.type.tour': 'Visite', + 'reservations.type.other': 'Autre', + 'reservations.confirm.delete': + 'Voulez-vous vraiment supprimer la réservation « {name} » ?', + 'reservations.confirm.deleteTitle': 'Supprimer la réservation ?', + 'reservations.confirm.deleteBody': '« {name} » sera définitivement supprimé.', + 'reservations.toast.updated': 'Réservation mise à jour', + 'reservations.toast.removed': 'Réservation supprimée', + 'reservations.toast.fileUploaded': 'Fichier importé', + 'reservations.toast.uploadError': "Échec de l'import", + 'reservations.newTitle': 'Nouvelle réservation', + 'reservations.bookingType': 'Type de réservation', + 'reservations.titleLabel': 'Titre', + 'reservations.titlePlaceholder': 'ex. Lufthansa LH123, Hôtel Adlon, …', + 'reservations.locationAddress': 'Lieu / Adresse', + 'reservations.locationPlaceholder': 'Adresse, aéroport, hôtel…', + 'reservations.confirmationCode': 'Code de réservation', + 'reservations.confirmationPlaceholder': 'ex. ABC12345', + 'reservations.day': 'Jour', + 'reservations.noDay': 'Aucun jour', + 'reservations.place': 'Lieu', + 'reservations.noPlace': 'Aucun lieu', + 'reservations.pendingSave': 'sera enregistré…', + 'reservations.uploading': 'Importation…', + 'reservations.attachFile': 'Joindre un fichier', + 'reservations.linkExisting': 'Lier un fichier existant', + 'reservations.toast.saveError': "Échec de l'enregistrement", + 'reservations.toast.updateError': 'Échec de la mise à jour', + 'reservations.toast.deleteError': 'Échec de la suppression', + 'reservations.confirm.remove': 'Supprimer la réservation pour « {name} » ?', + 'reservations.linkAssignment': "Lier à l'affectation du jour", + 'reservations.pickAssignment': 'Sélectionnez une affectation de votre plan…', + 'reservations.noAssignment': 'Aucun lien (autonome)', + 'reservations.price': 'Prix', + 'reservations.budgetCategory': 'Catégorie budgétaire', + 'reservations.budgetCategoryPlaceholder': 'ex. Transport, Hébergement', + 'reservations.budgetCategoryAuto': 'Auto (selon le type de réservation)', + 'reservations.budgetHint': + "Une entrée budgétaire sera créée automatiquement lors de l'enregistrement.", + 'reservations.departureDate': 'Départ', + 'reservations.arrivalDate': 'Arrivée', + 'reservations.departureTime': 'Heure dép.', + 'reservations.arrivalTime': 'Heure arr.', + 'reservations.pickupDate': 'Prise en charge', + 'reservations.returnDate': 'Restitution', + 'reservations.pickupTime': 'Heure prise en charge', + 'reservations.returnTime': 'Heure restitution', + 'reservations.endDate': 'Date de fin', + 'reservations.meta.departureTimezone': 'TZ dép.', + 'reservations.meta.arrivalTimezone': 'TZ arr.', + 'reservations.span.departure': 'Départ', + 'reservations.span.arrival': 'Arrivée', + 'reservations.span.inTransit': 'En transit', + 'reservations.span.pickup': 'Prise en charge', + 'reservations.span.return': 'Restitution', + 'reservations.span.active': 'Actif', + 'reservations.span.start': 'Début', + 'reservations.span.end': 'Fin', + 'reservations.span.ongoing': 'En cours', + 'reservations.validation.endBeforeStart': + 'La date/heure de fin doit être postérieure à la date/heure de début', + 'reservations.addBooking': 'Ajouter une réservation', +}; +export default reservations; diff --git a/shared/src/i18n/fr/settings.ts b/shared/src/i18n/fr/settings.ts new file mode 100644 index 00000000..4042da78 --- /dev/null +++ b/shared/src/i18n/fr/settings.ts @@ -0,0 +1,303 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Paramètres', + 'settings.subtitle': 'Configurez vos paramètres personnels', + 'settings.tabs.display': 'Affichage', + 'settings.tabs.map': 'Carte', + 'settings.tabs.notifications': 'Notifications', + 'settings.tabs.integrations': 'Intégrations', + 'settings.tabs.account': 'Compte', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'À propos', + 'settings.map': 'Carte', + 'settings.mapTemplate': 'Modèle de carte', + 'settings.mapTemplatePlaceholder.select': 'Sélectionner un modèle…', + 'settings.mapDefaultHint': 'Laissez vide pour OpenStreetMap (par défaut)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': "Modèle d'URL pour les tuiles de carte", + 'settings.mapProvider': 'Fournisseur de carte', + 'settings.mapProviderHint': + 'Affecte les cartes Trip Planner et Journey. Atlas utilise toujours Leaflet.', + 'settings.mapLeafletSubtitle': 'Classique 2D, toutes tuiles raster', + 'settings.mapMapboxSubtitle': 'Tuiles vectorielles, bâtiments 3D & terrain', + 'settings.mapExperimental': 'Expérimental', + 'settings.mapMapboxToken': "Jeton d'accès Mapbox", + 'settings.mapMapboxTokenHint': 'Jeton public (pk.*) depuis', + 'settings.mapMapboxTokenLink': "mapbox.com → Jetons d'accès", + 'settings.mapStyle': 'Style de carte', + 'settings.mapStylePlaceholder': 'Sélectionner un style Mapbox', + 'settings.mapStyleHint': 'Preset ou votre propre URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': 'Bâtiments 3D & terrain', + 'settings.map3dHint': + 'Inclinaison + extrusions 3D réelles des bâtiments — fonctionne avec tous les styles, y compris satellite.', + 'settings.mapHighQuality': 'Mode haute qualité', + 'settings.mapHighQualityHint': + 'Anticrénelage + projection globe pour des bords plus nets et une vue réaliste du monde.', + 'settings.mapHighQualityWarning': + 'Peut affecter les performances sur les appareils moins puissants.', + 'settings.mapTipLabel': 'Astuce :', + 'settings.mapTip': + 'Clic droit et glisser pour pivoter/incliner la carte. Clic milieu pour ajouter un lieu (le clic droit est réservé à la rotation).', + 'settings.latitude': 'Latitude', + 'settings.longitude': 'Longitude', + 'settings.saveMap': 'Enregistrer la carte', + 'settings.apiKeys': 'Clés API', + 'settings.mapsKey': 'Clé API Google Maps', + 'settings.mapsKeyHint': + "Pour la recherche de lieux. Nécessite l'API Places (New). Obtenez-la sur console.cloud.google.com", + 'settings.weatherKey': 'Clé API OpenWeatherMap', + 'settings.weatherKeyHint': + 'Pour les données météo. Gratuit sur openweathermap.org/api', + 'settings.keyPlaceholder': 'Saisir la clé…', + 'settings.configured': 'Configuré', + 'settings.saveKeys': 'Enregistrer les clés', + 'settings.display': 'Affichage', + 'settings.colorMode': 'Mode de couleur', + 'settings.light': 'Clair', + 'settings.dark': 'Sombre', + 'settings.auto': 'Auto', + 'settings.language': 'Langue', + 'settings.temperature': 'Unité de température', + 'settings.timeFormat': "Format de l'heure", + 'settings.blurBookingCodes': 'Masquer les codes de réservation', + 'settings.notifications': 'Notifications', + 'settings.notifyTripInvite': 'Invitations de voyage', + 'settings.notifyBookingChange': 'Modifications de réservation', + 'settings.notifyTripReminder': 'Rappels de voyage', + 'settings.notifyTodoDue': 'Tâche à échéance', + 'settings.notifyVacayInvite': 'Invitations de fusion Vacay', + 'settings.notifyPhotosShared': 'Photos partagées (Immich)', + 'settings.notifyCollabMessage': 'Messages de chat (Collab)', + 'settings.notifyPackingTagged': 'Liste de bagages : attributions', + 'settings.notifyWebhook': 'Notifications webhook', + 'settings.notificationsDisabled': + "Les notifications ne sont pas configurées. Demandez à un administrateur d'activer les notifications par e-mail ou webhook.", + 'settings.notificationsActive': 'Canal actif', + 'settings.notificationsManagedByAdmin': + 'Les événements de notification sont configurés par votre administrateur.', + 'settings.on': 'Activé', + 'settings.off': 'Désactivé', + 'settings.mcp.title': 'Configuration MCP', + 'settings.mcp.endpoint': 'Point de terminaison MCP', + 'settings.mcp.clientConfig': 'Configuration du client', + 'settings.mcp.clientConfigHint': + 'Remplacez par un token API de la liste ci-dessous. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).', + 'settings.mcp.clientConfigHintOAuth': + "Remplacez et par les identifiants affichés dans le client OAuth 2.1 créé ci-dessus. mcp-remote ouvrira votre navigateur pour finaliser l'autorisation lors de la première connexion. Le chemin vers npx devra peut-être être ajusté selon votre système (ex. C:\\PROGRA~1\\nodejs\\npx.cmd sous Windows).", + 'settings.mcp.copy': 'Copier', + 'settings.mcp.copied': 'Copié !', + 'settings.mcp.apiTokens': 'Tokens API', + 'settings.mcp.createToken': 'Créer un token', + 'settings.mcp.noTokens': + "Aucun token pour l'instant. Créez-en un pour connecter des clients MCP.", + 'settings.mcp.tokenCreatedAt': 'Créé', + 'settings.mcp.tokenUsedAt': 'Utilisé', + 'settings.mcp.deleteTokenTitle': 'Supprimer le token', + 'settings.mcp.deleteTokenMessage': + "Ce token cessera de fonctionner immédiatement. Tout client MCP l'utilisant perdra l'accès.", + 'settings.mcp.modal.createTitle': 'Créer un token API', + 'settings.mcp.modal.tokenName': 'Nom du token', + 'settings.mcp.modal.tokenNamePlaceholder': + 'ex. Claude Desktop, Ordinateur pro', + 'settings.mcp.modal.creating': 'Création…', + 'settings.mcp.modal.create': 'Créer le token', + 'settings.mcp.modal.createdTitle': 'Token créé', + 'settings.mcp.modal.createdWarning': + "Ce token ne sera affiché qu'une seule fois. Copiez-le et conservez-le maintenant — il ne pourra pas être récupéré.", + 'settings.mcp.modal.done': 'Terminé', + 'settings.mcp.toast.created': 'Token créé', + 'settings.mcp.toast.createError': 'Impossible de créer le token', + 'settings.mcp.toast.deleted': 'Token supprimé', + 'settings.mcp.toast.deleteError': 'Impossible de supprimer le token', + 'settings.mcp.apiTokensDeprecated': + 'Les tokens API sont dépréciés et seront supprimés dans une prochaine version. Veuillez utiliser les clients OAuth 2.1 à la place.', + 'settings.oauth.clients': 'Clients OAuth 2.1', + 'settings.oauth.clientsHint': + 'Enregistrez des clients OAuth 2.1 pour permettre à des applications MCP tierces (Claude Web, Cursor, etc.) de se connecter sans tokens statiques.', + 'settings.oauth.createClient': 'Nouveau client', + 'settings.oauth.noClients': 'Aucun client OAuth enregistré.', + 'settings.oauth.clientId': 'ID client', + 'settings.oauth.clientSecret': 'Secret client', + 'settings.oauth.deleteClient': 'Supprimer le client', + 'settings.oauth.deleteClientMessage': + "Ce client et toutes les sessions actives seront définitivement supprimés. Toute application l'utilisant perdra immédiatement l'accès.", + 'settings.oauth.rotateSecret': 'Renouveler le secret', + 'settings.oauth.rotateSecretMessage': + 'Un nouveau secret client sera généré et toutes les sessions existantes seront immédiatement invalidées. Mettez à jour votre application avant de fermer cette fenêtre.', + 'settings.oauth.rotateSecretConfirm': 'Renouveler', + 'settings.oauth.rotateSecretConfirming': 'Renouvellement…', + 'settings.oauth.rotateSecretDoneTitle': 'Nouveau secret généré', + 'settings.oauth.rotateSecretDoneWarning': + "Ce secret n'est affiché qu'une seule fois. Copiez-le maintenant et mettez à jour votre application — toutes les sessions précédentes ont été invalidées.", + 'settings.oauth.activeSessions': 'Sessions OAuth actives', + 'settings.oauth.sessionScopes': 'Portées', + 'settings.oauth.sessionExpires': 'Expire', + 'settings.oauth.revoke': 'Révoquer', + 'settings.oauth.revokeSession': 'Révoquer la session', + 'settings.oauth.revokeSessionMessage': + "Cela révoquera immédiatement l'accès pour cette session OAuth.", + 'settings.oauth.modal.createTitle': 'Enregistrer un client OAuth', + 'settings.oauth.modal.presets': 'Préréglages rapides', + 'settings.oauth.modal.clientName': "Nom de l'application", + 'settings.oauth.modal.clientNamePlaceholder': 'ex. Claude Web, Mon app MCP', + 'settings.oauth.modal.redirectUris': 'URIs de redirection', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Une URI par ligne. HTTPS requis (localhost exempté). Correspondance exacte.', + 'settings.oauth.modal.scopes': 'Portées autorisées', + 'settings.oauth.modal.scopesHint': + "list_trips et get_trip_summary sont toujours disponibles — aucune portée requise. Ils permettent à l'IA de découvrir les IDs de voyage nécessaires.", + 'settings.oauth.modal.selectAll': 'Tout sélectionner', + 'settings.oauth.modal.deselectAll': 'Tout désélectionner', + 'settings.oauth.modal.creating': 'Enregistrement…', + 'settings.oauth.modal.create': 'Enregistrer le client', + 'settings.oauth.modal.createdTitle': 'Client enregistré', + 'settings.oauth.modal.createdWarning': + "Le secret client n'est affiché qu'une seule fois. Copiez-le maintenant — il ne peut pas être récupéré.", + 'settings.oauth.toast.createError': + "Impossible d'enregistrer le client OAuth", + 'settings.oauth.toast.deleted': 'Client OAuth supprimé', + 'settings.oauth.toast.deleteError': 'Impossible de supprimer le client OAuth', + 'settings.oauth.toast.revoked': 'Session révoquée', + 'settings.oauth.toast.revokeError': 'Impossible de révoquer la session', + 'settings.oauth.toast.rotateError': + 'Impossible de renouveler le secret client', + 'settings.oauth.modal.machineClient': + 'Client machine (sans connexion navigateur)', + 'settings.oauth.modal.machineClientHint': + 'Utilise le grant client_credentials — aucune URI de redirection requise. Le token est émis directement via client_id + client_secret et agit en votre nom dans les portées sélectionnées.', + 'settings.oauth.modal.machineClientUsage': + 'Obtenir un token : POST /oauth/token avec grant_type=client_credentials, client_id et client_secret. Sans navigateur, sans token de rafraîchissement.', + 'settings.oauth.badge.machine': 'machine', + 'settings.account': 'Compte', + 'settings.about': 'À propos', + 'settings.about.reportBug': 'Signaler un bug', + 'settings.about.reportBugHint': 'Un problème ? Faites-le nous savoir', + 'settings.about.featureRequest': 'Proposer une fonctionnalité', + 'settings.about.featureRequestHint': 'Suggérez une nouvelle fonctionnalité', + 'settings.about.wikiHint': 'Documentation et guides', + 'settings.about.supporters.badge': 'Soutiens Mensuels', + 'settings.about.supporters.title': 'Compagnons de voyage pour TREK', + 'settings.about.supporters.subtitle': + "Pendant que tu planifies ton prochain itinéraire, ces personnes aident à planifier l'avenir de TREK. Leur contribution mensuelle va directement au développement et aux heures réellement passées — pour que TREK reste Open Source.", + 'settings.about.supporters.since': 'soutien depuis {date}', + 'settings.about.supporters.tierEmpty': 'Sois le premier', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK est un planificateur de voyage auto-hébergé qui vous aide à organiser vos voyages de la première idée au dernier souvenir. Planification journalière, budget, listes de bagages, photos et bien plus — le tout au même endroit, sur votre propre serveur.', + 'settings.about.madeWith': 'Fait avec', + 'settings.about.madeBy': + 'par Maurice et une communauté open-source grandissante.', + 'settings.username': "Nom d'utilisateur", + 'settings.email': 'E-mail', + 'settings.role': 'Rôle', + 'settings.roleAdmin': 'Administrateur', + 'settings.oidcLinked': 'Lié avec', + 'settings.changePassword': 'Changer le mot de passe', + 'settings.mustChangePassword': + 'Vous devez changer votre mot de passe avant de continuer. Veuillez définir un nouveau mot de passe ci-dessous.', + 'settings.currentPassword': 'Mot de passe actuel', + 'settings.currentPasswordRequired': 'Le mot de passe actuel est requis', + 'settings.newPassword': 'Nouveau mot de passe', + 'settings.confirmPassword': 'Confirmer le nouveau mot de passe', + 'settings.updatePassword': 'Mettre à jour le mot de passe', + 'settings.passwordRequired': + 'Veuillez saisir le mot de passe actuel et le nouveau', + 'settings.passwordTooShort': + 'Le mot de passe doit comporter au moins 8 caractères', + 'settings.passwordMismatch': 'Les mots de passe ne correspondent pas', + 'settings.passwordWeak': + 'Le mot de passe doit contenir des majuscules, des minuscules, un chiffre et un caractère spécial', + 'settings.passwordChanged': 'Mot de passe modifié avec succès', + 'settings.deleteAccount': 'Supprimer le compte', + 'settings.deleteAccountTitle': 'Supprimer votre compte ?', + 'settings.deleteAccountWarning': + 'Votre compte ainsi que tous vos voyages, lieux et fichiers seront définitivement supprimés. Cette action est irréversible.', + 'settings.deleteAccountConfirm': 'Supprimer définitivement', + 'settings.deleteBlockedTitle': 'Suppression impossible', + 'settings.deleteBlockedMessage': + "Vous êtes le seul administrateur. Promouvez un autre utilisateur en tant qu'administrateur avant de supprimer votre compte.", + 'settings.roleUser': 'Utilisateur', + 'settings.saveProfile': 'Enregistrer le profil', + 'settings.mfa.title': 'Authentification à deux facteurs (2FA)', + 'settings.mfa.description': + "Ajoute une étape supplémentaire lors de la connexion. Utilisez une application d'authentification (Google Authenticator, Authy, etc.).", + 'settings.mfa.requiredByPolicy': + "Votre administrateur exige l'authentification à deux facteurs. Configurez une application d'authentification ci-dessous avant de continuer.", + 'settings.mfa.backupTitle': 'Codes de secours', + 'settings.mfa.backupDescription': + "Utilisez ces codes à usage unique si vous perdez l'accès à votre application d'authentification.", + 'settings.mfa.backupWarning': + "Enregistrez ces codes maintenant. Chaque code n'est utilisable qu'une seule fois.", + 'settings.mfa.backupCopy': 'Copier les codes', + 'settings.mfa.backupDownload': 'Télécharger TXT', + 'settings.mfa.backupPrint': 'Imprimer / PDF', + 'settings.mfa.backupCopied': 'Codes de secours copiés', + 'settings.mfa.enabled': '2FA est activé sur votre compte.', + 'settings.mfa.disabled': "2FA n'est pas activé.", + 'settings.mfa.setup': "Configurer l'authentificateur", + 'settings.mfa.scanQr': + 'Scannez ce code QR avec votre application ou entrez la clé manuellement.', + 'settings.mfa.secretLabel': 'Clé secrète (saisie manuelle)', + 'settings.mfa.codePlaceholder': 'Code à 6 chiffres', + 'settings.mfa.enable': 'Activer 2FA', + 'settings.mfa.cancelSetup': 'Annuler', + 'settings.mfa.disableTitle': 'Désactiver 2FA', + 'settings.mfa.disableHint': + 'Entrez votre mot de passe et un code actuel de votre authentificateur.', + 'settings.mfa.disable': 'Désactiver 2FA', + 'settings.mfa.toastEnabled': 'Authentification à deux facteurs activée', + 'settings.mfa.toastDisabled': 'Authentification à deux facteurs désactivée', + 'settings.mfa.demoBlocked': 'Non disponible en mode démo', + 'settings.toast.mapSaved': 'Paramètres de carte enregistrés', + 'settings.toast.keysSaved': 'Clés API enregistrées', + 'settings.toast.displaySaved': "Paramètres d'affichage enregistrés", + 'settings.toast.profileSaved': 'Profil enregistré', + 'settings.uploadAvatar': 'Importer une photo de profil', + 'settings.removeAvatar': 'Supprimer la photo de profil', + 'settings.avatarUploaded': 'Photo de profil mise à jour', + 'settings.avatarRemoved': 'Photo de profil supprimée', + 'settings.avatarError': "Échec de l'import", + 'settings.bookingLabels': 'Étiquettes des itinéraires', + 'settings.bookingLabelsHint': + "Affiche les noms des gares / aéroports sur la carte. Si désactivé, seule l'icône est affichée.", + 'settings.notifyVersionAvailable': 'Nouvelle version disponible', + 'settings.notificationPreferences.noChannels': + "Aucun canal de notification n'est configuré. Demandez à un administrateur de configurer les notifications par e-mail ou webhook.", + 'settings.webhookUrl.label': 'URL du webhook', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Entrez votre URL de webhook Discord, Slack ou personnalisée pour recevoir des notifications.', + 'settings.webhookUrl.saved': 'URL du webhook enregistrée', + 'settings.webhookUrl.test': 'Tester', + 'settings.webhookUrl.testSuccess': 'Webhook de test envoyé avec succès', + 'settings.webhookUrl.testFailed': 'Échec du webhook de test', + 'settings.ntfyUrl.topicLabel': 'Sujet Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL du serveur Ntfy (optionnel)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Entrez votre sujet Ntfy pour recevoir des notifications push. Laissez le serveur vide pour utiliser la valeur par défaut configurée par votre administrateur.', + 'settings.ntfyUrl.tokenLabel': "Jeton d'accès (optionnel)", + 'settings.ntfyUrl.tokenHint': + 'Requis pour les sujets protégés par mot de passe.', + 'settings.ntfyUrl.saved': 'Paramètres Ntfy enregistrés', + 'settings.ntfyUrl.test': 'Tester', + 'settings.ntfyUrl.testSuccess': + 'Notification de test Ntfy envoyée avec succès', + 'settings.ntfyUrl.testFailed': 'Échec de la notification de test Ntfy', + 'settings.ntfyUrl.tokenCleared': "Jeton d'accès effacé", + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/fr/share.ts b/shared/src/i18n/fr/share.ts new file mode 100644 index 00000000..65e8e386 --- /dev/null +++ b/shared/src/i18n/fr/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Lien public', + 'share.linkHint': + "Créez un lien que n'importe qui peut utiliser pour consulter ce voyage sans se connecter. Lecture seule — aucune modification possible.", + 'share.createLink': 'Créer un lien', + 'share.deleteLink': 'Supprimer le lien', + 'share.createError': 'Impossible de créer le lien', + 'share.permMap': 'Carte et plan', + 'share.permBookings': 'Réservations', + 'share.permPacking': 'Bagages', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/fr/shared.ts b/shared/src/i18n/fr/shared.ts new file mode 100644 index 00000000..b2b2201b --- /dev/null +++ b/shared/src/i18n/fr/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Lien expiré ou invalide', + 'shared.expiredHint': "Ce lien de partage n'est plus actif.", + 'shared.readOnly': 'Vue en lecture seule', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Réservations', + 'shared.tabPacking': 'Bagages', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'jours', + 'shared.places': 'lieux', + 'shared.other': 'Autre', + 'shared.totalBudget': 'Budget total', + 'shared.messages': 'messages', + 'shared.sharedVia': 'Partagé via', + 'shared.confirmed': 'Confirmé', + 'shared.pending': 'En attente', +}; +export default shared; diff --git a/shared/src/i18n/fr/stats.ts b/shared/src/i18n/fr/stats.ts new file mode 100644 index 00000000..c1deee35 --- /dev/null +++ b/shared/src/i18n/fr/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Pays', + 'stats.cities': 'Villes', + 'stats.trips': 'Voyages', + 'stats.places': 'Lieux', + 'stats.worldProgress': 'Progression mondiale', + 'stats.visited': 'visités', + 'stats.remaining': 'restants', + 'stats.visitedCountries': 'Pays visités', +}; +export default stats; diff --git a/shared/src/i18n/fr/system_notice.ts b/shared/src/i18n/fr/system_notice.ts new file mode 100644 index 00000000..c33d3172 --- /dev/null +++ b/shared/src/i18n/fr/system_notice.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Bienvenue sur TREK', + 'system_notice.welcome_v1.body': + 'Votre planificateur de voyage tout-en-un. Créez des itinéraires, partagez vos voyages et restez organisé — en ligne ou hors ligne.', + 'system_notice.welcome_v1.cta_label': 'Planifier un voyage', + 'system_notice.welcome_v1.hero_alt': + "Destination de voyage pittoresque avec l'interface TREK", + 'system_notice.welcome_v1.highlight_plan': 'Itinéraires jour par jour', + 'system_notice.welcome_v1.highlight_share': 'Collaborez avec vos partenaires', + 'system_notice.welcome_v1.highlight_offline': + 'Fonctionne hors ligne sur mobile', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Avis précédent', + 'system_notice.pager.next': 'Avis suivant', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': "Aller à l'avis {n}", + 'system_notice.pager.position': 'Avis {current} sur {total}', + 'system_notice.v3_photos.title': 'Les photos ont bougé dans 3.0', + 'system_notice.v3_photos.body': + "**Photos** dans le planificateur ont été supprimées. Tes photos sont en sécurité — TREK n'a jamais modifié ta bibliothèque Immich ou Synology.\n\nLes photos vivent désormais dans l'addon **Journey**. Journey est optionnel — s'il n'est pas encore disponible, demande à ton admin de l'activer dans Admin → Modules.", + 'system_notice.v3_journey.title': 'Découvrez Journey — journal de voyage', + 'system_notice.v3_journey.body': + 'Documente tes voyages sous forme de récits enrichis avec chronologies, galeries photos et cartes interactives.', + 'system_notice.v3_journey.cta_label': 'Ouvrir Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Chronologie et galerie par jour', + 'system_notice.v3_journey.highlight_photos': + 'Import depuis Immich ou Synology', + 'system_notice.v3_journey.highlight_share': + 'Partage public — sans connexion requise', + 'system_notice.v3_journey.highlight_export': 'Export en livre photo PDF', + 'system_notice.v3_features.title': 'Plus de nouveautés en 3.0', + 'system_notice.v3_features.body': + 'Quelques autres choses à savoir sur cette version.', + 'system_notice.v3_features.highlight_dashboard': + 'Tableau de bord repensé mobile-first', + 'system_notice.v3_features.highlight_offline': + 'Mode hors ligne complet en PWA', + 'system_notice.v3_features.highlight_search': + 'Autocomplétion des lieux en temps réel', + 'system_notice.v3_features.highlight_import': + 'Importer des lieux depuis KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP : mise à niveau OAuth 2.1', + 'system_notice.v3_mcp.body': + "L'intégration MCP a été entièrement repensée. OAuth 2.1 est désormais la méthode d'authentification recommandée. Les tokens statiques (trek_…) sont dépréciés et seront supprimés dans une future version.", + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommandé (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': + '24 scopes de permissions granulaires', + 'system_notice.v3_mcp.highlight_deprecated': + 'Tokens statiques trek_ dépréciés', + 'system_notice.v3_mcp.highlight_tools': 'Outils et prompts étendus', + 'system_notice.v3_thankyou.title': 'Un mot personnel de ma part', + 'system_notice.v3_thankyou.body': + "Avant de continuer — je veux prendre un instant.\n\nTREK a commencé comme un projet perso que j'ai construit pour mes propres voyages. Je n'aurais jamais imaginé qu'il grandirait au point que 4 000 d'entre vous lui fassent confiance pour planifier vos aventures. Chaque étoile, chaque issue, chaque demande de fonctionnalité — je les lis toutes, et ce sont elles qui me font tenir pendant les nuits blanches entre un travail à temps plein et l'université.\n\nJe veux que vous sachiez : TREK sera toujours open source, toujours auto-hébergé, toujours à vous. Pas de tracking, pas d'abonnements, pas de conditions cachées. Juste un outil construit par quelqu'un qui aime voyager autant que vous.\n\nUn merci tout particulier à [jubnl](https://github.com/jubnl) — tu es devenu un collaborateur incroyable. Une grande partie de ce qui rend la 3.0 géniale porte ton empreinte. Merci d'avoir cru en ce projet quand il était encore brut.\n\nEt à chacun d'entre vous qui a signalé un bug, traduit une chaîne, partagé TREK avec un ami ou simplement l'a utilisé pour planifier un voyage — **merci**. Vous êtes la raison pour laquelle tout ceci existe.\n\nÀ de nombreuses autres aventures ensemble.\n\n— Maurice\n\n---\n\n[Rejoins la communauté sur Discord](https://discord.gg/7Q6M6jDwzf)\n\nSi TREK rend tes voyages meilleurs, un [petit café](https://ko-fi.com/mauriceboe) aide toujours à garder les lumières allumées.", + 'system_notice.v3014_whitespace_collision.title': + 'Action requise : conflit de compte utilisateur', + 'system_notice.v3014_whitespace_collision.body': + "La mise à niveau 3.0.14 a détecté un ou plusieurs conflits de nom d'utilisateur ou d'adresse e-mail causés par des espaces en début ou en fin de valeur dans les comptes enregistrés. Les comptes concernés ont été renommés automatiquement. Consultez les journaux du serveur pour les lignes commençant par **[migration] WHITESPACE COLLISION** afin d'identifier les comptes nécessitant une vérification.", +}; +export default system_notice; diff --git a/shared/src/i18n/fr/todo.ts b/shared/src/i18n/fr/todo.ts new file mode 100644 index 00000000..0f0db819 --- /dev/null +++ b/shared/src/i18n/fr/todo.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Liste de bagages', + 'todo.subtab.todo': 'À faire', + 'todo.completed': 'terminé(s)', + 'todo.filter.all': 'Tout', + 'todo.filter.open': 'En cours', + 'todo.filter.done': 'Terminé', + 'todo.uncategorized': 'Sans catégorie', + 'todo.namePlaceholder': 'Nom de la tâche', + 'todo.descriptionPlaceholder': 'Description (facultative)', + 'todo.unassigned': 'Non assigné', + 'todo.noCategory': 'Aucune catégorie', + 'todo.hasDescription': 'Avec description', + 'todo.addItem': 'Nouvelle tâche', + 'todo.sidebar.sortBy': 'Trier par', + 'todo.priority': 'Priorité', + 'todo.newCategoryLabel': 'nouvelle', + 'todo.newCategory': 'Nom de la catégorie', + 'todo.addCategory': 'Ajouter une catégorie', + 'todo.newItem': 'Nouvelle tâche', + 'todo.empty': + "Aucune tâche pour l'instant. Ajoutez une tâche pour commencer !", + 'todo.filter.my': 'Mes tâches', + 'todo.filter.overdue': 'En retard', + 'todo.sidebar.tasks': 'Tâches', + 'todo.sidebar.categories': 'Catégories', + 'todo.detail.title': 'Tâche', + 'todo.detail.description': 'Description', + 'todo.detail.category': 'Catégorie', + 'todo.detail.dueDate': "Date d'échéance", + 'todo.detail.assignedTo': 'Assigné à', + 'todo.detail.delete': 'Supprimer', + 'todo.detail.save': 'Enregistrer les modifications', + 'todo.detail.create': 'Créer la tâche', + 'todo.detail.priority': 'Priorité', + 'todo.detail.noPriority': 'Aucune', + 'todo.sortByPrio': 'Priorité', +}; +export default todo; diff --git a/shared/src/i18n/fr/transport.ts b/shared/src/i18n/fr/transport.ts new file mode 100644 index 00000000..ab8591df --- /dev/null +++ b/shared/src/i18n/fr/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Ajouter un transport', + 'transport.modalTitle.create': 'Ajouter un transport', + 'transport.modalTitle.edit': 'Modifier le transport', + 'transport.title': 'Transports', + 'transport.addManual': 'Transport manuel', +}; +export default transport; diff --git a/shared/src/i18n/fr/trip.ts b/shared/src/i18n/fr/trip.ts new file mode 100644 index 00000000..4f2e572c --- /dev/null +++ b/shared/src/i18n/fr/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transports', + 'trip.tabs.reservations': 'Réservations', + 'trip.tabs.reservationsShort': 'Résa', + 'trip.tabs.packing': 'Liste de bagages', + 'trip.tabs.packingShort': 'Bagages', + 'trip.tabs.lists': 'Listes', + 'trip.tabs.listsShort': 'Listes', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'Fichiers', + 'trip.loading': 'Chargement du voyage…', + 'trip.loadingPhotos': 'Chargement des photos des lieux...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Lieux', + 'trip.toast.placeUpdated': 'Lieu mis à jour', + 'trip.toast.placeAdded': 'Lieu ajouté', + 'trip.toast.placeDeleted': 'Lieu supprimé', + 'trip.toast.selectDay': "Veuillez d'abord sélectionner un jour", + 'trip.toast.assignedToDay': 'Lieu attribué au planning', + 'trip.toast.reorderError': 'Échec de la réorganisation', + 'trip.toast.reservationUpdated': 'Réservation mise à jour', + 'trip.toast.reservationAdded': 'Réservation ajoutée', + 'trip.toast.deleted': 'Supprimé', + 'trip.confirm.deletePlace': 'Voulez-vous vraiment supprimer ce lieu ?', + 'trip.confirm.deletePlaces': 'Supprimer {count} lieux?', + 'trip.toast.placesDeleted': '{count} lieux supprimés', +}; +export default trip; diff --git a/shared/src/i18n/fr/trips.ts b/shared/src/i18n/fr/trips.ts new file mode 100644 index 00000000..46268fa1 --- /dev/null +++ b/shared/src/i18n/fr/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} supprimé', + 'trips.memberRemoveError': 'Échec de la suppression', + 'trips.memberAdded': '{username} ajouté', + 'trips.memberAddError': "Échec de l'ajout", + 'trips.reminder': 'Rappel', + 'trips.reminderNone': 'Aucun', + 'trips.reminderDay': 'jour', + 'trips.reminderDays': 'jours', + 'trips.reminderCustom': 'Personnalisé', + 'trips.reminderDaysBefore': 'jours avant le départ', + 'trips.reminderDisabledHint': + 'Les rappels de voyage sont désactivés. Activez-les dans Admin > Paramètres > Notifications.', +}; +export default trips; diff --git a/shared/src/i18n/fr/undo.ts b/shared/src/i18n/fr/undo.ts new file mode 100644 index 00000000..6e78e7f5 --- /dev/null +++ b/shared/src/i18n/fr/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Annuler', + 'undo.tooltip': 'Annuler : {action}', + 'undo.assignPlace': 'Lieu ajouté au jour', + 'undo.removeAssignment': 'Lieu retiré du jour', + 'undo.reorder': 'Lieux réorganisés', + 'undo.optimize': 'Itinéraire optimisé', + 'undo.deletePlace': 'Lieu supprimé', + 'undo.deletePlaces': 'Lieux supprimés', + 'undo.moveDay': 'Lieu déplacé vers un autre jour', + 'undo.lock': 'Verrouillage du lieu modifié', + 'undo.importGpx': 'Import GPX', + 'undo.importKeyholeMarkup': 'Import KMZ/KML', + 'undo.importGoogleList': 'Import Google Maps', + 'undo.importNaverList': 'Import Naver Maps', + 'undo.addPlace': 'Lieu ajouté', + 'undo.done': 'Annulé : {action}', +}; +export default undo; diff --git a/shared/src/i18n/fr/vacay.ts b/shared/src/i18n/fr/vacay.ts new file mode 100644 index 00000000..f8b03b16 --- /dev/null +++ b/shared/src/i18n/fr/vacay.ts @@ -0,0 +1,109 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Planifiez et gérez vos jours de congés', + 'vacay.settings': 'Paramètres', + 'vacay.year': 'Année', + 'vacay.addYear': "Ajouter l'année suivante", + 'vacay.addPrevYear': "Ajouter l'année précédente", + 'vacay.removeYear': "Supprimer l'année", + 'vacay.removeYearConfirm': 'Supprimer {year} ?', + 'vacay.removeYearHint': + "Toutes les entrées de vacances et jours fériés d'entreprise de cette année seront définitivement supprimés.", + 'vacay.remove': 'Supprimer', + 'vacay.persons': 'Personnes', + 'vacay.noPersons': 'Aucune personne ajoutée', + 'vacay.addPerson': 'Ajouter une personne', + 'vacay.editPerson': 'Modifier la personne', + 'vacay.removePerson': 'Supprimer la personne', + 'vacay.removePersonConfirm': 'Supprimer {name} ?', + 'vacay.removePersonHint': + 'Toutes les entrées de vacances de cette personne seront définitivement supprimées.', + 'vacay.personName': 'Nom', + 'vacay.personNamePlaceholder': 'Saisir le nom', + 'vacay.color': 'Couleur', + 'vacay.add': 'Ajouter', + 'vacay.legend': 'Légende', + 'vacay.publicHoliday': 'Jour férié', + 'vacay.companyHoliday': "Jour férié d'entreprise", + 'vacay.weekend': 'Week-end', + 'vacay.modeVacation': 'Vacances', + 'vacay.modeCompany': "Jour férié d'entreprise", + 'vacay.entitlement': 'Droits', + 'vacay.entitlementDays': 'Jours', + 'vacay.used': 'Utilisés', + 'vacay.remaining': 'Restants', + 'vacay.carriedOver': 'de {year}', + 'vacay.weekendDays': 'Jours de week-end', + 'vacay.mon': 'Lun', + 'vacay.tue': 'Mar', + 'vacay.wed': 'Mer', + 'vacay.thu': 'Jeu', + 'vacay.fri': 'Ven', + 'vacay.sat': 'Sam', + 'vacay.sun': 'Dim', + 'vacay.blockWeekends': 'Bloquer les week-ends', + 'vacay.blockWeekendsHint': + 'Empêcher les entrées de vacances les samedis et dimanches', + 'vacay.publicHolidays': 'Jours fériés', + 'vacay.publicHolidaysHint': 'Marquer les jours fériés dans le calendrier', + 'vacay.selectCountry': 'Sélectionner un pays', + 'vacay.selectRegion': 'Sélectionner une région (facultatif)', + 'vacay.companyHolidays': "Jours fériés d'entreprise", + 'vacay.companyHolidaysHint': + "Autoriser le marquage des jours fériés d'entreprise", + 'vacay.companyHolidaysNoDeduct': + "Les jours fériés d'entreprise ne sont pas déduits des jours de vacances.", + 'vacay.weekStart': 'La semaine commence le', + 'vacay.weekStartHint': + 'Choisissez si la semaine commence le lundi ou le dimanche', + 'vacay.carryOver': 'Report', + 'vacay.carryOverHint': + "Reporter automatiquement les jours de vacances restants à l'année suivante", + 'vacay.sharing': 'Partage', + 'vacay.sharingHint': + "Partagez votre plan de vacances avec d'autres utilisateurs TREK", + 'vacay.owner': 'Propriétaire', + 'vacay.shareEmailPlaceholder': "E-mail de l'utilisateur TREK", + 'vacay.shareSuccess': 'Plan partagé avec succès', + 'vacay.shareError': 'Impossible de partager le plan', + 'vacay.dissolve': 'Séparer les calendriers', + 'vacay.dissolveHint': + 'Séparer à nouveau les calendriers. Vos entrées seront conservées.', + 'vacay.dissolveAction': 'Dissoudre', + 'vacay.dissolved': 'Calendrier séparé', + 'vacay.fusedWith': 'Partagé avec', + 'vacay.you': 'vous', + 'vacay.noData': 'Aucune donnée', + 'vacay.changeColor': 'Changer la couleur', + 'vacay.inviteUser': 'Inviter un utilisateur', + 'vacay.inviteHint': + 'Invitez un autre utilisateur TREK à partager un calendrier de vacances combiné.', + 'vacay.selectUser': 'Sélectionner un utilisateur', + 'vacay.sendInvite': "Envoyer l'invitation", + 'vacay.inviteSent': 'Invitation envoyée', + 'vacay.inviteError': "Impossible d'envoyer l'invitation", + 'vacay.pending': 'en attente', + 'vacay.noUsersAvailable': 'Aucun utilisateur disponible', + 'vacay.accept': 'Accepter', + 'vacay.decline': 'Refuser', + 'vacay.acceptFusion': 'Accepter et fusionner', + 'vacay.inviteTitle': 'Demande de fusion', + 'vacay.inviteWantsToFuse': + 'souhaite partager un calendrier de vacances avec vous.', + 'vacay.fuseInfo1': + 'Vous verrez tous les deux toutes les entrées de vacances dans un calendrier partagé.', + 'vacay.fuseInfo2': + "Les deux parties peuvent créer et modifier des entrées pour l'autre.", + 'vacay.fuseInfo3': + 'Les deux parties peuvent supprimer des entrées et modifier les droits aux vacances.', + 'vacay.fuseInfo4': + "Les paramètres comme les jours fériés et les jours d'entreprise sont partagés.", + 'vacay.fuseInfo5': + "La fusion peut être dissoute à tout moment par l'une ou l'autre partie. Vos entrées seront préservées.", + 'vacay.addCalendar': 'Ajouter un calendrier', + 'vacay.calendarColor': 'Couleur', + 'vacay.calendarLabel': 'Libellé', + 'vacay.noCalendars': 'Aucun calendrier', +}; +export default vacay; diff --git a/shared/src/i18n/hu/admin.ts b/shared/src/i18n/hu/admin.ts new file mode 100644 index 00000000..c246fe3b --- /dev/null +++ b/shared/src/i18n/hu/admin.ts @@ -0,0 +1,366 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Értesítések', + 'admin.notifications.hint': + 'Válasszon értesítési csatornát. Egyszerre csak egy lehet aktív.', + 'admin.notifications.none': 'Kikapcsolva', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Értesítési beállítások mentése', + 'admin.notifications.saved': 'Értesítési beállítások mentve', + 'admin.notifications.testWebhook': 'Teszt webhook küldése', + 'admin.notifications.testWebhookSuccess': 'Teszt webhook sikeresen elküldve', + 'admin.notifications.testWebhookFailed': 'Teszt webhook küldése sikertelen', + 'admin.smtp.title': 'E-mail és értesítések', + 'admin.smtp.hint': 'SMTP konfiguráció e-mail értesítések küldéséhez.', + 'admin.smtp.testButton': 'Teszt e-mail küldése', + 'admin.webhook.hint': + 'Értesítések küldése külső webhookra (Discord, Slack stb.).', + 'admin.smtp.testSuccess': 'Teszt e-mail sikeresen elküldve', + 'admin.smtp.testFailed': 'Teszt e-mail küldése sikertelen', + 'admin.title': 'Adminisztráció', + 'admin.subtitle': 'Felhasználókezelés és rendszerbeállítások', + 'admin.tabs.users': 'Felhasználók', + 'admin.tabs.categories': 'Kategóriák', + 'admin.tabs.backup': 'Biztonsági mentés', + 'admin.stats.users': 'Felhasználók', + 'admin.stats.trips': 'Utazások', + 'admin.stats.places': 'Helyek', + 'admin.stats.photos': 'Fotók', + 'admin.stats.files': 'Fájlok', + 'admin.table.user': 'Felhasználó', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Szerepkör', + 'admin.table.created': 'Létrehozva', + 'admin.table.lastLogin': 'Utolsó belépés', + 'admin.table.actions': 'Műveletek', + 'admin.you': '(Te)', + 'admin.editUser': 'Felhasználó szerkesztése', + 'admin.newPassword': 'Új jelszó', + 'admin.newPasswordHint': 'Hagyd üresen a jelenlegi jelszó megtartásához', + 'admin.deleteUser': + '"{name}" felhasználó törlése? Minden utazás véglegesen törlődik.', + 'admin.deleteUserTitle': 'Felhasználó törlése', + 'admin.newPasswordPlaceholder': 'Új jelszó megadása…', + 'admin.toast.loadError': 'Nem sikerült betölteni az admin adatokat', + 'admin.toast.userUpdated': 'Felhasználó frissítve', + 'admin.toast.updateError': 'Nem sikerült frissíteni', + 'admin.toast.userDeleted': 'Felhasználó törölve', + 'admin.toast.deleteError': 'Nem sikerült törölni', + 'admin.toast.cannotDeleteSelf': 'Saját fiók nem törölhető', + 'admin.toast.userCreated': 'Felhasználó létrehozva', + 'admin.toast.createError': 'Nem sikerült létrehozni a felhasználót', + 'admin.toast.fieldsRequired': + 'Felhasználónév, e-mail és jelszó megadása kötelező', + 'admin.createUser': 'Felhasználó létrehozása', + 'admin.invite.title': 'Meghívó linkek', + 'admin.invite.subtitle': + 'Egyszer használatos regisztrációs linkek létrehozása', + 'admin.invite.create': 'Link létrehozása', + 'admin.invite.createAndCopy': 'Létrehozás és másolás', + 'admin.invite.empty': 'Még nincsenek meghívó linkek', + 'admin.invite.maxUses': 'Max. használat', + 'admin.invite.expiry': 'Lejárat', + 'admin.invite.uses': 'felhasználva', + 'admin.invite.expiresAt': 'lejár', + 'admin.invite.createdBy': 'készítette', + 'admin.invite.active': 'Aktív', + 'admin.invite.expired': 'Lejárt', + 'admin.invite.usedUp': 'Elhasználva', + 'admin.invite.copied': 'Meghívó link vágólapra másolva', + 'admin.invite.copyLink': 'Link másolása', + 'admin.invite.deleted': 'Meghívó link törölve', + 'admin.invite.createError': 'Nem sikerült létrehozni a meghívó linket', + 'admin.invite.deleteError': 'Nem sikerült törölni a meghívó linket', + 'admin.tabs.settings': 'Beállítások', + 'admin.allowRegistration': 'Regisztráció engedélyezése', + 'admin.allowRegistrationHint': 'Új felhasználók regisztrálhatják magukat', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Kétlépcsős hitelesítés (2FA) kötelezővé tétele', + 'admin.requireMfaHint': + 'A 2FA nélküli felhasználóknak a Beállításokban kell befejezniük a beállítást az alkalmazás használata előtt.', + 'admin.apiKeys': 'API kulcsok', + 'admin.apiKeysHint': + 'Opcionális. Bővített helyadatokat tesz lehetővé, például fotókat és időjárást.', + 'admin.mapsKey': 'Google Maps API kulcs', + 'admin.mapsKeyHint': + 'Helykereséshez szükséges. Létrehozás: console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'API kulcs nélkül az OpenStreetMap szolgál helykeresésre. Google API kulccsal képek, értékelések és nyitvatartás is betölthetők. Létrehozás: console.cloud.google.com.', + 'admin.recommended': 'Ajánlott', + 'admin.weatherKey': 'OpenWeatherMap API kulcs', + 'admin.weatherKeyHint': 'Időjárás adatokhoz. Ingyenes: openweathermap.org', + 'admin.validateKey': 'Teszt', + 'admin.keyValid': 'Csatlakozva', + 'admin.keyInvalid': 'Érvénytelen', + 'admin.keySaved': 'API kulcsok mentve', + 'admin.oidcTitle': 'Egyszeri bejelentkezés (OIDC)', + 'admin.oidcSubtitle': + 'Bejelentkezés külső szolgáltatókon keresztül, pl. Google, Apple, Authentik vagy Keycloak.', + 'admin.oidcDisplayName': 'Megjelenítendő név', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'A szolgáltató OpenID Connect Issuer URL-je. pl. https://accounts.google.com', + 'admin.oidcSaved': 'OIDC konfiguráció mentve', + 'admin.oidcOnlyMode': 'Jelszavas hitelesítés letiltása', + 'admin.oidcOnlyModeHint': + 'Ha engedélyezve van, csak SSO bejelentkezés lehetséges. A jelszavas bejelentkezés és regisztráció le van tiltva.', + 'admin.fileTypes': 'Engedélyezett fájltípusok', + 'admin.fileTypesHint': + 'Állítsd be, milyen fájltípusokat tölthetnek fel a felhasználók.', + 'admin.fileTypesFormat': + 'Vesszővel elválasztott kiterjesztések (pl. jpg,png,pdf,doc). Használj *-ot az összes típus engedélyezéséhez.', + 'admin.fileTypesSaved': 'Fájltípus-beállítások mentve', + 'admin.placesPhotos.title': 'Helyfotók', + 'admin.placesPhotos.subtitle': + 'Fotók lekérése a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához. A Wikimedia-fotók nem érintettek.', + 'admin.placesAutocomplete.title': 'Hely automatikus kiegészítése', + 'admin.placesAutocomplete.subtitle': + 'A Google Places API használata keresési javaslatokhoz. Tiltsa le az API-kvóta megtakarításához.', + 'admin.placesDetails.title': 'Hely részletei', + 'admin.placesDetails.subtitle': + 'Részletes helyinformációk lekérése (nyitvatartás, értékelés, weboldal) a Google Places API-ból. Tiltsa le az API-kvóta megtakarításához.', + 'admin.bagTracking.title': 'Poggyászkövetés', + 'admin.bagTracking.subtitle': + 'Súly- és táskahozzárendelés engedélyezése csomagolási tételeknél', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Valós idejű üzenetküldés az együttműködéshez', + 'admin.collab.notes.title': 'Jegyzetek', + 'admin.collab.notes.subtitle': 'Megosztott jegyzetek és dokumentumok', + 'admin.collab.polls.title': 'Szavazások', + 'admin.collab.polls.subtitle': 'Csoportos szavazások', + 'admin.collab.whatsnext.title': 'Mi következik', + 'admin.collab.whatsnext.subtitle': + 'Tevékenységjavaslatok és következő lépések', + 'admin.tabs.config': 'Személyre szabás', + 'admin.tabs.defaults': 'Alapértelmezett beállítások', + 'admin.defaultSettings.title': 'Alapértelmezett felhasználói beállítások', + 'admin.defaultSettings.description': + 'Állítson be alapértelmezett értékeket az egész példányra. Azok a felhasználók, akik nem módosítottak egy beállítást, ezeket az értékeket fogják látni. A saját módosításaik mindig elsőbbséget élveznek.', + 'admin.defaultSettings.saved': 'Alapértelmezett mentve', + 'admin.defaultSettings.reset': 'Visszaállítás a beépített alapértelmezésre', + 'admin.defaultSettings.resetToBuiltIn': 'visszaállítás', + 'admin.tabs.templates': 'Csomagolási sablonok', + 'admin.packingTemplates.title': 'Csomagolási sablonok', + 'admin.packingTemplates.subtitle': + 'Újrafelhasználható csomagolási listák létrehozása utazásaidhoz', + 'admin.packingTemplates.create': 'Új sablon', + 'admin.packingTemplates.namePlaceholder': + 'Sablon neve (pl. Tengerparti nyaralás)', + 'admin.packingTemplates.empty': 'Még nincsenek sablonok', + 'admin.packingTemplates.items': 'tétel', + 'admin.packingTemplates.categories': 'kategória', + 'admin.packingTemplates.itemName': 'Tétel neve', + 'admin.packingTemplates.itemCategory': 'Kategória', + 'admin.packingTemplates.categoryName': 'Kategória neve (pl. Ruházat)', + 'admin.packingTemplates.addCategory': 'Kategória hozzáadása', + 'admin.packingTemplates.created': 'Sablon létrehozva', + 'admin.packingTemplates.deleted': 'Sablon törölve', + 'admin.packingTemplates.loadError': 'Nem sikerült betölteni a sablonokat', + 'admin.packingTemplates.createError': 'Nem sikerült létrehozni a sablont', + 'admin.packingTemplates.deleteError': 'Nem sikerült törölni a sablont', + 'admin.packingTemplates.saveError': 'Nem sikerült menteni', + 'admin.tabs.addons': 'Bővítmények', + 'admin.addons.title': 'Bővítmények', + 'admin.addons.subtitle': + 'Funkciók engedélyezése vagy letiltása a TREK testreszabásához.', + 'admin.addons.catalog.packing.name': 'Listák', + 'admin.addons.catalog.packing.description': + 'Csomagolási listák és teendők az utazásaidhoz', + 'admin.addons.catalog.budget.name': 'Költségvetés', + 'admin.addons.catalog.budget.description': + 'Kiadások nyomon követése és az utazási költségvetés tervezése', + 'admin.addons.catalog.documents.name': 'Dokumentumok', + 'admin.addons.catalog.documents.description': + 'Úti dokumentumok tárolása és kezelése', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Személyes szabadságtervező naptárnézettel', + 'admin.addons.catalog.atlas.name': 'Atlasz', + 'admin.addons.catalog.atlas.description': + 'Világtérkép meglátogatott országokkal és utazási statisztikákkal', + 'admin.addons.catalog.collab.name': 'Együttműködés', + 'admin.addons.catalog.collab.description': + 'Valós idejű jegyzetek, szavazások és csevegés az utazás tervezéséhez', + 'admin.addons.catalog.memories.name': 'Fotók (Immich)', + 'admin.addons.catalog.memories.description': + 'Utazási fotók megosztása az Immich példányon keresztül', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol AI asszisztens integrációhoz', + 'admin.addons.subtitleBefore': 'Funkciók engedélyezése vagy letiltása a ', + 'admin.addons.subtitleAfter': ' testreszabásához.', + 'admin.addons.enabled': 'Engedélyezve', + 'admin.addons.disabled': 'Letiltva', + 'admin.addons.type.trip': 'Utazás', + 'admin.addons.type.global': 'Globális', + 'admin.addons.type.integration': 'Integráció', + 'admin.addons.tripHint': 'Fülként érhető el minden utazáson belül', + 'admin.addons.globalHint': 'Önálló szekcióként elérhető a fő navigációban', + 'admin.addons.integrationHint': + 'Háttérszolgáltatások és API integrációk dedikált oldal nélkül', + 'admin.addons.toast.updated': 'Bővítmény frissítve', + 'admin.addons.toast.error': 'Nem sikerült frissíteni a bővítményt', + 'admin.addons.noAddons': 'Nincsenek elérhető bővítmények', + 'admin.weather.title': 'Időjárás adatok', + 'admin.weather.badge': '2026. március 24. óta', + 'admin.weather.description': + 'A TREK az Open-Meteo-t használja időjárás-adatforrásként. Az Open-Meteo egy ingyenes, nyílt forráskódú időjárás-szolgáltatás — nincs szükség API kulcsra.', + 'admin.weather.forecast': '16 napos előrejelzés', + 'admin.weather.forecastDesc': 'Korábban 5 nap volt (OpenWeatherMap)', + 'admin.weather.climate': 'Történelmi klímaadatok', + 'admin.weather.climateDesc': + 'Az elmúlt 85 év átlagai a 16 napos előrejelzésen túli napokhoz', + 'admin.weather.requests': '10 000 kérés / nap', + 'admin.weather.requestsDesc': 'Ingyenes, nincs szükség API kulcsra', + 'admin.weather.locationHint': + 'Az időjárás az adott nap első koordinátákkal rendelkező helye alapján készül. Ha nincs hely hozzárendelve a naphoz, a helylista bármelyik helye szolgál referenciául.', + 'admin.tabs.audit': 'Audit', + 'admin.audit.subtitle': + 'Biztonsági és adminisztrációs események (mentések, felhasználók, 2FA, beállítások).', + 'admin.audit.empty': 'Még nincsenek audit bejegyzések.', + 'admin.audit.refresh': 'Frissítés', + 'admin.audit.loadMore': 'Továbbiak betöltése', + 'admin.audit.showing': '{count} betöltve · {total} összesen', + 'admin.audit.col.time': 'Időpont', + 'admin.audit.col.user': 'Felhasználó', + 'admin.audit.col.action': 'Művelet', + 'admin.audit.col.resource': 'Erőforrás', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Részletek', + 'admin.tabs.mcpTokens': 'MCP hozzáférés', + 'admin.mcpTokens.title': 'MCP hozzáférés', + 'admin.mcpTokens.subtitle': + 'OAuth munkamenetek és API tokenek kezelése az összes felhasználó számára', + 'admin.mcpTokens.sectionTitle': 'API tokenek', + 'admin.mcpTokens.owner': 'Tulajdonos', + 'admin.mcpTokens.tokenName': 'Token neve', + 'admin.mcpTokens.created': 'Létrehozva', + 'admin.mcpTokens.lastUsed': 'Utoljára használva', + 'admin.mcpTokens.never': 'Soha', + 'admin.mcpTokens.empty': 'Még nem hoztak létre MCP tokeneket', + 'admin.mcpTokens.deleteTitle': 'Token törlése', + 'admin.mcpTokens.deleteMessage': + 'Ez a token azonnal érvénytelenítésre kerül. A felhasználó elveszíti az MCP hozzáférést ezen a tokenen keresztül.', + 'admin.mcpTokens.deleteSuccess': 'Token törölve', + 'admin.mcpTokens.deleteError': 'Nem sikerült törölni a tokent', + 'admin.mcpTokens.loadError': 'Nem sikerült betölteni a tokeneket', + 'admin.oauthSessions.sectionTitle': 'OAuth munkamenetek', + 'admin.oauthSessions.clientName': 'Kliens', + 'admin.oauthSessions.owner': 'Tulajdonos', + 'admin.oauthSessions.scopes': 'Jogosultságok', + 'admin.oauthSessions.created': 'Létrehozva', + 'admin.oauthSessions.empty': 'Nincsenek aktív OAuth munkamenetek', + 'admin.oauthSessions.revokeTitle': 'Munkamenet visszavonása', + 'admin.oauthSessions.revokeMessage': + 'Ez az OAuth munkamenet azonnal visszavonásra kerül. A kliens elveszíti az MCP hozzáférést.', + 'admin.oauthSessions.revokeSuccess': 'Munkamenet visszavonva', + 'admin.oauthSessions.revokeError': 'Nem sikerült visszavonni a munkamenetet', + 'admin.oauthSessions.loadError': + 'Nem sikerült betölteni az OAuth munkameneteket', + 'admin.tabs.github': 'GitHub', + 'admin.github.title': 'Frissítési előzmények', + 'admin.github.subtitle': 'Legújabb frissítések: {repo}', + 'admin.github.latest': 'Legújabb', + 'admin.github.prerelease': 'Előzetes kiadás', + 'admin.github.showDetails': 'Részletek megjelenítése', + 'admin.github.hideDetails': 'Részletek elrejtése', + 'admin.github.loadMore': 'Továbbiak betöltése', + 'admin.github.loading': 'Betöltés...', + 'admin.github.error': 'Nem sikerült betölteni a kiadásokat', + 'admin.github.by': 'készítette', + 'admin.github.support': 'Segít fenntartani a TREK fejlesztését', + 'admin.update.available': 'Frissítés elérhető', + 'admin.update.text': + 'A TREK {version} elérhető. Jelenleg a {current} verziót használod.', + 'admin.update.button': 'Megtekintés a GitHubon', + 'admin.update.install': 'Frissítés telepítése', + 'admin.update.confirmTitle': 'Frissítés telepítése?', + 'admin.update.confirmText': + 'A TREK frissítésre kerül {current} verzióról {version} verzióra. A szerver ezután automatikusan újraindul.', + 'admin.update.dataInfo': + 'Minden adat (utazások, felhasználók, API kulcsok, feltöltések, Vacay, Atlas, költségvetések) megmarad.', + 'admin.update.warning': + 'Az alkalmazás az újraindítás alatt rövid ideig nem lesz elérhető.', + 'admin.update.confirm': 'Frissítés most', + 'admin.update.installing': 'Frissítés…', + 'admin.update.success': 'Frissítés telepítve! A szerver újraindul…', + 'admin.update.failed': 'Frissítés sikertelen', + 'admin.update.backupHint': + 'Javasoljuk, hogy frissítés előtt készíts biztonsági mentést.', + 'admin.update.backupLink': 'Biztonsági mentéshez', + 'admin.update.howTo': 'Frissítési útmutató', + 'admin.update.dockerText': + 'A TREK példányod Dockerben fut. A {version} verzióra frissítéshez futtasd a következő parancsokat a szervereden:', + 'admin.update.reloadHint': + 'Kérjük, töltsd újra az oldalt néhány másodperc múlva.', + 'admin.tabs.permissions': 'Jogosultságok', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Az alkalmazáson belüli értesítések mindig aktívak, és globálisan nem kapcsolhatók ki.', + 'admin.notifications.adminWebhookPanel.title': 'Admin webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'Ez a webhook kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói webhookoktól, és automatikusan küld, ha URL van beállítva.', + 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL mentve', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Teszt webhook sikeresen elküldve', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Teszt webhook sikertelen', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Az admin webhook automatikusan küld, ha URL van beállítva', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Lehetővé teszi a felhasználóknak, hogy saját ntfy-témáikat konfigurálják push értesítésekhez. Állítsa be az alapértelmezett szervert alább a felhasználói beállítások előre kitöltéséhez.', + 'admin.notifications.testNtfy': 'Teszt Ntfy küldése', + 'admin.notifications.testNtfySuccess': 'Teszt Ntfy sikeresen elküldve', + 'admin.notifications.testNtfyFailed': 'Teszt Ntfy sikertelen', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Ez az Ntfy téma kizárólag admin értesítésekhez használatos (pl. verziófrissítési figyelmeztetések). Független a felhasználói témáktól, és mindig küld, ha konfigurálva van.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy szerver URL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Alapértelmezett szerverként is szolgál a felhasználói ntfy értesítésekhez. Üresen hagyva ntfy.sh-t használ. A felhasználók felülírhatják saját beállításaikban.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin téma', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': + 'Hozzáférési token (opcionális)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Admin hozzáférési token törölve', + 'admin.notifications.adminNtfyPanel.saved': 'Admin Ntfy beállítások mentve', + 'admin.notifications.adminNtfyPanel.test': 'Teszt Ntfy küldése', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Teszt Ntfy sikeresen elküldve', + 'admin.notifications.adminNtfyPanel.testFailed': 'Teszt Ntfy sikertelen', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Az admin Ntfy mindig küld, ha egy téma konfigurálva van', + 'admin.notifications.adminNotificationsHint': + 'Állítsa be, hogy mely csatornák szállítsák az admin értesítéseket (pl. verziófrissítési figyelmeztetések). A webhook automatikusan küld, ha admin webhook URL van megadva.', + 'admin.notifications.tripReminders.title': 'Utazási emlékeztetők', + 'admin.notifications.tripReminders.hint': + 'Emlékeztető értesítést küld az utazás kezdete előtt (az utazásnál megadott emlékeztető napok szükségesek).', + 'admin.notifications.tripReminders.enabled': + 'Utazási emlékeztetők engedélyezve', + 'admin.notifications.tripReminders.disabled': 'Utazási emlékeztetők letiltva', + 'admin.tabs.notifications': 'Értesítések', + 'admin.addons.catalog.journey.name': 'Útinaplók', + 'admin.addons.catalog.journey.description': + 'Utazáskövetés és útinapló bejelentkezésekkel, fotókkal és napi történetekkel', +}; +export default admin; diff --git a/shared/src/i18n/hu/airport.ts b/shared/src/i18n/hu/airport.ts new file mode 100644 index 00000000..61d13ac4 --- /dev/null +++ b/shared/src/i18n/hu/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Repülőtér kódja vagy város (pl. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/hu/atlas.ts b/shared/src/i18n/hu/atlas.ts new file mode 100644 index 00000000..72996477 --- /dev/null +++ b/shared/src/i18n/hu/atlas.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Utazási lábnyomod a világban', + 'atlas.countries': 'Országok', + 'atlas.trips': 'Utazások', + 'atlas.places': 'Helyek', + 'atlas.unmark': 'Eltávolítás', + 'atlas.confirmMark': 'Megjelölöd ezt az országot meglátogatottként?', + 'atlas.confirmUnmark': + 'Eltávolítod ezt az országot a meglátogatottak listájáról?', + 'atlas.confirmUnmarkRegion': + 'Eltávolítod ezt a régiót a meglátogatottak listájáról?', + 'atlas.markVisited': 'Megjelölés meglátogatottként', + 'atlas.markVisitedHint': 'Ország hozzáadása a meglátogatottak listájához', + 'atlas.markRegionVisitedHint': + 'Régió hozzáadása a meglátogatottak listájához', + 'atlas.addToBucket': 'Hozzáadás a bakancslistához', + 'atlas.addPoi': 'Hely hozzáadása', + 'atlas.searchCountry': 'Ország keresése...', + 'atlas.bucketNamePlaceholder': 'Név (ország, város, hely...)', + 'atlas.month': 'Hónap', + 'atlas.addToBucketHint': 'Mentés meglátogatni kívánt helyként', + 'atlas.bucketWhen': 'Mikor tervezed meglátogatni?', + 'atlas.statsTab': 'Statisztikák', + 'atlas.bucketTab': 'Bakancslista', + 'atlas.addBucket': 'Hozzáadás a bakancslistához', + 'atlas.bucketNotesPlaceholder': 'Jegyzetek (opcionális)', + 'atlas.bucketEmpty': 'A bakancslistád üres', + 'atlas.bucketEmptyHint': 'Adj hozzá helyeket, ahová álmodsz eljutni', + 'atlas.days': 'Napok', + 'atlas.visitedCountries': 'Meglátogatott országok', + 'atlas.cities': 'Városok', + 'atlas.noData': 'Még nincsenek utazási adatok', + 'atlas.noDataHint': + 'Hozz létre egy utazást és adj hozzá helyeket a világtérképhez', + 'atlas.lastTrip': 'Utolsó utazás', + 'atlas.nextTrip': 'Következő utazás', + 'atlas.daysLeft': 'nap van hátra', + 'atlas.streak': 'Sorozat', + 'atlas.year': 'év', + 'atlas.years': 'év', + 'atlas.yearInRow': 'egymást követő év', + 'atlas.yearsInRow': 'egymást követő év', + 'atlas.tripIn': 'utazás', + 'atlas.tripsIn': 'utazás', + 'atlas.since': 'óta', + 'atlas.europe': 'Európa', + 'atlas.asia': 'Ázsia', + 'atlas.northAmerica': 'É-Amerika', + 'atlas.southAmerica': 'D-Amerika', + 'atlas.africa': 'Afrika', + 'atlas.oceania': 'Óceánia', + 'atlas.other': 'Egyéb', + 'atlas.firstVisit': 'Első utazás', + 'atlas.lastVisitLabel': 'Utolsó utazás', + 'atlas.tripSingular': 'Utazás', + 'atlas.tripPlural': 'Utazások', + 'atlas.placeVisited': 'Meglátogatott hely', + 'atlas.placesVisited': 'Meglátogatott helyek', +}; +export default atlas; diff --git a/shared/src/i18n/hu/backup.ts b/shared/src/i18n/hu/backup.ts new file mode 100644 index 00000000..7720e3bb --- /dev/null +++ b/shared/src/i18n/hu/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Adatmentés', + 'backup.subtitle': 'Adatbázis és minden feltöltött fájl', + 'backup.refresh': 'Frissítés', + 'backup.upload': 'Mentés feltöltése', + 'backup.uploading': 'Feltöltés…', + 'backup.create': 'Mentés készítése', + 'backup.creating': 'Készítés…', + 'backup.empty': 'Még nincsenek mentések', + 'backup.createFirst': 'Első mentés készítése', + 'backup.download': 'Letöltés', + 'backup.restore': 'Visszaállítás', + 'backup.confirm.restore': + '"{name}" mentés visszaállítása?\n\nMinden jelenlegi adat a mentéssel lesz helyettesítve.', + 'backup.confirm.uploadRestore': + '"{name}" mentésfájl feltöltése és visszaállítása?\n\nMinden jelenlegi adat felülíródik.', + 'backup.confirm.delete': '"{name}" mentés törlése?', + 'backup.toast.loadError': 'Nem sikerült betölteni a mentéseket', + 'backup.toast.created': 'Mentés sikeresen létrehozva', + 'backup.toast.createError': 'Nem sikerült létrehozni a mentést', + 'backup.toast.restored': 'Mentés visszaállítva. Az oldal újratöltődik…', + 'backup.toast.restoreError': 'Nem sikerült visszaállítani', + 'backup.toast.uploadError': 'Nem sikerült feltölteni', + 'backup.toast.deleted': 'Mentés törölve', + 'backup.toast.deleteError': 'Nem sikerült törölni', + 'backup.toast.downloadError': 'Letöltés sikertelen', + 'backup.toast.settingsSaved': 'Automatikus mentés beállításai mentve', + 'backup.toast.settingsError': 'Nem sikerült menteni a beállításokat', + 'backup.auto.title': 'Automatikus mentés', + 'backup.auto.subtitle': 'Automatikus mentés ütemezés szerint', + 'backup.auto.enable': 'Automatikus mentés engedélyezése', + 'backup.auto.enableHint': + 'A mentések automatikusan készülnek a választott ütemezés szerint', + 'backup.auto.interval': 'Időköz', + 'backup.auto.hour': 'Futtatás időpontja', + 'backup.auto.hourHint': 'Szerver helyi ideje ({format} formátum)', + 'backup.auto.dayOfWeek': 'A hét napja', + 'backup.auto.dayOfMonth': 'A hónap napja', + 'backup.auto.dayOfMonthHint': + '1–28-ra korlátozva az összes hónappal való kompatibilitás érdekében', + 'backup.auto.scheduleSummary': 'Ütemezés', + 'backup.auto.summaryDaily': 'Minden nap {hour}:00-kor', + 'backup.auto.summaryWeekly': 'Minden {day} {hour}:00-kor', + 'backup.auto.summaryMonthly': 'Minden hónap {day}. napján {hour}:00-kor', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Az automatikus mentés Docker környezeti változókon keresztül van konfigurálva. A beállítások módosításához frissítsd a docker-compose.yml fájlt és indítsd újra a konténert.', + 'backup.auto.copyEnv': 'Docker env változók másolása', + 'backup.auto.envCopied': 'Docker env változók vágólapra másolva', + 'backup.auto.keepLabel': 'Régi mentések törlése ennyi idő után', + 'backup.dow.sunday': 'Va', + 'backup.dow.monday': 'Hé', + 'backup.dow.tuesday': 'Ke', + 'backup.dow.wednesday': 'Sze', + 'backup.dow.thursday': 'Csü', + 'backup.dow.friday': 'Pé', + 'backup.dow.saturday': 'Szo', + 'backup.interval.hourly': 'Óránként', + 'backup.interval.daily': 'Naponta', + 'backup.interval.weekly': 'Hetente', + 'backup.interval.monthly': 'Havonta', + 'backup.keep.1day': '1 nap', + 'backup.keep.3days': '3 nap', + 'backup.keep.7days': '7 nap', + 'backup.keep.14days': '14 nap', + 'backup.keep.30days': '30 nap', + 'backup.keep.forever': 'Örökre megőrzés', + 'backup.restoreConfirmTitle': 'Mentés visszaállítása?', + 'backup.restoreWarning': + 'Minden jelenlegi adat (utazások, helyek, felhasználók, feltöltések) véglegesen lecserélődik a mentéssel. Ez a művelet nem vonható vissza.', + 'backup.restoreTip': + 'Tipp: Készíts mentést a jelenlegi állapotról a visszaállítás előtt.', + 'backup.restoreConfirm': 'Igen, visszaállítás', +}; +export default backup; diff --git a/shared/src/i18n/hu/budget.ts b/shared/src/i18n/hu/budget.ts new file mode 100644 index 00000000..87a21a4f --- /dev/null +++ b/shared/src/i18n/hu/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Költségvetés', + 'budget.exportCsv': 'CSV exportálás', + 'budget.emptyTitle': 'Még nincs költségvetés létrehozva', + 'budget.emptyText': + 'Hozz létre kategóriákat és bejegyzéseket az utazási költségvetés tervezéséhez', + 'budget.emptyPlaceholder': 'Kategória neve...', + 'budget.createCategory': 'Kategória létrehozása', + 'budget.category': 'Kategória', + 'budget.categoryName': 'Kategória neve', + 'budget.table.name': 'Név', + 'budget.table.total': 'Összesen', + 'budget.table.persons': 'Személyek', + 'budget.table.days': 'nap', + 'budget.table.perPerson': 'Személyenként', + 'budget.table.perDay': 'Naponta', + 'budget.table.perPersonDay': 'Fő / Nap', + 'budget.table.note': 'Megjegyzés', + 'budget.table.date': 'Dátum', + 'budget.newEntry': 'Új bejegyzés', + 'budget.defaultEntry': 'Új bejegyzés', + 'budget.defaultCategory': 'Új kategória', + 'budget.total': 'Összesen', + 'budget.totalBudget': 'Teljes költségvetés', + 'budget.byCategory': 'Kategóriánként', + 'budget.editTooltip': 'Kattints a szerkesztéshez', + 'budget.linkedToReservation': + 'Foglaláshoz kapcsolva — ott szerkessze a nevet', + 'budget.confirm.deleteCategory': + 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} bejegyzéssel?', + 'budget.deleteCategory': 'Kategória törlése', + 'budget.perPerson': 'Személyenként', + 'budget.paid': 'Fizetve', + 'budget.open': 'Nyitott', + 'budget.noMembers': 'Nincsenek résztvevők hozzárendelve', + 'budget.settlement': 'Elszámolás', + 'budget.settlementInfo': + 'Kattints egy tag avatárjára egy költségvetési tételen a zöld jelöléshez — ez azt jelenti, hogy fizetett. Az elszámolás ezután mutatja, ki kinek mennyivel tartozik.', + 'budget.netBalances': 'Nettó egyenlegek', + 'budget.categoriesLabel': 'kategóriák', +}; +export default budget; diff --git a/shared/src/i18n/hu/categories.ts b/shared/src/i18n/hu/categories.ts new file mode 100644 index 00000000..cc5dafbb --- /dev/null +++ b/shared/src/i18n/hu/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Kategóriák', + 'categories.subtitle': 'Helyek kategóriáinak kezelése', + 'categories.new': 'Új kategória', + 'categories.empty': 'Még nincsenek kategóriák', + 'categories.namePlaceholder': 'Kategória neve', + 'categories.icon': 'Ikon', + 'categories.color': 'Szín', + 'categories.customColor': 'Egyéni szín kiválasztása', + 'categories.preview': 'Előnézet', + 'categories.defaultName': 'Kategória', + 'categories.update': 'Frissítés', + 'categories.create': 'Létrehozás', + 'categories.confirm.delete': + 'Kategória törlése? Az ebben a kategóriában lévő helyek nem törlődnek.', + 'categories.toast.loadError': 'Nem sikerült betölteni a kategóriákat', + 'categories.toast.nameRequired': 'Kérjük, adj meg egy nevet', + 'categories.toast.updated': 'Kategória frissítve', + 'categories.toast.created': 'Kategória létrehozva', + 'categories.toast.saveError': 'Nem sikerült menteni', + 'categories.toast.deleted': 'Kategória törölve', + 'categories.toast.deleteError': 'Nem sikerült törölni', +}; +export default categories; diff --git a/shared/src/i18n/hu/collab.ts b/shared/src/i18n/hu/collab.ts new file mode 100644 index 00000000..f5e35aaa --- /dev/null +++ b/shared/src/i18n/hu/collab.ts @@ -0,0 +1,76 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Csevegés', + 'collab.tabs.notes': 'Jegyzetek', + 'collab.tabs.polls': 'Szavazások', + 'collab.whatsNext.title': 'Mi következik', + 'collab.whatsNext.today': 'Ma', + 'collab.whatsNext.tomorrow': 'Holnap', + 'collab.whatsNext.empty': 'Nincsenek közelgő tevékenységek', + 'collab.whatsNext.until': '-ig', + 'collab.whatsNext.emptyHint': + 'Az időponttal rendelkező tevékenységek itt jelennek meg', + 'collab.chat.send': 'Küldés', + 'collab.chat.placeholder': 'Üzenet írása...', + 'collab.chat.empty': 'Kezdd el a beszélgetést', + 'collab.chat.emptyHint': + 'Az üzenetek az utazás minden tagjával meg vannak osztva', + 'collab.chat.emptyDesc': + 'Oszd meg ötleteidet, terveidet és híreidet az utazócsoportoddal', + 'collab.chat.today': 'Ma', + 'collab.chat.yesterday': 'Tegnap', + 'collab.chat.deletedMessage': 'törölt egy üzenetet', + 'collab.chat.reply': 'Válasz', + 'collab.chat.loadMore': 'Korábbi üzenetek betöltése', + 'collab.chat.justNow': 'éppen most', + 'collab.chat.minutesAgo': '{n} perce', + 'collab.chat.hoursAgo': '{n} órája', + 'collab.notes.title': 'Jegyzetek', + 'collab.notes.new': 'Új jegyzet', + 'collab.notes.empty': 'Még nincsenek jegyzetek', + 'collab.notes.emptyHint': 'Rögzítsd az ötleteidet és terveidet', + 'collab.notes.all': 'Összes', + 'collab.notes.titlePlaceholder': 'Jegyzet címe', + 'collab.notes.contentPlaceholder': 'Írj valamit...', + 'collab.notes.categoryPlaceholder': 'Kategória', + 'collab.notes.newCategory': 'Új kategória...', + 'collab.notes.category': 'Kategória', + 'collab.notes.noCategory': 'Nincs kategória', + 'collab.notes.color': 'Szín', + 'collab.notes.save': 'Mentés', + 'collab.notes.cancel': 'Mégse', + 'collab.notes.edit': 'Szerkesztés', + 'collab.notes.delete': 'Törlés', + 'collab.notes.pin': 'Kitűzés', + 'collab.notes.unpin': 'Kitűzés eltávolítása', + 'collab.notes.daysAgo': '{n} napja', + 'collab.notes.categorySettings': 'Kategóriák kezelése', + 'collab.notes.create': 'Létrehozás', + 'collab.notes.website': 'Weboldal', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Fájlok csatolása', + 'collab.notes.noCategoriesYet': 'Még nincsenek kategóriák', + 'collab.notes.emptyDesc': 'Hozz létre egy jegyzetet a kezdéshez', + 'collab.polls.title': 'Szavazások', + 'collab.polls.new': 'Új szavazás', + 'collab.polls.empty': 'Még nincsenek szavazások', + 'collab.polls.emptyHint': 'Kérdezd meg a csoportot és szavazzatok együtt', + 'collab.polls.question': 'Kérdés', + 'collab.polls.questionPlaceholder': 'Mit csináljunk?', + 'collab.polls.addOption': 'Opció hozzáadása', + 'collab.polls.optionPlaceholder': '{n}. opció', + 'collab.polls.create': 'Szavazás létrehozása', + 'collab.polls.close': 'Lezárás', + 'collab.polls.closed': 'Lezárva', + 'collab.polls.votes': '{n} szavazat', + 'collab.polls.vote': '{n} szavazat', + 'collab.polls.multipleChoice': 'Többszörös választás', + 'collab.polls.multiChoice': 'Többszörös választás', + 'collab.polls.deadline': 'Határidő', + 'collab.polls.option': 'Opció', + 'collab.polls.options': 'Opciók', + 'collab.polls.delete': 'Törlés', + 'collab.polls.closedSection': 'Lezárva', +}; +export default collab; diff --git a/shared/src/i18n/hu/common.ts b/shared/src/i18n/hu/common.ts new file mode 100644 index 00000000..245cb8b3 --- /dev/null +++ b/shared/src/i18n/hu/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Mentés', + 'common.showMore': 'Továbbiak', + 'common.showLess': 'Kevesebb', + 'common.cancel': 'Mégse', + 'common.clear': 'Törlés', + 'common.delete': 'Törlés', + 'common.edit': 'Szerkesztés', + 'common.add': 'Hozzáadás', + 'common.loading': 'Betöltés...', + 'common.import': 'Importálás', + 'common.select': 'Kiválaszt', + 'common.selectAll': 'Mindet kiválaszt', + 'common.deselectAll': 'Összes kijelölés megszüntetése', + 'common.error': 'Hiba', + 'common.unknownError': 'Ismeretlen hiba', + 'common.tooManyAttempts': + 'Túl sok próbálkozás. Kérjük, próbálja újra később.', + 'common.back': 'Vissza', + 'common.all': 'Összes', + 'common.close': 'Bezárás', + 'common.open': 'Megnyitás', + 'common.upload': 'Feltöltés', + 'common.search': 'Keresés', + 'common.confirm': 'Megerősítés', + 'common.ok': 'OK', + 'common.yes': 'Igen', + 'common.no': 'Nem', + 'common.or': 'vagy', + 'common.none': 'Nincs', + 'common.date': 'Dátum', + 'common.rename': 'Átnevezés', + 'common.discardChanges': 'Változtatások elvetése', + 'common.discard': 'Elveti', + 'common.name': 'Név', + 'common.email': 'E-mail', + 'common.password': 'Jelszó', + 'common.saving': 'Mentés...', + 'common.expand': 'Kibontás', + 'common.collapse': 'Összecsukás', + 'common.saved': 'Mentve', + 'common.update': 'Frissítés', + 'common.change': 'Módosítás', + 'common.uploading': 'Feltöltés…', + 'common.backToPlanning': 'Vissza a tervezéshez', + 'common.reset': 'Visszaállítás', + 'common.copy': 'Másolás', + 'common.copied': 'Másolva', + 'common.justNow': 'az imént', + 'common.hoursAgo': '{count} órája', + 'common.daysAgo': '{count} napja', +}; +export default common; diff --git a/shared/src/i18n/hu/dashboard.ts b/shared/src/i18n/hu/dashboard.ts new file mode 100644 index 00000000..44ed2bda --- /dev/null +++ b/shared/src/i18n/hu/dashboard.ts @@ -0,0 +1,108 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Utazásaim', + 'dashboard.subtitle.loading': 'Utazások betöltése...', + 'dashboard.subtitle.trips': '{count} utazás ({archived} archivált)', + 'dashboard.subtitle.empty': 'Indítsd el az első utazásodat', + 'dashboard.subtitle.activeOne': '{count} aktív utazás', + 'dashboard.subtitle.activeMany': '{count} aktív utazás', + 'dashboard.subtitle.archivedSuffix': ' · {count} archivált', + 'dashboard.newTrip': 'Új utazás', + 'dashboard.gridView': 'Rácsnézet', + 'dashboard.listView': 'Listanézet', + 'dashboard.currency': 'Pénznem', + 'dashboard.timezone': 'Időzónák', + 'dashboard.localTime': 'Helyi', + 'dashboard.timezoneCustomTitle': 'Egyéni időzóna', + 'dashboard.timezoneCustomLabelPlaceholder': 'Címke (opcionális)', + 'dashboard.timezoneCustomTzPlaceholder': 'pl. America/New_York', + 'dashboard.timezoneCustomAdd': 'Hozzáadás', + 'dashboard.timezoneCustomErrorEmpty': 'Adj meg egy időzóna-azonosítót', + 'dashboard.timezoneCustomErrorInvalid': + 'Érvénytelen időzóna. Használj Europe/Berlin formátumot', + 'dashboard.timezoneCustomErrorDuplicate': 'Már hozzáadva', + 'dashboard.emptyTitle': 'Még nincsenek utazások', + 'dashboard.emptyText': + 'Hozd létre az első utazásodat, és kezdj el tervezni helyeket, napi programokat és csomagolási listákat.', + 'dashboard.emptyButton': 'Első utazás létrehozása', + 'dashboard.nextTrip': 'Következő utazás', + 'dashboard.shared': 'Megosztott', + 'dashboard.sharedBy': 'Megosztotta: {name}', + 'dashboard.days': 'nap', + 'dashboard.places': 'hely', + 'dashboard.members': 'Útitársak', + 'dashboard.archive': 'Archiválás', + 'dashboard.copyTrip': 'Másolás', + 'dashboard.copySuffix': 'másolat', + 'dashboard.restore': 'Visszaállítás', + 'dashboard.archived': 'Archivált', + 'dashboard.status.ongoing': 'Folyamatban', + 'dashboard.status.today': 'Ma', + 'dashboard.status.tomorrow': 'Holnap', + 'dashboard.status.past': 'Múlt', + 'dashboard.status.daysLeft': 'Még {count} nap', + 'dashboard.toast.loadError': 'Nem sikerült betölteni az utazásokat', + 'dashboard.toast.created': 'Utazás sikeresen létrehozva!', + 'dashboard.toast.createError': 'Nem sikerült létrehozni', + 'dashboard.toast.updated': 'Utazás frissítve!', + 'dashboard.toast.updateError': 'Nem sikerült frissíteni', + 'dashboard.toast.deleted': 'Utazás törölve', + 'dashboard.toast.deleteError': 'Nem sikerült törölni', + 'dashboard.toast.archived': 'Utazás archiválva', + 'dashboard.toast.archiveError': 'Nem sikerült archiválni', + 'dashboard.toast.restored': 'Utazás visszaállítva', + 'dashboard.toast.restoreError': 'Nem sikerült visszaállítani', + 'dashboard.toast.copied': 'Utazás másolva!', + 'dashboard.toast.copyError': 'Nem sikerült másolni az utazást', + 'dashboard.confirm.delete': + '"{title}" utazás törlése? Minden hely és terv véglegesen törlődik.', + 'dashboard.editTrip': 'Utazás szerkesztése', + 'dashboard.createTrip': 'Új utazás létrehozása', + 'dashboard.tripTitle': 'Cím', + 'dashboard.tripTitlePlaceholder': 'pl. Nyár Japánban', + 'dashboard.tripDescription': 'Leírás', + 'dashboard.tripDescriptionPlaceholder': 'Miről szól ez az utazás?', + 'dashboard.startDate': 'Kezdő dátum', + 'dashboard.endDate': 'Záró dátum', + 'dashboard.dayCount': 'Napok száma', + 'dashboard.dayCountHint': + 'Hány napot tervezzen, ha nincsenek utazási dátumok megadva.', + 'dashboard.noDateHint': + 'Nincs dátum megadva — 7 alapértelmezett nap jön létre. Ezt bármikor módosíthatod.', + 'dashboard.coverImage': 'Borítókép', + 'dashboard.addCoverImage': 'Borítókép hozzáadása', + 'dashboard.addMembers': 'Útitársak', + 'dashboard.addMember': 'Tag hozzáadása', + 'dashboard.coverSaved': 'Borítókép mentve', + 'dashboard.coverUploadError': 'Feltöltés sikertelen', + 'dashboard.coverRemoveError': 'Eltávolítás sikertelen', + 'dashboard.titleRequired': 'A cím megadása kötelező', + 'dashboard.endDateError': 'A záró dátumnak a kezdő dátum után kell lennie', + 'dashboard.greeting.morning': 'Jó reggelt,', + 'dashboard.greeting.afternoon': 'Jó napot,', + 'dashboard.greeting.evening': 'Jó estét,', + 'dashboard.mobile.liveNow': 'Most élőben', + 'dashboard.mobile.tripProgress': 'Út előrehaladása', + 'dashboard.mobile.daysLeft': 'még {count} nap', + 'dashboard.mobile.places': 'Helyszínek', + 'dashboard.mobile.buddies': 'Útitársak', + 'dashboard.mobile.newTrip': 'Új út', + 'dashboard.mobile.currency': 'Pénznem', + 'dashboard.mobile.timezone': 'Időzóna', + 'dashboard.mobile.upcomingTrips': 'Közelgő utak', + 'dashboard.mobile.yourTrips': 'Utaid', + 'dashboard.mobile.trips': 'út', + 'dashboard.mobile.starts': 'Kezdés', + 'dashboard.mobile.duration': 'Időtartam', + 'dashboard.mobile.day': 'nap', + 'dashboard.mobile.days': 'nap', + 'dashboard.mobile.ongoing': 'Folyamatban', + 'dashboard.mobile.startsToday': 'Ma kezdődik', + 'dashboard.mobile.tomorrow': 'Holnap', + 'dashboard.mobile.inDays': '{count} nap múlva', + 'dashboard.mobile.inMonths': '{count} hónap múlva', + 'dashboard.mobile.completed': 'Befejezett', + 'dashboard.mobile.currencyConverter': 'Pénznemváltó', +}; +export default dashboard; diff --git a/shared/src/i18n/hu/day.ts b/shared/src/i18n/hu/day.ts new file mode 100644 index 00000000..8656592a --- /dev/null +++ b/shared/src/i18n/hu/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Csapadékvalószínűség', + 'day.precipitation': 'Csapadék', + 'day.wind': 'Szél', + 'day.sunrise': 'Napkelte', + 'day.sunset': 'Napnyugta', + 'day.hourlyForecast': 'Óránkénti előrejelzés', + 'day.climateHint': + 'Történelmi átlagok — valós előrejelzés a dátum előtti 16 napon belül érhető el.', + 'day.noWeather': + 'Nem állnak rendelkezésre időjárási adatok. Adj hozzá egy helyet koordinátákkal.', + 'day.overview': 'Napi áttekintés', + 'day.accommodation': 'Szállás', + 'day.addAccommodation': 'Szállás hozzáadása', + 'day.hotelDayRange': 'Alkalmazás napokra', + 'day.noPlacesForHotel': 'Először adj hozzá helyeket az utazásodhoz', + 'day.allDays': 'Összes', + 'day.checkIn': 'Bejelentkezés', + 'day.checkInUntil': 'Eddig', + 'day.checkOut': 'Kijelentkezés', + 'day.confirmation': 'Visszaigazolás', + 'day.editAccommodation': 'Szállás szerkesztése', + 'day.reservations': 'Foglalások', +}; +export default day; diff --git a/shared/src/i18n/hu/dayplan.ts b/shared/src/i18n/hu/dayplan.ts new file mode 100644 index 00000000..3d9d54da --- /dev/null +++ b/shared/src/i18n/hu/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Naptár exportálása (ICS)', + 'dayplan.emptyDay': 'Nincs tervezett hely erre a napra', + 'dayplan.addNote': 'Jegyzet hozzáadása', + 'dayplan.editNote': 'Jegyzet szerkesztése', + 'dayplan.noteAdd': 'Jegyzet hozzáadása', + 'dayplan.noteEdit': 'Jegyzet szerkesztése', + 'dayplan.noteTitle': 'Jegyzet', + 'dayplan.noteSubtitle': 'Napi jegyzet', + 'dayplan.totalCost': 'Összköltség', + 'dayplan.days': 'nap', + 'dayplan.dayN': '{n}. nap', + 'dayplan.calculating': 'Számítás...', + 'dayplan.route': 'Útvonal', + 'dayplan.optimize': 'Optimalizálás', + 'dayplan.optimized': 'Útvonal optimalizálva', + 'dayplan.routeError': 'Nem sikerült kiszámítani az útvonalat', + 'dayplan.toast.needTwoPlaces': + 'Legalább két hely szükséges az útvonal-optimalizáláshoz', + 'dayplan.toast.routeOptimized': 'Útvonal optimalizálva', + 'dayplan.toast.noGeoPlaces': + 'Nem találhatók koordinátákkal rendelkező helyek az útvonalszámításhoz', + 'dayplan.confirmed': 'Megerősítve', + 'dayplan.pendingRes': 'Függőben', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Napi terv exportálása PDF-be', + 'dayplan.pdfError': 'Nem sikerült a PDF exportálás', + 'dayplan.cannotReorderTransport': + 'A rögzített időpontú foglalások nem rendezhetők át', + 'dayplan.confirmRemoveTimeTitle': 'Időpont eltávolítása?', + 'dayplan.confirmRemoveTimeBody': + 'Ennek a helynek rögzített időpontja van ({time}). Az áthelyezéssel az időpont eltávolítódik és szabad rendezés válik lehetővé.', + 'dayplan.confirmRemoveTimeAction': 'Időpont eltávolítása és áthelyezés', + 'dayplan.cannotDropOnTimed': + 'Elemek nem helyezhetők rögzített időpontú bejegyzések közé', + 'dayplan.cannotBreakChronology': + 'Ez megbontaná az időzített elemek és foglalások időrendi sorrendjét', + 'dayplan.mobile.addPlace': 'Helyszín hozzáadása', + 'dayplan.mobile.searchPlaces': 'Helyszínek keresése...', + 'dayplan.mobile.allAssigned': 'Minden helyszín kiosztva', + 'dayplan.mobile.noMatch': 'Nincs találat', + 'dayplan.mobile.createNew': 'Új helyszín létrehozása', +}; +export default dayplan; diff --git a/shared/src/i18n/hu/externalNotifications.ts b/shared/src/i18n/hu/externalNotifications.ts new file mode 100644 index 00000000..da4c3d9f --- /dev/null +++ b/shared/src/i18n/hu/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const hu: NotificationLocale = { + email: { + footer: + 'Ezt az értesítést azért kaptad, mert engedélyezted az értesítéseket a TREK-ben.', + manage: 'Beállítások kezelése', + madeWith: 'Made with', + openTrek: 'TREK megnyitása', + }, + events: { + trip_invite: (p) => ({ + title: `Meghívó a(z) "${p.trip}" utazásra`, + body: `${p.actor} meghívta ${p.invitee || 'egy tagot'} a(z) "${p.trip}" utazásra.`, + }), + booking_change: (p) => ({ + title: `Új foglalás: ${p.booking}`, + body: `${p.actor} hozzáadott egy "${p.booking}" (${p.type}) foglalást a(z) "${p.trip}" utazáshoz.`, + }), + trip_reminder: (p) => ({ + title: `Utazás emlékeztető: ${p.trip}`, + body: `A(z) "${p.trip}" utazás hamarosan kezdődik!`, + }), + todo_due: (p) => ({ + title: `Teendő esedékes: ${p.todo}`, + body: `"${p.todo}" (${p.trip}) határideje: ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion meghívó', + body: `${p.actor} meghívott a nyaralási tervek összevonásához. Nyissa meg a TREK-et az elfogadáshoz vagy elutasításhoz.`, + }), + photos_shared: (p) => ({ + title: `${p.count} fotó megosztva`, + body: `${p.actor} ${p.count} fotót osztott meg a(z) "${p.trip}" utazásban.`, + }), + collab_message: (p) => ({ + title: `Új üzenet a(z) "${p.trip}" utazásban`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Csomagolás: ${p.category}`, + body: `${p.actor} hozzárendelte Önt a "${p.category}" csomagolási kategóriához a(z) "${p.trip}" utazásban.`, + }), + version_available: (p) => ({ + title: 'Új TREK verzió érhető el', + body: `A TREK ${p.version} elérhető. Látogasson el az adminisztrációs panelre a frissítéshez.`, + }), + synology_session_cleared: () => ({ + title: 'Synology munkamenet törölve', + body: 'A Synology fiókja vagy URL-je megváltozott. Kijelentkeztek a Synology Photos-ból.', + }), + }, + passwordReset: { + subject: 'Jelszó visszaállítása', + greeting: 'Szia', + body: 'Kérést kaptunk a TREK-fiókod jelszavának visszaállítására. Kattints az alábbi gombra az új jelszó beállításához.', + ctaIntro: 'Jelszó visszaállítása', + expiry: 'Ez a link 60 perc után lejár.', + ignore: + 'Ha nem te kérted ezt, nyugodtan hagyd figyelmen kívül ezt az e-mailt — a jelszavad változatlan marad.', + }, +}; + +export default hu; diff --git a/shared/src/i18n/hu/files.ts b/shared/src/i18n/hu/files.ts new file mode 100644 index 00000000..c924303e --- /dev/null +++ b/shared/src/i18n/hu/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Fájlok', + 'files.pageTitle': 'Fájlok és dokumentumok', + 'files.subtitle': '{count} fájl a következőhöz: {trip}', + 'files.download': 'Letöltés', + 'files.openError': 'A fájl megnyitása sikertelen', + 'files.downloadPdf': 'PDF letöltése', + 'files.count': '{count} fájl', + 'files.countSingular': '1 fájl', + 'files.uploaded': '{count} feltöltve', + 'files.uploadError': 'Feltöltés sikertelen', + 'files.dropzone': 'Húzd ide a fájlokat', + 'files.dropzoneHint': 'vagy kattints a böngészéshez', + 'files.allowedTypes': + 'Képek, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Feltöltés...', + 'files.filterAll': 'Összes', + 'files.filterPdf': 'PDF-ek', + 'files.filterImages': 'Képek', + 'files.filterDocs': 'Dokumentumok', + 'files.filterCollab': 'Közös jegyzetek', + 'files.sourceCollab': 'Közös jegyzetekből', + 'files.empty': 'Még nincsenek fájlok', + 'files.emptyHint': 'Tölts fel fájlokat az utazásodhoz', + 'files.openTab': 'Megnyitás új lapon', + 'files.confirm.delete': 'Biztosan törölni szeretnéd ezt a fájlt?', + 'files.toast.deleted': 'Fájl törölve', + 'files.toast.deleteError': 'Nem sikerült törölni a fájlt', + 'files.sourcePlan': 'Napi terv', + 'files.sourceBooking': 'Foglalás', + 'files.sourceTransport': 'Közlekedés', + 'files.attach': 'Csatolás', + 'files.pasteHint': 'Képeket a vágólapról is beillesztheted (Ctrl+V)', + 'files.trash': 'Kuka', + 'files.trashEmpty': 'A kuka üres', + 'files.emptyTrash': 'Kuka ürítése', + 'files.restore': 'Visszaállítás', + 'files.star': 'Csillag', + 'files.unstar': 'Csillag eltávolítása', + 'files.assign': 'Hozzárendelés', + 'files.assignTitle': 'Fájl hozzárendelése', + 'files.assignPlace': 'Hely', + 'files.assignBooking': 'Foglalás', + 'files.assignTransport': 'Közlekedés', + 'files.unassigned': 'Nincs hozzárendelve', + 'files.unlink': 'Kapcsolat eltávolítása', + 'files.toast.trashed': 'Kukába helyezve', + 'files.toast.restored': 'Fájl visszaállítva', + 'files.toast.trashEmptied': 'Kuka kiürítve', + 'files.toast.assigned': 'Fájl hozzárendelve', + 'files.toast.assignError': 'Hozzárendelés sikertelen', + 'files.toast.restoreError': 'Visszaállítás sikertelen', + 'files.confirm.permanentDelete': + 'Véglegesen törlöd ezt a fájlt? Ez nem vonható vissza.', + 'files.confirm.emptyTrash': + 'Véglegesen törlöd az összes kukába helyezett fájlt? Ez nem vonható vissza.', + 'files.noteLabel': 'Megjegyzés', + 'files.notePlaceholder': 'Megjegyzés hozzáadása...', +}; +export default files; diff --git a/shared/src/i18n/hu/index.ts b/shared/src/i18n/hu/index.ts new file mode 100644 index 00000000..43c637d6 --- /dev/null +++ b/shared/src/i18n/hu/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...collab, + ...memories, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/hu/inspector.ts b/shared/src/i18n/hu/inspector.ts new file mode 100644 index 00000000..896fb6f9 --- /dev/null +++ b/shared/src/i18n/hu/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Nyitva', + 'inspector.closed': 'Zárva', + 'inspector.openingHours': 'Nyitvatartás', + 'inspector.showHours': 'Nyitvatartás megjelenítése', + 'inspector.files': 'Fájlok', + 'inspector.filesCount': '{count} fájl', + 'inspector.removeFromDay': 'Eltávolítás a napról', + 'inspector.remove': 'Eltávolítás', + 'inspector.addToDay': 'Hozzáadás a naphoz', + 'inspector.confirmedRes': 'Megerősített foglalás', + 'inspector.pendingRes': 'Függőben lévő foglalás', + 'inspector.google': 'Megnyitás a Google Térképben', + 'inspector.website': 'Weboldal megnyitása', + 'inspector.addRes': 'Foglalás', + 'inspector.editRes': 'Foglalás szerkesztése', + 'inspector.participants': 'Résztvevők', + 'inspector.trackStats': 'Útvonal adatok', +}; +export default inspector; diff --git a/shared/src/i18n/hu/journey.ts b/shared/src/i18n/hu/journey.ts new file mode 100644 index 00000000..01bf659c --- /dev/null +++ b/shared/src/i18n/hu/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Utak keresése…', + 'journey.search.noResults': 'Nincs „{query}" kifejezéssel egyező út', + 'journey.title': 'Útinaplók', + 'journey.subtitle': 'Kövesse nyomon utazásait valós időben', + 'journey.new': 'Új útinapló', + 'journey.create': 'Létrehozás', + 'journey.titlePlaceholder': 'Hová utazol?', + 'journey.empty': 'Még nincsenek útinaplók', + 'journey.emptyHint': 'Kezdd el dokumentálni a következő utazásod', + 'journey.deleted': 'Útinapló törölve', + 'journey.createError': 'Nem sikerült létrehozni az útinaplót', + 'journey.deleteError': 'Nem sikerült törölni az útinaplót', + 'journey.deleteConfirmTitle': 'Törlés', + 'journey.deleteConfirmMessage': + 'Törlöd a(z) „{title}" útinaplót? Ez nem vonható vissza.', + 'journey.deleteConfirmGeneric': 'Biztosan törölni szeretnéd?', + 'journey.notFound': 'Útinapló nem található', + 'journey.photos': 'Fotók', + 'journey.timelineEmpty': 'Még nincsenek megállók', + 'journey.timelineEmptyHint': + 'Adj hozzá egy bejelentkezést vagy írj naplóbejegyzést a kezdéshez', + 'journey.status.draft': 'Vázlat', + 'journey.status.active': 'Aktív', + 'journey.status.completed': 'Befejezett', + 'journey.status.upcoming': 'Közelgő', + 'journey.status.archived': 'Archivált', + 'journey.checkin.add': 'Bejelentkezés', + 'journey.checkin.namePlaceholder': 'Helyszín neve', + 'journey.checkin.notesPlaceholder': 'Jegyzetek (opcionális)', + 'journey.checkin.save': 'Mentés', + 'journey.checkin.error': 'Nem sikerült menteni a bejelentkezést', + 'journey.entry.add': 'Napló', + 'journey.entry.edit': 'Bejegyzés szerkesztése', + 'journey.entry.titlePlaceholder': 'Cím (opcionális)', + 'journey.entry.bodyPlaceholder': 'Mi történt ma?', + 'journey.entry.save': 'Mentés', + 'journey.entry.error': 'Nem sikerült menteni a bejegyzést', + 'journey.photo.add': 'Fotó', + 'journey.photo.uploadError': 'A feltöltés sikertelen', + 'journey.share.share': 'Megosztás', + 'journey.share.public': 'Nyilvános', + 'journey.share.linkCopied': 'Nyilvános link másolva', + 'journey.share.disabled': 'Nyilvános megosztás letiltva', + 'journey.editor.titlePlaceholder': 'Adj nevet ennek a pillanatnak...', + 'journey.editor.bodyPlaceholder': 'Meséld el ennek a napnak a történetét...', + 'journey.editor.placePlaceholder': 'Helyszín (opcionális)', + 'journey.editor.tagsPlaceholder': + 'Címkék: rejtett kincs, legjobb étel, újra meglátogatandó...', + 'journey.visibility.private': 'Privát', + 'journey.visibility.shared': 'Megosztott', + 'journey.visibility.public': 'Nyilvános', + 'journey.emptyState.title': 'Itt kezdődik a történeted', + 'journey.emptyState.subtitle': + 'Jelentkezz be egy helyszínen vagy írd meg az első naplóbejegyzésed', + 'journey.frontpage.subtitle': + 'Alakítsd utazásaidat történetekké, amelyeket soha nem felejtesz el', + 'journey.frontpage.createJourney': 'Útinapló létrehozása', + 'journey.frontpage.activeJourney': 'Aktív útinapló', + 'journey.frontpage.allJourneys': 'Összes útinapló', + 'journey.frontpage.journeys': 'útinapló', + 'journey.frontpage.createNew': 'Új útinapló létrehozása', + 'journey.frontpage.createNewSub': + 'Válassz utakat, írj történeteket, oszd meg kalandjaidat', + 'journey.frontpage.live': 'Élő', + 'journey.frontpage.synced': 'Szinkronizálva', + 'journey.frontpage.continueWriting': 'Írás folytatása', + 'journey.frontpage.updated': 'Frissítve: {time}', + 'journey.frontpage.suggestionLabel': 'Az út épp véget ért', + 'journey.frontpage.suggestionText': + 'Alakítsd a(z) {title} útinaplóvá', + 'journey.frontpage.dismiss': 'Elvetés', + 'journey.frontpage.journeyName': 'Útinapló neve', + 'journey.frontpage.namePlaceholder': 'pl. Délkelet-Ázsia 2026', + 'journey.frontpage.selectTrips': 'Utak kiválasztása', + 'journey.frontpage.tripsSelected': 'út kiválasztva', + 'journey.frontpage.trips': 'út', + 'journey.frontpage.placesImported': 'helyszín importálásra kerül', + 'journey.frontpage.places': 'helyszín', + 'journey.detail.backToJourney': 'Vissza az útinaplóhoz', + 'journey.detail.syncedWithTrips': 'Szinkronizálva az utakkal', + 'journey.detail.addEntry': 'Bejegyzés hozzáadása', + 'journey.detail.newEntry': 'Új bejegyzés', + 'journey.detail.editEntry': 'Bejegyzés szerkesztése', + 'journey.detail.noEntries': 'Még nincsenek bejegyzések', + 'journey.detail.noEntriesHint': + 'Adj hozzá egy utat a vázlatos bejegyzések elkészítéséhez', + 'journey.detail.noPhotos': 'Még nincsenek fotók', + 'journey.detail.noPhotosHint': + 'Tölts fel fotókat a bejegyzésekhez vagy böngészd az Immich/Synology könyvtárat', + 'journey.detail.journeyStats': 'Útinapló statisztika', + 'journey.detail.syncedTrips': 'Szinkronizált utak', + 'journey.detail.noTripsLinked': 'Még nincsenek kapcsolt utak', + 'journey.detail.contributors': 'Közreműködők', + 'journey.detail.readMore': 'Tovább olvasás', + 'journey.detail.prosCons': 'Előnyök és hátrányok', + 'journey.detail.photos': 'fotók', + 'journey.detail.day': '{number}. nap', + 'journey.detail.places': 'helyek', + 'journey.stats.days': 'Napok', + 'journey.stats.cities': 'Városok', + 'journey.stats.entries': 'Bejegyzések', + 'journey.stats.photos': 'Fotók', + 'journey.stats.places': 'Helyszínek', + 'journey.skeletons.show': 'Javaslatok megjelenítése', + 'journey.skeletons.hide': 'Javaslatok elrejtése', + 'journey.verdict.lovedIt': 'Imádtam', + 'journey.verdict.couldBeBetter': 'Lehetne jobb', + 'journey.synced.places': 'helyszín', + 'journey.synced.synced': 'szinkronizálva', + 'journey.editor.discardChangesConfirm': + 'Mentetlen módosításaid vannak. Elveted?', + 'journey.editor.uploadFailed': 'A fotók feltöltése sikertelen', + 'journey.editor.uploadPhotos': 'Fotók feltöltése', + 'journey.editor.uploading': 'Feltöltés...', + 'journey.editor.uploadingProgress': 'Feltöltés {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} / {total} fotó sikertelen — mentsd el újra a próbálkozáshoz', + 'journey.editor.fromGallery': 'Galériából', + 'journey.editor.allPhotosAdded': 'Minden fotó már hozzáadva', + 'journey.editor.writeStory': 'Írd meg a történeted...', + 'journey.editor.prosCons': 'Előnyök és hátrányok', + 'journey.editor.pros': 'Előnyök', + 'journey.editor.cons': 'Hátrányok', + 'journey.editor.proPlaceholder': 'Valami remek...', + 'journey.editor.conPlaceholder': 'Nem annyira jó...', + 'journey.editor.addAnother': 'Még egy hozzáadása', + 'journey.editor.date': 'Dátum', + 'journey.editor.location': 'Helyszín', + 'journey.editor.searchLocation': 'Helyszín keresése...', + 'journey.editor.mood': 'Hangulat', + 'journey.editor.weather': 'Időjárás', + 'journey.editor.photoFirst': '1.', + 'journey.editor.makeFirst': 'Legyen az 1.', + 'journey.editor.searching': 'Keresés...', + 'journey.mood.amazing': 'Fantasztikus', + 'journey.mood.good': 'Jó', + 'journey.mood.neutral': 'Semleges', + 'journey.mood.rough': 'Nehéz', + 'journey.weather.sunny': 'Napos', + 'journey.weather.partly': 'Részben felhős', + 'journey.weather.cloudy': 'Felhős', + 'journey.weather.rainy': 'Esős', + 'journey.weather.stormy': 'Viharos', + 'journey.weather.cold': 'Havas', + 'journey.trips.linkTrip': 'Út kapcsolása', + 'journey.trips.searchTrip': 'Út keresése', + 'journey.trips.searchPlaceholder': 'Út neve vagy úti cél...', + 'journey.trips.noTripsAvailable': 'Nincsenek elérhető utak', + 'journey.trips.link': 'Kapcsolás', + 'journey.trips.tripLinked': 'Út kapcsolva', + 'journey.trips.linkFailed': 'Nem sikerült az utat kapcsolni', + 'journey.trips.addTrip': 'Út hozzáadása', + 'journey.trips.unlinkTrip': 'Út leválasztása', + 'journey.trips.unlinkMessage': + 'Leválasztod a(z) „{title}" utat? Az összes szinkronizált bejegyzés és fotó véglegesen törlődik. Ez nem vonható vissza.', + 'journey.trips.unlink': 'Leválasztás', + 'journey.trips.tripUnlinked': 'Út leválasztva', + 'journey.trips.unlinkFailed': 'Nem sikerült az utat leválasztani', + 'journey.trips.noTripsLinkedSettings': 'Nincsenek kapcsolt utak', + 'journey.contributors.invite': 'Közreműködő meghívása', + 'journey.contributors.searchUser': 'Felhasználó keresése', + 'journey.contributors.searchPlaceholder': 'Felhasználónév vagy e-mail...', + 'journey.contributors.noUsers': 'Nem található felhasználó', + 'journey.contributors.role': 'Szerep', + 'journey.contributors.added': 'Közreműködő hozzáadva', + 'journey.contributors.addFailed': 'Nem sikerült hozzáadni a közreműködőt', + 'journey.share.publicShare': 'Nyilvános megosztás', + 'journey.share.createLink': 'Megosztó link létrehozása', + 'journey.share.linkCreated': 'Megosztó link létrehozva', + 'journey.share.createFailed': 'Nem sikerült létrehozni a linket', + 'journey.share.copy': 'Másolás', + 'journey.share.copied': 'Másolva!', + 'journey.share.timeline': 'Idővonal', + 'journey.share.gallery': 'Galéria', + 'journey.share.map': 'Térkép', + 'journey.share.removeLink': 'Megosztó link eltávolítása', + 'journey.share.linkDeleted': 'Megosztó link törölve', + 'journey.share.deleteFailed': 'Nem sikerült törölni', + 'journey.share.updateFailed': 'Nem sikerült frissíteni', + 'journey.invite.role': 'Szerepkör', + 'journey.invite.viewer': 'Megtekintő', + 'journey.invite.editor': 'Szerkesztő', + 'journey.invite.invite': 'Meghívás', + 'journey.invite.inviting': 'Meghívás...', + 'journey.settings.title': 'Útinapló beállításai', + 'journey.settings.coverImage': 'Borítókép', + 'journey.settings.changeCover': 'Borító módosítása', + 'journey.settings.addCover': 'Borítókép hozzáadása', + 'journey.settings.name': 'Név', + 'journey.settings.subtitle': 'Alcím', + 'journey.settings.subtitlePlaceholder': 'pl. Thaiföld, Vietnam és Kambodzsa', + 'journey.settings.endJourney': 'Út archiválása', + 'journey.settings.reopenJourney': 'Út visszaállítása', + 'journey.settings.archived': 'Út archiválva', + 'journey.settings.reopened': 'Út újranyitva', + 'journey.settings.endDescription': + 'Elrejti az Élő jelzést. Bármikor újranyitható.', + 'journey.settings.delete': 'Törlés', + 'journey.settings.deleteJourney': 'Útinapló törlése', + 'journey.settings.deleteMessage': + 'Törlöd a(z) „{title}" útinaplót? Minden bejegyzés és fotó elveszik.', + 'journey.settings.saved': 'Beállítások mentve', + 'journey.settings.saveFailed': 'Nem sikerült menteni', + 'journey.settings.coverUpdated': 'Borítókép frissítve', + 'journey.settings.coverFailed': 'A feltöltés sikertelen', + 'journey.settings.failedToDelete': 'Törlés sikertelen', + 'journey.entries.deleteTitle': 'Bejegyzés törlése', + 'journey.photosUploaded': '{count} fotó feltöltve', + 'journey.photosUploadFailed': 'Néhány fotót nem sikerült feltölteni', + 'journey.photosAdded': '{count} fotó hozzáadva', + 'journey.public.notFound': 'Nem található', + 'journey.public.notFoundMessage': + 'Ez az útinapló nem létezik vagy a link lejárt.', + 'journey.public.readOnly': 'Csak olvasható · Nyilvános útinapló', + 'journey.public.tagline': 'Utazástervező és felfedező eszköz', + 'journey.public.sharedVia': 'Megosztva a következőn keresztül:', + 'journey.public.madeWith': 'Készítve a következővel:', + 'journey.pdf.journeyBook': 'Útinaplókönyv', + 'journey.pdf.madeWith': 'Készítve a TREK segítségével', + 'journey.pdf.day': 'Nap', + 'journey.pdf.theEnd': 'Vége', + 'journey.pdf.saveAsPdf': 'Mentés PDF-ként', + 'journey.pdf.pages': 'oldal', + 'journey.picker.tripPeriod': 'Utazási időszak', + 'journey.picker.dateRange': 'Időszak', + 'journey.picker.allPhotos': 'Összes fotó', + 'journey.picker.albums': 'Albumok', + 'journey.picker.selected': 'kiválasztva', + 'journey.picker.addTo': 'Hozzáadás', + 'journey.picker.newGallery': 'Új galéria', + 'journey.picker.selectAll': 'Összes kijelölése', + 'journey.picker.deselectAll': 'Összes kijelölés törlése', + 'journey.picker.noAlbums': 'Nem található album', + 'journey.picker.selectDate': 'Dátum választása', + 'journey.picker.search': 'Keresés', +}; +export default journey; diff --git a/shared/src/i18n/hu/login.ts b/shared/src/i18n/hu/login.ts new file mode 100644 index 00000000..add396e7 --- /dev/null +++ b/shared/src/i18n/hu/login.ts @@ -0,0 +1,102 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': + 'Bejelentkezés sikertelen. Kérjük, ellenőrizd a megadott adatokat.', + 'login.tagline': 'Az utazásaid.\nA terved.', + 'login.description': + 'Tervezz utazásokat közösen interaktív térképekkel, költségvetéssel és valós idejű szinkronizálással.', + 'login.features.maps': 'Interaktív térképek', + 'login.features.mapsDesc': 'Google Places, útvonalak és csoportosítás', + 'login.features.realtime': 'Valós idejű szinkron', + 'login.features.realtimeDesc': 'Közös tervezés WebSocket-en keresztül', + 'login.features.budget': 'Költségvetés-követés', + 'login.features.budgetDesc': + 'Kategóriák, diagramok és személyenkénti költségek', + 'login.features.collab': 'Együttműködés', + 'login.features.collabDesc': 'Többfelhasználós, megosztott utazásokkal', + 'login.features.packing': 'Csomagolási listák', + 'login.features.packingDesc': 'Kategóriák és haladás', + 'login.features.bookings': 'Foglalások', + 'login.features.bookingsDesc': 'Repülők, szállodák, éttermek és még több', + 'login.features.files': 'Dokumentumok', + 'login.features.filesDesc': 'Fájlok feltöltése és kezelése', + 'login.features.routes': 'Útvonal-optimalizálás', + 'login.features.routesDesc': + 'Automatikus optimalizálás és Google Maps export', + 'login.selfHosted': + 'Saját üzemeltetés · Nyílt forráskód · Az adataid nálad maradnak', + 'login.title': 'Bejelentkezés', + 'login.subtitle': 'Üdv újra', + 'login.signingIn': 'Bejelentkezés…', + 'login.signIn': 'Bejelentkezés', + 'login.createAdmin': 'Admin fiók létrehozása', + 'login.createAdminHint': 'Hozd létre az első admin fiókot a TREK-hez.', + 'login.setNewPassword': 'Új jelszó beállítása', + 'login.setNewPasswordHint': + 'A folytatás előtt meg kell változtatnia a jelszavát.', + 'login.createAccount': 'Fiók létrehozása', + 'login.createAccountHint': 'Új fiók regisztrálása.', + 'login.creating': 'Létrehozás…', + 'login.noAccount': 'Nincs még fiókod?', + 'login.hasAccount': 'Már van fiókod?', + 'login.register': 'Regisztráció', + 'login.emailPlaceholder': 'email@cimed.hu', + 'login.username': 'Felhasználónév', + 'login.oidc.registrationDisabled': + 'A regisztráció le van tiltva. Lépj kapcsolatba az adminisztrátorral.', + 'login.oidc.noEmail': 'Nem érkezett e-mail a szolgáltatótól.', + 'login.oidc.tokenFailed': 'Hitelesítés sikertelen.', + 'login.oidc.invalidState': 'Érvénytelen munkamenet. Kérjük, próbáld újra.', + 'login.demoFailed': 'Demo bejelentkezés sikertelen', + 'login.oidcSignIn': 'Bejelentkezés ezzel: {name}', + 'login.oidcOnly': + 'A jelszavas hitelesítés le van tiltva. Kérjük, jelentkezz be az SSO szolgáltatódon keresztül.', + 'login.oidcLoggedOut': + 'Kijelentkeztél. Jelentkezz be újra az SSO szolgáltatódon keresztül.', + 'login.demoHint': 'Próbáld ki a demót — regisztráció nélkül', + 'login.mfaTitle': 'Kétfaktoros hitelesítés', + 'login.mfaSubtitle': 'Add meg a 6 jegyű kódot a hitelesítő alkalmazásból.', + 'login.mfaCodeLabel': 'Ellenőrző kód', + 'login.mfaCodeRequired': 'Add meg a kódot a hitelesítő alkalmazásból.', + 'login.mfaHint': + 'Nyisd meg a Google Authenticator, Authy vagy más TOTP alkalmazást.', + 'login.mfaBack': '← Vissza a bejelentkezéshez', + 'login.mfaVerify': 'Ellenőrzés', + 'login.invalidInviteLink': 'Érvénytelen vagy lejárt meghívólink', + 'login.oidcFailed': 'OIDC bejelentkezés sikertelen', + 'login.usernameRequired': 'A felhasználónév kötelező', + 'login.passwordMinLength': + 'A jelszónak legalább 8 karakter hosszúnak kell lennie', + 'login.forgotPassword': 'Elfelejtetted a jelszavad?', + 'login.forgotPasswordTitle': 'Jelszó visszaállítása', + 'login.forgotPasswordBody': + 'Írd be a regisztrációnál használt e-mail-címet. Ha létezik fiók, küldünk egy visszaállítási linket.', + 'login.forgotPasswordSubmit': 'Link küldése', + 'login.forgotPasswordSentTitle': 'Nézd meg az e-mailjeidet', + 'login.forgotPasswordSentBody': + 'Ha létezik fiók ehhez az e-mailhez, a visszaállítási link úton van. 60 perc után lejár.', + 'login.forgotPasswordSmtpHintOff': + 'Megjegyzés: a rendszergazda nem konfigurálta az SMTP-t, ezért a visszaállítási link e-mail helyett a szerverkonzolba kerül.', + 'login.backToLogin': 'Vissza a bejelentkezéshez', + 'login.newPassword': 'Új jelszó', + 'login.confirmPassword': 'Új jelszó megerősítése', + 'login.passwordsDontMatch': 'A jelszavak nem egyeznek', + 'login.mfaCode': '2FA-kód', + 'login.resetPasswordTitle': 'Új jelszó beállítása', + 'login.resetPasswordBody': + 'Válassz erős jelszót, amit itt még nem használtál. Minimum 8 karakter.', + 'login.resetPasswordMfaBody': + 'Add meg a 2FA-kódodat vagy egy tartalék kódot a visszaállítás befejezéséhez.', + 'login.resetPasswordSubmit': 'Jelszó visszaállítása', + 'login.resetPasswordVerify': 'Ellenőrzés és visszaállítás', + 'login.resetPasswordSuccessTitle': 'Jelszó frissítve', + 'login.resetPasswordSuccessBody': + 'Mostantól bejelentkezhetsz az új jelszavaddal.', + 'login.resetPasswordInvalidLink': 'Érvénytelen visszaállítási link', + 'login.resetPasswordInvalidLinkBody': + 'A link hiányzik vagy sérült. A folytatáshoz kérj egy újat.', + 'login.resetPasswordFailed': + 'A visszaállítás nem sikerült. A link lehet, hogy lejárt.', +}; +export default login; diff --git a/shared/src/i18n/hu/map.ts b/shared/src/i18n/hu/map.ts new file mode 100644 index 00000000..03692d9d --- /dev/null +++ b/shared/src/i18n/hu/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Kapcsolatok', + 'map.showConnections': 'Foglalási útvonalak megjelenítése', + 'map.hideConnections': 'Foglalási útvonalak elrejtése', +}; +export default map; diff --git a/shared/src/i18n/hu/members.ts b/shared/src/i18n/hu/members.ts new file mode 100644 index 00000000..bec59e8b --- /dev/null +++ b/shared/src/i18n/hu/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Utazás megosztása', + 'members.inviteUser': 'Felhasználó meghívása', + 'members.selectUser': 'Felhasználó kiválasztása…', + 'members.invite': 'Meghívás', + 'members.allHaveAccess': 'Minden felhasználónak már van hozzáférése.', + 'members.access': 'Hozzáférés', + 'members.person': 'személy', + 'members.persons': 'személy', + 'members.you': 'te', + 'members.owner': 'Tulajdonos', + 'members.leaveTrip': 'Utazás elhagyása', + 'members.removeAccess': 'Hozzáférés eltávolítása', + 'members.confirmLeave': 'Elhagyod az utazást? Elveszíted a hozzáférést.', + 'members.confirmRemove': 'Eltávolítod a hozzáférést ettől a felhasználótól?', + 'members.loadError': 'Nem sikerült betölteni a tagokat', + 'members.added': 'hozzáadva', + 'members.addError': 'Nem sikerült hozzáadni', + 'members.removed': 'Tag eltávolítva', + 'members.removeError': 'Nem sikerült eltávolítani', +}; +export default members; diff --git a/shared/src/i18n/hu/memories.ts b/shared/src/i18n/hu/memories.ts new file mode 100644 index 00000000..2965eb66 --- /dev/null +++ b/shared/src/i18n/hu/memories.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Fotók', + 'memories.notConnected': 'Immich nincs csatlakoztatva', + 'memories.notConnectedHint': + 'Csatlakoztasd az Immich példányodat a Beállításokban, hogy itt lásd az utazási fotóidat.', + 'memories.notConnectedMultipleHint': + 'A fényképek hozzáadásához csatlakoztasson egyet a következő fényképszolgáltatók közül a Beállításokban: {provider_names}.', + 'memories.noDates': 'Adj hozzá dátumokat az utazáshoz a fotók betöltéséhez.', + 'memories.noPhotos': 'Nem találhatók fotók', + 'memories.noPhotosHint': + 'Nem találhatók fotók az Immichben erre az utazási időszakra.', + 'memories.photosFound': 'fotó', + 'memories.fromOthers': 'másoktól', + 'memories.sharePhotos': 'Fotók megosztása', + 'memories.sharing': 'Megosztás', + 'memories.reviewTitle': 'Nézd át a fotóidat', + 'memories.reviewHint': 'Kattints a fotókra a megosztásból való kizáráshoz.', + 'memories.shareCount': '{count} fotó megosztása', + 'memories.providerUrl': 'Szerver URL', + 'memories.providerApiKey': 'API kulcs', + 'memories.providerUsername': 'Felhasználónév', + 'memories.providerPassword': 'Jelszó', + 'memories.providerOTP': 'MFA kód (ha engedélyezve van)', + 'memories.skipSSLVerification': 'SSL tanúsítvány ellenőrzésének kihagyása', + 'memories.immichAutoUpload': + 'Journey-fotók feltöltésekor másolat Immich-be is', + 'memories.providerUrlHintSynology': + 'Adja meg a Photos alkalmazás elérési útját az URL-ben, pl. https://nas:5001/photo', + 'memories.testConnection': 'Kapcsolat tesztelése', + 'memories.testShort': 'Teszt', + 'memories.testFirst': 'Először teszteld a kapcsolatot', + 'memories.connected': 'Csatlakoztatva', + 'memories.disconnected': 'Nincs csatlakoztatva', + 'memories.connectionSuccess': 'Csatlakozva az Immichhez', + 'memories.connectionError': 'Nem sikerült csatlakozni az Immichhez', + 'memories.saved': '{provider_name} beállítások mentve', + 'memories.providerDisconnectedBanner': + 'A {provider_name} kapcsolat megszakadt. Csatlakozzon újra a Beállításokban a fényképek megtekintéséhez.', + 'memories.saveError': + 'Nem sikerült menteni a(z) {provider_name} beállításait', + 'memories.addPhotos': 'Fotók hozzáadása', + 'memories.linkAlbum': 'Album csatolása', + 'memories.selectAlbum': 'Immich album kiválasztása', + 'memories.selectAlbumMultiple': 'Album kiválasztása', + 'memories.noAlbums': 'Nem található album', + 'memories.syncAlbum': 'Album szinkronizálása', + 'memories.unlinkAlbum': 'Leválasztás', + 'memories.photos': 'fotó', + 'memories.selectPhotos': 'Fotók kiválasztása az Immichből', + 'memories.selectPhotosMultiple': 'Fényképek kiválasztása', + 'memories.selectHint': 'Koppints a fotókra a kijelölésükhöz.', + 'memories.selected': 'kijelölve', + 'memories.addSelected': '{count} fotó hozzáadása', + 'memories.alreadyAdded': 'Hozzáadva', + 'memories.private': 'Privát', + 'memories.stopSharing': 'Megosztás leállítása', + 'memories.oldest': 'Legrégebbi elöl', + 'memories.newest': 'Legújabb elöl', + 'memories.allLocations': 'Összes helyszín', + 'memories.tripDates': 'Utazás dátumai', + 'memories.allPhotos': 'Összes fotó', + 'memories.confirmShareTitle': 'Megosztás az utazótársakkal?', + 'memories.confirmShareHint': + '{count} fotó lesz látható az utazás összes tagja számára. Később egyenként is priváttá teheted őket.', + 'memories.confirmShareButton': 'Fotók megosztása', + 'memories.error.loadAlbums': 'Az albumok betöltése sikertelen', + 'memories.error.linkAlbum': 'Az album csatolása sikertelen', + 'memories.error.unlinkAlbum': 'Az album leválasztása sikertelen', + 'memories.error.syncAlbum': 'Az album szinkronizálása sikertelen', + 'memories.error.loadPhotos': 'A fotók betöltése sikertelen', + 'memories.error.addPhotos': 'A fotók hozzáadása sikertelen', + 'memories.error.removePhoto': 'A fotó eltávolítása sikertelen', + 'memories.error.toggleSharing': 'A megosztás frissítése sikertelen', + 'memories.saveRouteNotConfigured': + 'A mentési útvonal nincs konfigurálva ehhez a szolgáltatóhoz', + 'memories.testRouteNotConfigured': + 'A tesztútvonal nincs konfigurálva ehhez a szolgáltatóhoz', + 'memories.fillRequiredFields': 'Kérjük töltse ki az összes kötelező mezőt', +}; +export default memories; diff --git a/shared/src/i18n/hu/nav.ts b/shared/src/i18n/hu/nav.ts new file mode 100644 index 00000000..9cc7cc78 --- /dev/null +++ b/shared/src/i18n/hu/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Utazás', + 'nav.share': 'Megosztás', + 'nav.settings': 'Beállítások', + 'nav.admin': 'Admin', + 'nav.logout': 'Kijelentkezés', + 'nav.lightMode': 'Világos mód', + 'nav.darkMode': 'Sötét mód', + 'nav.autoMode': 'Automatikus mód', + 'nav.administrator': 'Adminisztrátor', + 'nav.myTrips': 'Utazásaim', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Beállítások', + 'nav.bottomAdmin': 'Adminisztráció', + 'nav.bottomLogout': 'Kijelentkezés', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/hu/notif.ts b/shared/src/i18n/hu/notif.ts new file mode 100644 index 00000000..17ba7bb2 --- /dev/null +++ b/shared/src/i18n/hu/notif.ts @@ -0,0 +1,45 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Teszt] Értesítés', + 'notif.test.simple.text': 'Ez egy egyszerű teszt értesítés.', + 'notif.test.boolean.text': 'Elfogadod ezt a teszt értesítést?', + 'notif.test.navigate.text': 'Kattints alább az irányítópultra navigáláshoz.', + 'notif.trip_invite.title': 'Utazásra meghívó', + 'notif.trip_invite.text': '{actor} meghívott a(z) {trip} utazásra', + 'notif.booking_change.title': 'Foglalás frissítve', + 'notif.booking_change.text': + '{actor} frissített egy foglalást a(z) {trip} utazásban', + 'notif.trip_reminder.title': 'Utazás emlékeztető', + 'notif.trip_reminder.text': 'A(z) {trip} utazás hamarosan kezdődik!', + 'notif.todo_due.title': 'Teendő esedékes', + 'notif.todo_due.text': '{todo} ({trip}) határideje: {due}', + 'notif.vacay_invite.title': 'Vacay Fusion meghívó', + 'notif.vacay_invite.text': + '{actor} meghívott a nyaralási tervek összevonásához', + 'notif.photos_shared.title': 'Fotók megosztva', + 'notif.photos_shared.text': + '{actor} {count} fotót osztott meg a(z) {trip} utazásban', + 'notif.collab_message.title': 'Új üzenet', + 'notif.collab_message.text': '{actor} üzenetet küldött a(z) {trip} utazásban', + 'notif.packing_tagged.title': 'Csomagolási feladat', + 'notif.packing_tagged.text': + '{actor} hozzárendelte Önt a {category} kategóriához a(z) {trip} utazásban', + 'notif.version_available.title': 'Új verzió elérhető', + 'notif.version_available.text': 'A TREK {version} elérhető', + 'notif.action.view_trip': 'Utazás megtekintése', + 'notif.action.view_collab': 'Üzenetek megtekintése', + 'notif.action.view_packing': 'Csomagolás megtekintése', + 'notif.action.view_photos': 'Fotók megtekintése', + 'notif.action.view_vacay': 'Vacay megtekintése', + 'notif.action.view_admin': 'Admin megnyitása', + 'notif.action.view': 'Megtekintés', + 'notif.action.accept': 'Elfogadás', + 'notif.action.decline': 'Elutasítás', + 'notif.generic.title': 'Értesítés', + 'notif.generic.text': 'Új értesítésed érkezett', + 'notif.dev.unknown_event.title': '[DEV] Ismeretlen esemény', + 'notif.dev.unknown_event.text': + 'A(z) "{event}" eseménytípus nincs regisztrálva az EVENT_NOTIFICATION_CONFIG-ban', +}; +export default notif; diff --git a/shared/src/i18n/hu/notifications.ts b/shared/src/i18n/hu/notifications.ts new file mode 100644 index 00000000..71f5df41 --- /dev/null +++ b/shared/src/i18n/hu/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Értesítések', + 'notifications.markAllRead': 'Összes olvasottnak jelölése', + 'notifications.deleteAll': 'Összes törlése', + 'notifications.showAll': 'Összes értesítés megtekintése', + 'notifications.empty': 'Nincsenek értesítések', + 'notifications.emptyDescription': 'Mindennel naprakész vagy!', + 'notifications.all': 'Összes', + 'notifications.unreadOnly': 'Olvasatlan', + 'notifications.markRead': 'Olvasottnak jelölés', + 'notifications.markUnread': 'Olvasatlannak jelölés', + 'notifications.delete': 'Törlés', + 'notifications.system': 'Rendszer', + 'notifications.synologySessionCleared.title': 'Synology Photos leválasztva', + 'notifications.synologySessionCleared.text': + 'A szerver vagy a fiók megváltozott — lépjen a Beállításokba a kapcsolat újrateszteléséhez.', + 'notifications.test.title': 'Teszt értesítés {actor} részéről', + 'notifications.test.text': 'Ez egy egyszerű teszt értesítés.', + 'notifications.test.booleanTitle': '{actor} jóváhagyásodat kéri', + 'notifications.test.booleanText': 'Teszt igen/nem értesítés.', + 'notifications.test.accept': 'Jóváhagyás', + 'notifications.test.decline': 'Elutasítás', + 'notifications.test.navigateTitle': 'Nézz meg valamit', + 'notifications.test.navigateText': 'Teszt navigációs értesítés.', + 'notifications.test.goThere': 'Odamegyek', + 'notifications.test.adminTitle': 'Adminisztrátor üzenet', + 'notifications.test.adminText': + '{actor} teszt értesítést küldött az összes adminisztrátornak.', + 'notifications.test.tripTitle': '{actor} üzenetet küldött az utazásodba', + 'notifications.test.tripText': 'Teszt értesítés a(z) "{trip}" utazáshoz.', + 'notifications.versionAvailable.title': 'Elérhető frissítés', + 'notifications.versionAvailable.text': 'A TREK {version} már elérhető.', + 'notifications.versionAvailable.button': 'Részletek megtekintése', +}; +export default notifications; diff --git a/shared/src/i18n/hu/oauth.ts b/shared/src/i18n/hu/oauth.ts new file mode 100644 index 00000000..0ae46942 --- /dev/null +++ b/shared/src/i18n/hu/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Utazások', + 'oauth.scope.group.places': 'Helyek', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Csomagolás', + 'oauth.scope.group.todos': 'Feladatok', + 'oauth.scope.group.budget': 'Költségvetés', + 'oauth.scope.group.reservations': 'Foglalások', + 'oauth.scope.group.collab': 'Együttműködés', + 'oauth.scope.group.notifications': 'Értesítések', + 'oauth.scope.group.vacay': 'Szabadság', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Időjárás', + 'oauth.scope.group.journey': 'Útinaplók', + 'oauth.scope.trips:read.label': 'Utazások és útvonalak megtekintése', + 'oauth.scope.trips:read.description': + 'Utazások, napok, napi feljegyzések és tagok olvasása', + 'oauth.scope.trips:write.label': 'Utazások és útvonalak szerkesztése', + 'oauth.scope.trips:write.description': + 'Utazások, napok és feljegyzések létrehozása, frissítése és tagok kezelése', + 'oauth.scope.trips:delete.label': 'Utazások törlése', + 'oauth.scope.trips:delete.description': + 'Teljes utazások végleges törlése — ez a művelet visszafordíthatatlan', + 'oauth.scope.trips:share.label': 'Megosztási linkek kezelése', + 'oauth.scope.trips:share.description': + 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása', + 'oauth.scope.places:read.label': 'Helyek és térképadatok megtekintése', + 'oauth.scope.places:read.description': + 'Helyek, napi hozzárendelések, címkék és kategóriák olvasása', + 'oauth.scope.places:write.label': 'Helyek kezelése', + 'oauth.scope.places:write.description': + 'Helyek, hozzárendelések és címkék létrehozása, frissítése és törlése', + 'oauth.scope.atlas:read.label': 'Atlas megtekintése', + 'oauth.scope.atlas:read.description': + 'Meglátogatott országok, régiók és bakancslisták olvasása', + 'oauth.scope.atlas:write.label': 'Atlas kezelése', + 'oauth.scope.atlas:write.description': + 'Országok és régiók meglátogatottként jelölése, bakancslisták kezelése', + 'oauth.scope.packing:read.label': 'Csomaglisták megtekintése', + 'oauth.scope.packing:read.description': + 'Csomagolási tételek, táskák és kategória-hozzárendelések olvasása', + 'oauth.scope.packing:write.label': 'Csomaglisták kezelése', + 'oauth.scope.packing:write.description': + 'Csomagolási tételek és táskák hozzáadása, frissítése, törlése, jelölése és átrendezése', + 'oauth.scope.todos:read.label': 'Feladatlisták megtekintése', + 'oauth.scope.todos:read.description': + 'Utazás feladatai és kategória-hozzárendelések olvasása', + 'oauth.scope.todos:write.label': 'Feladatlisták kezelése', + 'oauth.scope.todos:write.description': + 'Feladatok létrehozása, frissítése, jelölése, törlése és átrendezése', + 'oauth.scope.budget:read.label': 'Költségvetés megtekintése', + 'oauth.scope.budget:read.description': + 'Költségvetési tételek és kiadások részletezésének olvasása', + 'oauth.scope.budget:write.label': 'Költségvetés kezelése', + 'oauth.scope.budget:write.description': + 'Költségvetési tételek létrehozása, frissítése és törlése', + 'oauth.scope.reservations:read.label': 'Foglalások megtekintése', + 'oauth.scope.reservations:read.description': + 'Foglalások és szállásadatok olvasása', + 'oauth.scope.reservations:write.label': 'Foglalások kezelése', + 'oauth.scope.reservations:write.description': + 'Foglalások létrehozása, frissítése, törlése és átrendezése', + 'oauth.scope.collab:read.label': 'Együttműködés megtekintése', + 'oauth.scope.collab:read.description': + 'Együttműködési feljegyzések, szavazások és üzenetek olvasása', + 'oauth.scope.collab:write.label': 'Együttműködés kezelése', + 'oauth.scope.collab:write.description': + 'Együttműködési feljegyzések, szavazások és üzenetek létrehozása, frissítése és törlése', + 'oauth.scope.notifications:read.label': 'Értesítések megtekintése', + 'oauth.scope.notifications:read.description': + 'Alkalmazáson belüli értesítések és olvasatlan számok olvasása', + 'oauth.scope.notifications:write.label': 'Értesítések kezelése', + 'oauth.scope.notifications:write.description': + 'Értesítések olvasottként jelölése és válaszadás rájuk', + 'oauth.scope.vacay:read.label': 'Szabadságtervek megtekintése', + 'oauth.scope.vacay:read.description': + 'Szabadságtervezési adatok, bejegyzések és statisztikák olvasása', + 'oauth.scope.vacay:write.label': 'Szabadságtervek kezelése', + 'oauth.scope.vacay:write.description': + 'Szabadságbejegyzések, ünnepnapok és csapattervek létrehozása és kezelése', + 'oauth.scope.geo:read.label': 'Térképek és geokódolás', + 'oauth.scope.geo:read.description': + 'Helyek keresése, térkép URL-ek feloldása és koordináták fordított geokódolása', + 'oauth.scope.weather:read.label': 'Időjárás-előrejelzések', + 'oauth.scope.weather:read.description': + 'Időjárás-előrejelzések lekérése az utazási helyszínekre és dátumokra', + 'oauth.scope.journey:read.label': 'Útinaplók megtekintése', + 'oauth.scope.journey:read.description': + 'Útinaplók, bejegyzések és közreműködők listájának olvasása', + 'oauth.scope.journey:write.label': 'Útinaplók kezelése', + 'oauth.scope.journey:write.description': + 'Útinaplók és bejegyzéseik létrehozása, frissítése és törlése', + 'oauth.scope.journey:share.label': 'Útinapló-linkek kezelése', + 'oauth.scope.journey:share.description': + 'Nyilvános megosztási linkek létrehozása, frissítése és visszavonása útinaplókhoz', +}; +export default oauth; diff --git a/shared/src/i18n/hu/packing.ts b/shared/src/i18n/hu/packing.ts new file mode 100644 index 00000000..5dd6ca5d --- /dev/null +++ b/shared/src/i18n/hu/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Csomagolási lista', + 'packing.empty': 'A csomagolási lista üres', + 'packing.import': 'Importálás', + 'packing.importTitle': 'Csomagolási lista importálása', + 'packing.importHint': + 'Soronként egy tétel. Formátum: Kategória, Név, Súly g-ban (opcionális), Táska (opcionális), checked/unchecked (opcionális)', + 'packing.importPlaceholder': + 'Tisztálkodás, Fogkefe\nRuházat, Pólók, 200\nDokumentumok, Útlevél, , Kézipoggyász\nElektronika, Töltő, 50, Bőrönd, checked', + 'packing.importCsv': 'CSV/TXT betöltése', + 'packing.importAction': '{count} importálása', + 'packing.importSuccess': '{count} tétel importálva', + 'packing.importError': 'Importálás sikertelen', + 'packing.importEmpty': 'Nincsenek importálható tételek', + 'packing.progress': '{packed} / {total} becsomagolva ({percent}%)', + 'packing.clearChecked': '{count} kipipált eltávolítása', + 'packing.clearCheckedShort': '{count} eltávolítása', + 'packing.suggestions': 'Javaslatok', + 'packing.suggestionsTitle': 'Javaslatok hozzáadása', + 'packing.allSuggested': 'Minden javaslat hozzáadva', + 'packing.allPacked': 'Minden be van csomagolva!', + 'packing.addPlaceholder': 'Új tárgy hozzáadása...', + 'packing.categoryPlaceholder': 'Kategória...', + 'packing.filterAll': 'Összes', + 'packing.filterOpen': 'Nyitott', + 'packing.filterDone': 'Kész', + 'packing.emptyTitle': 'A csomagolási lista üres', + 'packing.emptyHint': 'Adj hozzá tárgyakat vagy használd a javaslatokat', + 'packing.emptyFiltered': 'Nincs elem ebben a szűrőben', + 'packing.menuRename': 'Átnevezés', + 'packing.menuCheckAll': 'Összes kipipálása', + 'packing.menuUncheckAll': 'Összes jelölés törlése', + 'packing.menuDeleteCat': 'Kategória törlése', + 'packing.noMembers': 'Nincsenek utazási tagok', + 'packing.addItem': 'Tétel hozzáadása', + 'packing.addItemPlaceholder': 'Tétel neve...', + 'packing.addCategory': 'Kategória hozzáadása', + 'packing.newCategoryPlaceholder': 'Kategória neve (pl. Ruházat)', + 'packing.applyTemplate': 'Sablon alkalmazása', + 'packing.template': 'Sablon', + 'packing.templateApplied': '{count} tétel hozzáadva a sablonból', + 'packing.templateError': 'Nem sikerült alkalmazni a sablont', + 'packing.saveAsTemplate': 'Mentés sablonként', + 'packing.templateName': 'Sablon neve', + 'packing.templateSaved': 'Csomaglista elmentve sablonként', + 'packing.bags': 'Táskák', + 'packing.noBag': 'Nincs hozzárendelve', + 'packing.totalWeight': 'Összsúly', + 'packing.bagName': 'Táska neve...', + 'packing.addBag': 'Táska hozzáadása', + 'packing.changeCategory': 'Kategória módosítása', + 'packing.confirm.clearChecked': + 'Biztosan el szeretnéd távolítani a(z) {count} kipipált tárgyat?', + 'packing.confirm.deleteCat': + 'Biztosan törölni szeretnéd a(z) "{name}" kategóriát {count} tárggyal?', + 'packing.defaultCategory': 'Egyéb', + 'packing.toast.saveError': 'Nem sikerült menteni', + 'packing.toast.deleteError': 'Nem sikerült törölni', + 'packing.toast.renameError': 'Nem sikerült átnevezni', + 'packing.toast.addError': 'Nem sikerült hozzáadni', + 'packing.suggestions.items': [ + { + name: 'Útlevél', + category: 'Dokumentumok', + }, + { + name: 'Személyi igazolvány', + category: 'Dokumentumok', + }, + { + name: 'Utazási biztosítás', + category: 'Dokumentumok', + }, + { + name: 'Repülőjegyek', + category: 'Dokumentumok', + }, + { + name: 'Bankkártya', + category: 'Pénzügyek', + }, + { + name: 'Készpénz', + category: 'Pénzügyek', + }, + { + name: 'Vízum', + category: 'Dokumentumok', + }, + { + name: 'Pólók', + category: 'Ruházat', + }, + { + name: 'Nadrágok', + category: 'Ruházat', + }, + { + name: 'Fehérnemű', + category: 'Ruházat', + }, + { + name: 'Zoknik', + category: 'Ruházat', + }, + { + name: 'Kabát', + category: 'Ruházat', + }, + { + name: 'Hálóruha', + category: 'Ruházat', + }, + { + name: 'Fürdőruha', + category: 'Ruházat', + }, + { + name: 'Esőkabát', + category: 'Ruházat', + }, + { + name: 'Kényelmes cipő', + category: 'Ruházat', + }, + { + name: 'Fogkefe', + category: 'Tisztálkodás', + }, + { + name: 'Fogkrém', + category: 'Tisztálkodás', + }, + { + name: 'Sampon', + category: 'Tisztálkodás', + }, + { + name: 'Dezodor', + category: 'Tisztálkodás', + }, + { + name: 'Naptej', + category: 'Tisztálkodás', + }, + { + name: 'Borotva', + category: 'Tisztálkodás', + }, + { + name: 'Töltő', + category: 'Elektronika', + }, + { + name: 'Powerbank', + category: 'Elektronika', + }, + { + name: 'Fejhallgató', + category: 'Elektronika', + }, + { + name: 'Úti adapter', + category: 'Elektronika', + }, + { + name: 'Fényképezőgép', + category: 'Elektronika', + }, + { + name: 'Fájdalomcsillapító', + category: 'Egészség', + }, + { + name: 'Ragtapasz', + category: 'Egészség', + }, + { + name: 'Fertőtlenítőszer', + category: 'Egészség', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/hu/pdf.ts b/shared/src/i18n/hu/pdf.ts new file mode 100644 index 00000000..f19283df --- /dev/null +++ b/shared/src/i18n/hu/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Utazási terv', + 'pdf.planned': 'Tervezett', + 'pdf.costLabel': 'Költség', + 'pdf.preview': 'PDF előnézet', + 'pdf.saveAsPdf': 'Mentés PDF-ként', +}; +export default pdf; diff --git a/shared/src/i18n/hu/perm.ts b/shared/src/i18n/hu/perm.ts new file mode 100644 index 00000000..cf4f89df --- /dev/null +++ b/shared/src/i18n/hu/perm.ts @@ -0,0 +1,65 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Jogosultsági beállítások', + 'perm.subtitle': + 'Szabályozd, ki milyen műveleteket végezhet az alkalmazásban', + 'perm.saved': 'Jogosultsági beállítások mentve', + 'perm.resetDefaults': 'Alapértelmezések visszaállítása', + 'perm.customized': 'testreszabott', + 'perm.level.admin': 'Csak adminisztrátor', + 'perm.level.tripOwner': 'Utazás tulajdonosa', + 'perm.level.tripMember': 'Utazás tagjai', + 'perm.level.everybody': 'Mindenki', + 'perm.cat.trip': 'Utazáskezelés', + 'perm.cat.members': 'Tagkezelés', + 'perm.cat.files': 'Fájlok', + 'perm.cat.content': 'Tartalom és menetrend', + 'perm.cat.extras': 'Költségvetés, csomagolás és együttműködés', + 'perm.action.trip_create': 'Utazások létrehozása', + 'perm.action.trip_edit': 'Utazás részleteinek szerkesztése', + 'perm.action.trip_delete': 'Utazások törlése', + 'perm.action.trip_archive': 'Utazások archiválása / visszaállítása', + 'perm.action.trip_cover_upload': 'Borítókép feltöltése', + 'perm.action.member_manage': 'Tagok hozzáadása / eltávolítása', + 'perm.action.file_upload': 'Fájlok feltöltése', + 'perm.action.file_edit': 'Fájl metaadatok szerkesztése', + 'perm.action.file_delete': 'Fájlok törlése', + 'perm.action.place_edit': 'Helyek hozzáadása / szerkesztése / törlése', + 'perm.action.day_edit': 'Napok, jegyzetek és hozzárendelések szerkesztése', + 'perm.action.reservation_edit': 'Foglalások kezelése', + 'perm.action.budget_edit': 'Költségvetés kezelése', + 'perm.action.packing_edit': 'Csomagolási listák kezelése', + 'perm.action.collab_edit': 'Együttműködés (jegyzetek, szavazások, chat)', + 'perm.action.share_manage': 'Megosztási linkek kezelése', + 'perm.actionHint.trip_create': 'Ki hozhat létre új utazásokat', + 'perm.actionHint.trip_edit': + 'Ki módosíthatja az utazás nevét, dátumait, leírását és pénznemét', + 'perm.actionHint.trip_delete': 'Ki törölhet véglegesen egy utazást', + 'perm.actionHint.trip_archive': + 'Ki archiválhat vagy állíthat vissza egy utazást', + 'perm.actionHint.trip_cover_upload': + 'Ki tölthet fel vagy módosíthat borítóképet', + 'perm.actionHint.member_manage': + 'Ki hívhat meg vagy távolíthat el utazás tagokat', + 'perm.actionHint.file_upload': 'Ki tölthet fel fájlokat egy utazáshoz', + 'perm.actionHint.file_edit': + 'Ki szerkesztheti a fájlok leírásait és linkjeit', + 'perm.actionHint.file_delete': + 'Ki helyezhet fájlokat a kukába vagy törölheti véglegesen', + 'perm.actionHint.place_edit': + 'Ki adhat hozzá, szerkeszthet vagy törölhet helyeket', + 'perm.actionHint.day_edit': + 'Ki szerkesztheti a napokat, napi jegyzeteket és hely-hozzárendeléseket', + 'perm.actionHint.reservation_edit': + 'Ki hozhat létre, szerkeszthet vagy törölhet foglalásokat', + 'perm.actionHint.budget_edit': + 'Ki hozhat létre, szerkeszthet vagy törölhet költségvetési tételeket', + 'perm.actionHint.packing_edit': + 'Ki kezelheti a csomagolási tételeket és táskákat', + 'perm.actionHint.collab_edit': + 'Ki hozhat létre jegyzeteket, szavazásokat és küldhet üzeneteket', + 'perm.actionHint.share_manage': + 'Ki hozhat létre vagy törölhet nyilvános megosztási linkeket', +}; +export default perm; diff --git a/shared/src/i18n/hu/photos.ts b/shared/src/i18n/hu/photos.ts new file mode 100644 index 00000000..c7e15a2e --- /dev/null +++ b/shared/src/i18n/hu/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Fotók', + 'photos.subtitle': '{count} fotó a következőhöz: {trip}', + 'photos.dropHere': 'Húzza ide a fényképeket...', + 'photos.dropHereActive': 'Húzza ide a fényképeket', + 'photos.captionForAll': 'Felirat (mindenkinek)', + 'photos.captionPlaceholder': 'Opcionális felirat...', + 'photos.addCaption': 'Felirat hozzáadása...', + 'photos.allDays': 'Minden nap', + 'photos.noPhotos': 'Még nincsenek fotók', + 'photos.uploadHint': 'Töltsd fel az úti fotóidat', + 'photos.clickToSelect': 'vagy kattints a kiválasztáshoz', + 'photos.linkPlace': 'Hely társítása', + 'photos.noPlace': 'Nincs hely', + 'photos.uploadN': '{n} fotó feltöltése', + 'photos.linkDay': 'Nap csatolása', + 'photos.noDay': 'Nincs nap', + 'photos.dayLabel': '{number}. nap', + 'photos.photoSelected': 'Fotó kiválasztva', + 'photos.photosSelected': 'Fotók kiválasztva', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · legfeljebb 30 fotó', +}; +export default photos; diff --git a/shared/src/i18n/hu/places.ts b/shared/src/i18n/hu/places.ts new file mode 100644 index 00000000..85470c2a --- /dev/null +++ b/shared/src/i18n/hu/places.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Hely/Tevékenység hozzáadása', + 'places.importFile': 'Fájl importálása', + 'places.sidebarDrop': 'Ejtse el az importáláshoz', + 'places.importFileHint': + '.gpx, .kml vagy .kmz fájlok importálása olyan eszközökből, mint a Google My Maps, Google Earth vagy egy GPS tracker.', + 'places.importFileDropHere': + 'Kattintson egy fájl kiválasztásához, vagy húzza ide', + 'places.importFileDropActive': 'Ejtse ide a fájlt a kiválasztáshoz', + 'places.importFileUnsupported': + 'Nem támogatott fájltípus. Használjon .gpx, .kml vagy .kmz fájlt.', + 'places.importFileTooLarge': + 'A fájl túl nagy. A maximális feltöltési méret {maxMb} MB.', + 'places.importFileError': 'Importálás sikertelen', + 'places.importAllSkipped': 'Minden hely már szerepel az utazásban.', + 'places.gpxImported': '{count} hely importálva GPX-ből', + 'places.gpxImportTypes': 'Mit szeretnél importálni?', + 'places.gpxImportWaypoints': 'Útpontok', + 'places.gpxImportRoutes': 'Útvonalak', + 'places.gpxImportTracks': 'Nyomvonalak (útvonalgeometriával)', + 'places.gpxImportNoneSelected': + 'Válassz legalább egy típust az importáláshoz.', + 'places.kmlImportTypes': 'Mit szeretnél importálni?', + 'places.kmlImportPoints': 'Pontok (Placemarks)', + 'places.kmlImportPaths': 'Útvonalak (LineStrings)', + 'places.kmlImportNoneSelected': 'Válassz legalább egy típust.', + 'places.selectionCount': '{count} kiválasztva', + 'places.deleteSelected': 'Kijelöltek törlése', + 'places.kmlKmzImported': '{count} hely importálva KMZ/KML-ből', + 'places.urlResolved': 'Hely importálva URL-ből', + 'places.importList': 'Lista importálás', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importálva: {created} • Kihagyva: {skipped}', + 'places.importGoogleList': 'Google Lista', + 'places.importNaverList': 'Naver Lista', + 'places.googleListHint': + 'Illessz be egy megosztott Google Maps lista linket az osszes hely importalasahoz.', + 'places.googleListImported': '{count} hely importalva a(z) "{list}" listabol', + 'places.googleListError': 'Google Maps lista importalasa sikertelen', + 'places.naverListHint': + 'Illessz be egy megosztott Naver Maps lista linket az összes hely importálásához.', + 'places.naverListImported': '{count} hely importálva a(z) "{list}" listából', + 'places.naverListError': 'Naver Maps lista importálása sikertelen', + 'places.viewDetails': 'Részletek megtekintése', + 'places.assignToDay': 'Melyik naphoz adod?', + 'places.all': 'Összes', + 'places.unplanned': 'Nem tervezett', + 'places.filterTracks': 'Nyomvonalak', + 'places.search': 'Helyek keresése...', + 'places.allCategories': 'Összes kategória', + 'places.categoriesSelected': 'kategória', + 'places.clearFilter': 'Szűrő törlése', + 'places.count': '{count} hely', + 'places.countSingular': '1 hely', + 'places.allPlanned': 'Minden hely be van tervezve', + 'places.noneFound': 'Nem találhatók helyek', + 'places.editPlace': 'Hely szerkesztése', + 'places.formName': 'Név', + 'places.formNamePlaceholder': 'pl. Eiffel-torony', + 'places.formDescription': 'Leírás', + 'places.formDescriptionPlaceholder': 'Rövid leírás...', + 'places.formAddress': 'Cím', + 'places.formAddressPlaceholder': 'Utca, Város, Ország', + 'places.formLat': 'Szélességi fok (pl. 48.8566)', + 'places.formLng': 'Hosszúsági fok (pl. 2.3522)', + 'places.formCategory': 'Kategória', + 'places.noCategory': 'Nincs kategória', + 'places.categoryNamePlaceholder': 'Kategória neve', + 'places.formTime': 'Időpont', + 'places.startTime': 'Kezdés', + 'places.endTime': 'Befejezés', + 'places.endTimeBeforeStart': 'A befejezési idő a kezdési idő előtt van', + 'places.timeCollision': 'Időbeli átfedés:', + 'places.formWebsite': 'Weboldal', + 'places.formNotes': 'Jegyzetek', + 'places.formNotesPlaceholder': 'Személyes jegyzetek...', + 'places.formReservation': 'Foglalás', + 'places.reservationNotesPlaceholder': + 'Foglalási jegyzetek, visszaigazolási szám...', + 'places.mapsSearchPlaceholder': 'Helyek keresése...', + 'places.mapsSearchError': 'Helykeresés sikertelen.', + 'places.loadingDetails': 'Hely adatainak betöltése…', + 'places.osmHint': + 'OpenStreetMap keresés aktív (képek, nyitvatartás és értékelések nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', + 'places.osmActive': + 'Keresés OpenStreetMap-en keresztül (képek, értékelések és nyitvatartás nélkül). Bővített adatokhoz add meg a Google API kulcsot a beállításokban.', + 'places.categoryCreateError': 'Nem sikerült létrehozni a kategóriát', + 'places.nameRequired': 'Kérjük, adj meg egy nevet', + 'places.saveError': 'Nem sikerült menteni', +}; +export default places; diff --git a/shared/src/i18n/hu/planner.ts b/shared/src/i18n/hu/planner.ts new file mode 100644 index 00000000..a07d248f --- /dev/null +++ b/shared/src/i18n/hu/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Helyek', + 'planner.bookings': 'Foglalások', + 'planner.packingList': 'Csomagolási lista', + 'planner.documents': 'Dokumentumok', + 'planner.dayPlan': 'Napi terv', + 'planner.reservations': 'Foglalások', + 'planner.minTwoPlaces': 'Legalább 2 koordinátákkal rendelkező hely szükséges', + 'planner.noGeoPlaces': 'Nincsenek koordinátákkal rendelkező helyek', + 'planner.routeCalculated': 'Útvonal kiszámítva', + 'planner.routeCalcFailed': 'Nem sikerült kiszámítani az útvonalat', + 'planner.routeError': 'Hiba az útvonalszámítás során', + 'planner.icsExportFailed': 'Az ICS-exportálás sikertelen', + 'planner.routeOptimized': 'Útvonal optimalizálva', + 'planner.reservationUpdated': 'Foglalás frissítve', + 'planner.reservationAdded': 'Foglalás hozzáadva', + 'planner.confirmDeleteReservation': 'Foglalás törlése?', + 'planner.reservationDeleted': 'Foglalás törölve', + 'planner.days': 'nap', + 'planner.allPlaces': 'Összes hely', + 'planner.totalPlaces': 'Összesen {n} hely', + 'planner.noDaysPlanned': 'Még nincsenek napok tervezve', + 'planner.editTrip': 'Utazás szerkesztése →', + 'planner.placeOne': '1 hely', + 'planner.placeN': '{n} hely', + 'planner.addNote': 'Jegyzet hozzáadása', + 'planner.noEntries': 'Nincsenek bejegyzések erre a napra', + 'planner.addPlace': 'Hely/tevékenység hozzáadása', + 'planner.addPlaceShort': '+ Hely/tevékenység hozzáadása', + 'planner.resPending': 'Foglalás függőben · ', + 'planner.resConfirmed': 'Foglalás megerősítve · ', + 'planner.notePlaceholder': 'Jegyzet…', + 'planner.noteTimePlaceholder': 'Időpont (opcionális)', + 'planner.noteExamplePlaceholder': + 'pl. S3 14:30-kor a főpályaudvarról, komp a 7. mólóról, ebédszünet…', + 'planner.totalCost': 'Összköltség', + 'planner.searchPlaces': 'Helyek keresése…', + 'planner.allCategories': 'Összes kategória', + 'planner.noPlacesFound': 'Nem találhatók helyek', + 'planner.addFirstPlace': 'Első hely hozzáadása', + 'planner.noReservations': 'Nincsenek foglalások', + 'planner.addFirstReservation': 'Első foglalás hozzáadása', + 'planner.new': 'Új', + 'planner.addToDay': '+ Nap', + 'planner.calculating': 'Számítás…', + 'planner.route': 'Útvonal', + 'planner.optimize': 'Optimalizálás', + 'planner.openGoogleMaps': 'Megnyitás a Google Térképben', + 'planner.selectDayHint': + 'Válassz egy napot a bal oldali listából a napi terv megtekintéséhez', + 'planner.noPlacesForDay': 'Még nincsenek helyek erre a napra', + 'planner.addPlacesLink': 'Helyek hozzáadása →', + 'planner.minTotal': 'perc összesen', + 'planner.noReservation': 'Nincs foglalás', + 'planner.removeFromDay': 'Eltávolítás a napról', + 'planner.addToThisDay': 'Hozzáadás a naphoz', + 'planner.overview': 'Áttekintés', + 'planner.noDays': 'Még nincsenek napok', + 'planner.editTripToAddDays': 'Szerkeszd az utazást napok hozzáadásához', + 'planner.dayCount': '{n} nap', + 'planner.clickToUnlock': 'Kattints a feloldáshoz', + 'planner.keepPosition': 'Pozíció megtartása útvonal-optimalizálás során', + 'planner.dayDetails': 'Nap részletei', + 'planner.dayN': '{n}. nap', +}; +export default planner; diff --git a/shared/src/i18n/hu/register.ts b/shared/src/i18n/hu/register.ts new file mode 100644 index 00000000..d0e2e605 --- /dev/null +++ b/shared/src/i18n/hu/register.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'A jelszavak nem egyeznek', + 'register.passwordTooShort': + 'A jelszónak legalább 8 karakter hosszúnak kell lennie', + 'register.failed': 'Regisztráció sikertelen', + 'register.getStarted': 'Kezdjük', + 'register.subtitle': + 'Hozz létre egy fiókot, és kezdd el megtervezni álomutazásaidat.', + 'register.feature1': 'Korlátlan utazási tervek', + 'register.feature2': 'Interaktív térképnézet', + 'register.feature3': 'Helyek és kategóriák kezelése', + 'register.feature4': 'Foglalások nyomon követése', + 'register.feature5': 'Csomagolási listák készítése', + 'register.feature6': 'Fényképek és fájlok tárolása', + 'register.createAccount': 'Fiók létrehozása', + 'register.startPlanning': 'Kezdd el az utazástervezést', + 'register.minChars': 'Min. 6 karakter', + 'register.confirmPassword': 'Jelszó megerősítése', + 'register.repeatPassword': 'Jelszó ismétlése', + 'register.registering': 'Regisztráció...', + 'register.register': 'Regisztráció', + 'register.hasAccount': 'Már van fiókod?', + 'register.signIn': 'Bejelentkezés', +}; +export default register; diff --git a/shared/src/i18n/hu/reservations.ts b/shared/src/i18n/hu/reservations.ts new file mode 100644 index 00000000..5ae8373f --- /dev/null +++ b/shared/src/i18n/hu/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Foglalások', + 'reservations.empty': 'Még nincsenek foglalások', + 'reservations.emptyHint': + 'Adj hozzá foglalásokat repülőkhöz, szállodákhoz és egyebekhez', + 'reservations.add': 'Foglalás hozzáadása', + 'reservations.addManual': 'Kézi foglalás', + 'reservations.placeHint': + 'Tipp: A foglalásokat legjobb közvetlenül egy helyről létrehozni, hogy összekapcsolódjon a napi tervvel.', + 'reservations.confirmed': 'Megerősítve', + 'reservations.pending': 'Függőben', + 'reservations.summary': '{confirmed} megerősítve, {pending} függőben', + 'reservations.fromPlan': 'Tervből', + 'reservations.showFiles': 'Fájlok megjelenítése', + 'reservations.editTitle': 'Foglalás szerkesztése', + 'reservations.status': 'Állapot', + 'reservations.datetime': 'Dátum és idő', + 'reservations.startTime': 'Kezdési idő', + 'reservations.endTime': 'Befejezési idő', + 'reservations.date': 'Dátum', + 'reservations.time': 'Időpont', + 'reservations.timeAlt': 'Időpont (alternatív, pl. 19:30)', + 'reservations.linkExisting': 'Meglévő fájl csatolása', + 'reservations.notes': 'Jegyzetek', + 'reservations.notesPlaceholder': 'További jegyzetek...', + 'reservations.meta.airline': 'Légitársaság', + 'reservations.meta.flightNumber': 'Járatszám', + 'reservations.meta.from': 'Honnan', + 'reservations.meta.to': 'Hová', + 'reservations.needsReview': 'Ellenőrzés', + 'reservations.needsReviewHint': + 'A repülőteret nem sikerült automatikusan azonosítani — erősítsd meg a helyet.', + 'reservations.searchLocation': 'Állomás, kikötő, cím keresése...', + 'reservations.meta.trainNumber': 'Vonatszám', + 'reservations.meta.platform': 'Vágány', + 'reservations.meta.seat': 'Ülés', + 'reservations.meta.checkIn': 'Bejelentkezés', + 'reservations.meta.checkInUntil': 'Bejelentkezés eddig', + 'reservations.meta.checkOut': 'Kijelentkezés', + 'reservations.meta.linkAccommodation': 'Szállás', + 'reservations.meta.pickAccommodation': 'Szállás hozzárendelése', + 'reservations.meta.noAccommodation': 'Nincs', + 'reservations.meta.hotelPlace': 'Szálloda', + 'reservations.meta.pickHotel': 'Szálloda kiválasztása', + 'reservations.meta.fromDay': 'Ettől', + 'reservations.meta.toDay': 'Eddig', + 'reservations.meta.selectDay': 'Nap kiválasztása', + 'reservations.type.flight': 'Repülő', + 'reservations.type.hotel': 'Szálloda', + 'reservations.type.restaurant': 'Étterem', + 'reservations.type.train': 'Vonat', + 'reservations.type.car': 'Autó', + 'reservations.type.cruise': 'Hajóút', + 'reservations.type.event': 'Esemény', + 'reservations.type.tour': 'Túra', + 'reservations.type.other': 'Egyéb', + 'reservations.confirm.delete': + 'Biztosan törölni szeretnéd a(z) "{name}" foglalást?', + 'reservations.confirm.deleteTitle': 'Foglalás törlése?', + 'reservations.confirm.deleteBody': '"{name}" véglegesen törlődik.', + 'reservations.toast.updated': 'Foglalás frissítve', + 'reservations.toast.removed': 'Foglalás törölve', + 'reservations.toast.fileUploaded': 'Fájl feltöltve', + 'reservations.toast.uploadError': 'Feltöltés sikertelen', + 'reservations.newTitle': 'Új foglalás', + 'reservations.bookingType': 'Foglalás típusa', + 'reservations.titleLabel': 'Cím', + 'reservations.titlePlaceholder': 'pl. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Helyszín / Cím', + 'reservations.locationPlaceholder': 'Cím, Repülőtér, Szálloda...', + 'reservations.confirmationCode': 'Foglalási kód', + 'reservations.confirmationPlaceholder': 'pl. ABC12345', + 'reservations.day': 'Nap', + 'reservations.noDay': 'Nincs nap', + 'reservations.place': 'Hely', + 'reservations.noPlace': 'Nincs hely', + 'reservations.pendingSave': 'mentés…', + 'reservations.uploading': 'Feltöltés...', + 'reservations.attachFile': 'Fájl csatolása', + 'reservations.toast.saveError': 'Nem sikerült menteni', + 'reservations.toast.updateError': 'Nem sikerült frissíteni', + 'reservations.toast.deleteError': 'Nem sikerült törölni', + 'reservations.confirm.remove': '"{name}" foglalás eltávolítása?', + 'reservations.linkAssignment': 'Összekapcsolás napi tervvel', + 'reservations.pickAssignment': 'Válassz hozzárendelést a tervedből...', + 'reservations.noAssignment': 'Nincs összekapcsolás (önálló)', + 'reservations.price': 'Ár', + 'reservations.budgetCategory': 'Költségvetési kategória', + 'reservations.budgetCategoryPlaceholder': 'pl. Közlekedés, Szállás', + 'reservations.budgetCategoryAuto': 'Automatikus (foglalás típusa alapján)', + 'reservations.budgetHint': + 'Mentéskor automatikusan létrejön egy költségvetési tétel.', + 'reservations.departureDate': 'Indulás', + 'reservations.arrivalDate': 'Érkezés', + 'reservations.departureTime': 'Indulási idő', + 'reservations.arrivalTime': 'Érkezési idő', + 'reservations.pickupDate': 'Felvétel', + 'reservations.returnDate': 'Visszaadás', + 'reservations.pickupTime': 'Felvétel ideje', + 'reservations.returnTime': 'Visszaadás ideje', + 'reservations.endDate': 'Befejezés dátuma', + 'reservations.meta.departureTimezone': 'TZ indulás', + 'reservations.meta.arrivalTimezone': 'TZ érkezés', + 'reservations.span.departure': 'Indulás', + 'reservations.span.arrival': 'Érkezés', + 'reservations.span.inTransit': 'Úton', + 'reservations.span.pickup': 'Felvétel', + 'reservations.span.return': 'Visszaadás', + 'reservations.span.active': 'Aktív', + 'reservations.span.start': 'Kezdés', + 'reservations.span.end': 'Vége', + 'reservations.span.ongoing': 'Folyamatban', + 'reservations.validation.endBeforeStart': + 'A befejezés dátuma/időpontja a kezdés utáni kell legyen', + 'reservations.addBooking': 'Foglalás hozzáadása', +}; +export default reservations; diff --git a/shared/src/i18n/hu/settings.ts b/shared/src/i18n/hu/settings.ts new file mode 100644 index 00000000..8a33689f --- /dev/null +++ b/shared/src/i18n/hu/settings.ts @@ -0,0 +1,301 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Beállítások', + 'settings.subtitle': 'Személyes beállítások konfigurálása', + 'settings.tabs.display': 'Megjelenés', + 'settings.tabs.map': 'Térkép', + 'settings.tabs.notifications': 'Értesítések', + 'settings.tabs.integrations': 'Integrációk', + 'settings.tabs.account': 'Fiók', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Névjegy', + 'settings.map': 'Térkép', + 'settings.mapTemplate': 'Térkép sablon', + 'settings.mapTemplatePlaceholder.select': 'Sablon kiválasztása...', + 'settings.mapDefaultHint': + 'Hagyd üresen az OpenStreetMap használatához (alapértelmezett)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL sablon a térképcsempékhez', + 'settings.mapProvider': 'Térkép szolgáltató', + 'settings.mapProviderHint': + 'A Trip Planner és Journey térképekre érvényes. Az Atlas mindig Leafletet használ.', + 'settings.mapLeafletSubtitle': 'Klasszikus 2D, bármilyen raszter csempe', + 'settings.mapMapboxSubtitle': 'Vektoros csempék, 3D épületek és terep', + 'settings.mapExperimental': 'Kísérleti', + 'settings.mapMapboxToken': 'Mapbox hozzáférési token', + 'settings.mapMapboxTokenHint': 'Publikus token (pk.*) innen:', + 'settings.mapMapboxTokenLink': 'mapbox.com → Hozzáférési tokenek', + 'settings.mapStyle': 'Térkép stílus', + 'settings.mapStylePlaceholder': 'Válassz Mapbox stílust', + 'settings.mapStyleHint': 'Preset vagy saját mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D épületek és terep', + 'settings.map3dHint': + 'Dőlés + valódi 3D épület-kiemelés — minden stílussal működik, beleértve a műholdast.', + 'settings.mapHighQuality': 'Magas minőség mód', + 'settings.mapHighQualityHint': + 'Antialiasing + földgömb-vetítés az élesebb kontúrokért és egy valósághű világnézethez.', + 'settings.mapHighQualityWarning': + 'Gyengébb eszközökön befolyásolhatja a teljesítményt.', + 'settings.mapTipLabel': 'Tipp:', + 'settings.mapTip': + 'Jobb klikk és húzás a térkép forgatásához/döntéséhez. Középső kattintás hely hozzáadásához (a jobb klikk a forgatáshoz van fenntartva).', + 'settings.latitude': 'Szélességi fok', + 'settings.longitude': 'Hosszúsági fok', + 'settings.saveMap': 'Térkép mentése', + 'settings.apiKeys': 'API kulcsok', + 'settings.mapsKey': 'Google Maps API kulcs', + 'settings.mapsKeyHint': + 'Helykereséséhez. Places API (New) szükséges. Létrehozás: console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API kulcs', + 'settings.weatherKeyHint': + 'Időjárás adatokhoz. Ingyenes: openweathermap.org/api', + 'settings.keyPlaceholder': 'Kulcs megadása...', + 'settings.configured': 'Konfigurálva', + 'settings.saveKeys': 'Kulcsok mentése', + 'settings.display': 'Megjelenítés', + 'settings.colorMode': 'Színmód', + 'settings.light': 'Világos', + 'settings.dark': 'Sötét', + 'settings.auto': 'Automatikus', + 'settings.language': 'Nyelv', + 'settings.temperature': 'Hőmérséklet egység', + 'settings.timeFormat': 'Időformátum', + 'settings.blurBookingCodes': 'Foglalási kódok elrejtése', + 'settings.notifications': 'Értesítések', + 'settings.notifyTripInvite': 'Utazási meghívók', + 'settings.notifyBookingChange': 'Foglalási változások', + 'settings.notifyTripReminder': 'Utazási emlékeztetők', + 'settings.notifyTodoDue': 'Teendő esedékes', + 'settings.notifyVacayInvite': 'Vacay összevonási meghívók', + 'settings.notifyPhotosShared': 'Megosztott fotók (Immich)', + 'settings.notifyCollabMessage': 'Csevegés üzenetek (Collab)', + 'settings.notifyPackingTagged': 'Csomagolási lista: hozzárendelések', + 'settings.notifyWebhook': 'Webhook értesítések', + 'settings.notificationsDisabled': + 'Az értesítések nincsenek beállítva. Kérje meg a rendszergazdát, hogy engedélyezze az e-mail vagy webhook értesítéseket.', + 'settings.notificationsActive': 'Aktív csatorna', + 'settings.notificationsManagedByAdmin': + 'Az értesítési eseményeket az adminisztrátor konfigurálja.', + 'settings.on': 'Be', + 'settings.off': 'Ki', + 'settings.mcp.title': 'MCP konfiguráció', + 'settings.mcp.endpoint': 'MCP végpont', + 'settings.mcp.clientConfig': 'Kliens konfiguráció', + 'settings.mcp.clientConfigHint': + 'Cserélje ki a részt egy API tokenre az alábbi listából. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).', + 'settings.mcp.clientConfigHintOAuth': + 'Cserélje ki a és részeket a fent létrehozott OAuth 2.1 kliens adataival. Az mcp-remote megnyitja a böngészőt az első csatlakozáskor az engedélyezés elvégzéséhez. Az npx elérési útját szükség lehet módosítani a rendszeréhez (pl. C:\\PROGRA~1\\nodejs\\npx.cmd Windows-on).', + 'settings.mcp.copy': 'Másolás', + 'settings.mcp.copied': 'Másolva!', + 'settings.mcp.apiTokens': 'API tokenek', + 'settings.mcp.createToken': 'Új token létrehozása', + 'settings.mcp.noTokens': + 'Még nincsenek tokenek. Hozzon létre egyet MCP kliensek csatlakoztatásához.', + 'settings.mcp.tokenCreatedAt': 'Létrehozva', + 'settings.mcp.tokenUsedAt': 'Használva', + 'settings.mcp.deleteTokenTitle': 'Token törlése', + 'settings.mcp.deleteTokenMessage': + 'Ez a token azonnal érvénytelenné válik. Minden MCP kliens, amely használja, elveszíti a hozzáférést.', + 'settings.mcp.modal.createTitle': 'API token létrehozása', + 'settings.mcp.modal.tokenName': 'Token neve', + 'settings.mcp.modal.tokenNamePlaceholder': + 'pl. Claude Desktop, Munkahelyi laptop', + 'settings.mcp.modal.creating': 'Létrehozás…', + 'settings.mcp.modal.create': 'Token létrehozása', + 'settings.mcp.modal.createdTitle': 'Token létrehozva', + 'settings.mcp.modal.createdWarning': + 'Ez a token csak egyszer jelenik meg. Másolja és mentse el most — nem lehet visszaállítani.', + 'settings.mcp.modal.done': 'Kész', + 'settings.mcp.toast.created': 'Token létrehozva', + 'settings.mcp.toast.createError': 'Nem sikerült létrehozni a tokent', + 'settings.mcp.toast.deleted': 'Token törölve', + 'settings.mcp.toast.deleteError': 'Nem sikerült törölni a tokent', + 'settings.mcp.apiTokensDeprecated': + 'Az API tokenek elavultak és egy jövőbeli verzióban eltávolításra kerülnek. Kérjük, használjon helyettük OAuth 2.1 klienseket.', + 'settings.oauth.clients': 'OAuth 2.1 kliensek', + 'settings.oauth.clientsHint': + 'Regisztráljon OAuth 2.1 klienseket, hogy a harmadik féltől származó MCP alkalmazások (Claude Web, Cursor stb.) statikus tokenek nélkül csatlakozhassanak.', + 'settings.oauth.createClient': 'Új kliens', + 'settings.oauth.noClients': 'Nincs regisztrált OAuth kliens.', + 'settings.oauth.clientId': 'Kliens azonosító', + 'settings.oauth.clientSecret': 'Kliens titok', + 'settings.oauth.deleteClient': 'Kliens törlése', + 'settings.oauth.deleteClientMessage': + 'Ez a kliens és az összes aktív munkamenet véglegesen törlésre kerül. Minden alkalmazás, amely ezt használja, azonnal elveszíti a hozzáférést.', + 'settings.oauth.rotateSecret': 'Titok megújítása', + 'settings.oauth.rotateSecretMessage': + 'Új kliens titok kerül generálásra és az összes meglévő munkamenet azonnal érvénytelenné válik. Frissítse alkalmazását a párbeszéd bezárása előtt.', + 'settings.oauth.rotateSecretConfirm': 'Megújítás', + 'settings.oauth.rotateSecretConfirming': 'Megújítás…', + 'settings.oauth.rotateSecretDoneTitle': 'Új titok generálva', + 'settings.oauth.rotateSecretDoneWarning': + 'Ez a titok csak egyszer jelenik meg. Másolja most és frissítse alkalmazását — az összes korábbi munkamenet érvénytelenné vált.', + 'settings.oauth.activeSessions': 'Aktív OAuth munkamenetek', + 'settings.oauth.sessionScopes': 'Jogosultságok', + 'settings.oauth.sessionExpires': 'Lejár', + 'settings.oauth.revoke': 'Visszavonás', + 'settings.oauth.revokeSession': 'Munkamenet visszavonása', + 'settings.oauth.revokeSessionMessage': + 'Ez azonnal visszavonja a hozzáférést ehhez az OAuth munkamenethez.', + 'settings.oauth.modal.createTitle': 'OAuth kliens regisztrálása', + 'settings.oauth.modal.presets': 'Gyors beállítások', + 'settings.oauth.modal.clientName': 'Alkalmazás neve', + 'settings.oauth.modal.clientNamePlaceholder': + 'pl. Claude Web, Az én MCP appom', + 'settings.oauth.modal.redirectUris': 'Átirányítási URI-k', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Soronként egy URI. HTTPS szükséges (localhost kivételével). Pontos egyezés szükséges.', + 'settings.oauth.modal.scopes': 'Engedélyezett jogosultságok', + 'settings.oauth.modal.scopesHint': + 'A list_trips és get_trip_summary mindig elérhető — jogosultság nélkül. Segítenek az AI-nak megtalálni az utazás azonosítókat.', + 'settings.oauth.modal.selectAll': 'Összes kijelölése', + 'settings.oauth.modal.deselectAll': 'Összes kijelölés törlése', + 'settings.oauth.modal.creating': 'Regisztrálás…', + 'settings.oauth.modal.create': 'Kliens regisztrálása', + 'settings.oauth.modal.createdTitle': 'Kliens regisztrálva', + 'settings.oauth.modal.createdWarning': + 'A kliens titok csak egyszer jelenik meg. Másolja most — nem állítható helyre.', + 'settings.oauth.toast.createError': + 'Az OAuth kliens regisztrálása sikertelen', + 'settings.oauth.toast.deleted': 'OAuth kliens törölve', + 'settings.oauth.toast.deleteError': 'Az OAuth kliens törlése sikertelen', + 'settings.oauth.toast.revoked': 'Munkamenet visszavonva', + 'settings.oauth.toast.revokeError': 'A munkamenet visszavonása sikertelen', + 'settings.oauth.toast.rotateError': 'A kliens titok megújítása sikertelen', + 'settings.oauth.modal.machineClient': + 'Gépi kliens (böngészős bejelentkezés nélkül)', + 'settings.oauth.modal.machineClientHint': + 'client_credentials grant használata — nincs szükség átirányítási URI-kra. A token közvetlenül client_id + client_secret segítségével kerül kiállításra, és a kiválasztott hatókörökön belül az Ön nevében jár el.', + 'settings.oauth.modal.machineClientUsage': + 'Token lekérése: POST /oauth/token a grant_type=client_credentials, client_id és client_secret értékekkel. Böngésző és frissítési token nélkül.', + 'settings.oauth.badge.machine': 'gépi', + 'settings.account': 'Fiók', + 'settings.about': 'Névjegy', + 'settings.about.reportBug': 'Hiba bejelentése', + 'settings.about.reportBugHint': 'Problémát találtál? Jelezd nekünk', + 'settings.about.featureRequest': 'Funkció javaslat', + 'settings.about.featureRequestHint': 'Javasolj egy új funkciót', + 'settings.about.wikiHint': 'Dokumentáció és útmutatók', + 'settings.about.supporters.badge': 'Havi támogatók', + 'settings.about.supporters.title': 'Útitársak a TREK mellett', + 'settings.about.supporters.subtitle': + 'Miközben te a következő útvonaladat tervezed, ők a TREK jövőjét tervezik velem együtt. Havi hozzájárulásuk közvetlenül fejlesztésre és valódi órákra fordítódik — hogy a TREK Open Source maradhasson.', + 'settings.about.supporters.since': 'támogató {date} óta', + 'settings.about.supporters.tierEmpty': 'Légy az első', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'A TREK egy saját szerveren üzemeltetett útitervező, amely segít az utazásaid megszervezésében az első ötlettől az utolsó emlékig. Napi tervezés, költségvetés, csomagolási listák, fotók és még sok más — minden egy helyen, a saját szervereden.', + 'settings.about.madeWith': 'Készítve', + 'settings.about.madeBy': + 'Maurice és egy növekvő nyílt forráskódú közösség által.', + 'settings.username': 'Felhasználónév', + 'settings.email': 'E-mail', + 'settings.role': 'Szerepkör', + 'settings.roleAdmin': 'Adminisztrátor', + 'settings.oidcLinked': 'Összekapcsolva:', + 'settings.changePassword': 'Jelszó módosítása', + 'settings.currentPassword': 'Jelenlegi jelszó', + 'settings.newPassword': 'Új jelszó', + 'settings.confirmPassword': 'Új jelszó megerősítése', + 'settings.updatePassword': 'Jelszó frissítése', + 'settings.passwordRequired': 'Kérjük, add meg a jelenlegi és az új jelszót', + 'settings.currentPasswordRequired': 'A jelenlegi jelszó megadása kötelező', + 'settings.passwordTooShort': + 'A jelszónak legalább 8 karakter hosszúnak kell lennie', + 'settings.passwordWeak': + 'A jelszónak tartalmaznia kell nagybetűt, kisbetűt, számot és speciális karaktert', + 'settings.passwordMismatch': 'A jelszavak nem egyeznek', + 'settings.passwordChanged': 'Jelszó sikeresen módosítva', + 'settings.deleteAccount': 'Törlés', + 'settings.deleteAccountTitle': 'Biztosan törölni szeretnéd a fiókodat?', + 'settings.deleteAccountWarning': + 'A fiókod és minden utazásod, helyed és fájlod véglegesen törlődik. Ez a művelet nem vonható vissza.', + 'settings.deleteAccountConfirm': 'Végleges törlés', + 'settings.deleteBlockedTitle': 'Törlés nem lehetséges', + 'settings.deleteBlockedMessage': + 'Te vagy az egyetlen adminisztrátor. Nevezz ki egy másik felhasználót adminnak, mielőtt törölnéd a fiókodat.', + 'settings.roleUser': 'Felhasználó', + 'settings.saveProfile': 'Mentés', + 'settings.toast.mapSaved': 'Térképbeállítások mentve', + 'settings.toast.keysSaved': 'API kulcsok mentve', + 'settings.toast.displaySaved': 'Megjelenítési beállítások mentve', + 'settings.toast.profileSaved': 'Profil frissítve', + 'settings.uploadAvatar': 'Profilkép feltöltése', + 'settings.removeAvatar': 'Profilkép eltávolítása', + 'settings.avatarUploaded': 'Profilkép frissítve', + 'settings.avatarRemoved': 'Profilkép eltávolítva', + 'settings.avatarError': 'Feltöltés sikertelen', + 'settings.mfa.title': 'Kétfaktoros hitelesítés (2FA)', + 'settings.mfa.description': + 'Egy második lépést ad a bejelentkezéshez e-mail és jelszó használatakor. Használj hitelesítő alkalmazást (Google Authenticator, Authy stb.).', + 'settings.mfa.requiredByPolicy': + 'A rendszergazda kétlépcsős hitelesítést ír elő. Állíts be hitelesítő alkalmazást lent, mielőtt továbblépnél.', + 'settings.mfa.backupTitle': 'Tartalék kódok', + 'settings.mfa.backupDescription': + 'Használd ezeket az egyszer használatos kódokat, ha elveszíted a hozzáférést a hitelesítő alkalmazásodhoz.', + 'settings.mfa.backupWarning': + 'Mentsd el ezeket most. Minden kód csak egyszer használható.', + 'settings.mfa.backupCopy': 'Kódok másolása', + 'settings.mfa.backupDownload': 'TXT letöltése', + 'settings.mfa.backupPrint': 'Nyomtatás / PDF', + 'settings.mfa.backupCopied': 'Tartalék kódok másolva', + 'settings.mfa.enabled': '2FA engedélyezve van a fiókodban.', + 'settings.mfa.disabled': '2FA nincs engedélyezve.', + 'settings.mfa.setup': 'Hitelesítő beállítása', + 'settings.mfa.scanQr': + 'Olvasd be ezt a QR-kódot az alkalmazásoddal, vagy add meg manuálisan a titkos kulcsot.', + 'settings.mfa.secretLabel': 'Titkos kulcs (kézi megadás)', + 'settings.mfa.codePlaceholder': '6 jegyű kód', + 'settings.mfa.enable': '2FA engedélyezése', + 'settings.mfa.cancelSetup': 'Mégse', + 'settings.mfa.disableTitle': '2FA kikapcsolása', + 'settings.mfa.disableHint': + 'Add meg a fiókod jelszavát és a hitelesítő alkalmazás aktuális kódját.', + 'settings.mfa.disable': '2FA kikapcsolása', + 'settings.mfa.toastEnabled': 'Kétfaktoros hitelesítés engedélyezve', + 'settings.mfa.toastDisabled': 'Kétfaktoros hitelesítés kikapcsolva', + 'settings.mfa.demoBlocked': 'Demo módban nem érhető el', + 'settings.mustChangePassword': + 'A folytatás előtt meg kell változtatnod a jelszavad. Kérjük, adj meg egy új jelszót alább.', + 'settings.bookingLabels': 'Útvonal-címkék a foglalásokhoz', + 'settings.bookingLabelsHint': + 'Állomás- / repülőtér-nevek megjelenítése a térképen. Ha ki van kapcsolva, csak az ikon látszik.', + 'settings.notifyVersionAvailable': 'Új verzió elérhető', + 'settings.notificationPreferences.noChannels': + 'Nincsenek értesítési csatornák beállítva. Kérd meg a rendszergazdát, hogy állítson be e-mail vagy webhook értesítéseket.', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Adja meg a Discord, Slack vagy egyéni webhook URL-jét az értesítések fogadásához.', + 'settings.webhookUrl.saved': 'Webhook URL mentve', + 'settings.webhookUrl.test': 'Teszt', + 'settings.webhookUrl.testSuccess': 'Teszt webhook sikeresen elküldve', + 'settings.webhookUrl.testFailed': 'Teszt webhook sikertelen', + 'settings.ntfyUrl.topicLabel': 'Ntfy téma', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy szerver URL (opcionális)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Add meg az Ntfy témádat push értesítések fogadásához. Hagyd üresen a szervert a rendszergazda által beállított alapértelmezett használatához.', + 'settings.ntfyUrl.tokenLabel': 'Hozzáférési token (opcionális)', + 'settings.ntfyUrl.tokenHint': 'Jelszóval védett témákhoz szükséges.', + 'settings.ntfyUrl.saved': 'Ntfy beállítások mentve', + 'settings.ntfyUrl.test': 'Teszt', + 'settings.ntfyUrl.testSuccess': 'Teszt Ntfy értesítés sikeresen elküldve', + 'settings.ntfyUrl.testFailed': 'Teszt Ntfy értesítés sikertelen', + 'settings.ntfyUrl.tokenCleared': 'Hozzáférési token törölve', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/hu/share.ts b/shared/src/i18n/hu/share.ts new file mode 100644 index 00000000..52dbea15 --- /dev/null +++ b/shared/src/i18n/hu/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Nyilvános link', + 'share.linkHint': + 'Hozz létre egy linket, amellyel bárki megtekintheti ezt az utazást bejelentkezés nélkül. Csak olvasható — szerkesztés nem lehetséges.', + 'share.createLink': 'Link létrehozása', + 'share.deleteLink': 'Link törlése', + 'share.createError': 'Nem sikerült létrehozni a linket', + 'share.permMap': 'Térkép és terv', + 'share.permBookings': 'Foglalások', + 'share.permPacking': 'Csomagolás', + 'share.permBudget': 'Költségvetés', + 'share.permCollab': 'Csevegés', +}; +export default share; diff --git a/shared/src/i18n/hu/shared.ts b/shared/src/i18n/hu/shared.ts new file mode 100644 index 00000000..338c1efe --- /dev/null +++ b/shared/src/i18n/hu/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link lejárt vagy érvénytelen', + 'shared.expiredHint': 'Ez a megosztott utazási link már nem aktív.', + 'shared.readOnly': 'Csak olvasható megosztott nézet', + 'shared.tabPlan': 'Terv', + 'shared.tabBookings': 'Foglalások', + 'shared.tabPacking': 'Csomagolás', + 'shared.tabBudget': 'Költségvetés', + 'shared.tabChat': 'Csevegés', + 'shared.days': 'nap', + 'shared.places': 'hely', + 'shared.other': 'Egyéb', + 'shared.totalBudget': 'Teljes költségvetés', + 'shared.messages': 'üzenet', + 'shared.sharedVia': 'Megosztva:', + 'shared.confirmed': 'Megerősítve', + 'shared.pending': 'Függőben', +}; +export default shared; diff --git a/shared/src/i18n/hu/stats.ts b/shared/src/i18n/hu/stats.ts new file mode 100644 index 00000000..c65e834f --- /dev/null +++ b/shared/src/i18n/hu/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Országok', + 'stats.cities': 'Városok', + 'stats.trips': 'Utazások', + 'stats.places': 'Helyek', + 'stats.worldProgress': 'Világ felfedezése', + 'stats.visited': 'meglátogatott', + 'stats.remaining': 'hátralévő', + 'stats.visitedCountries': 'Meglátogatott országok', +}; +export default stats; diff --git a/shared/src/i18n/hu/system_notice.ts b/shared/src/i18n/hu/system_notice.ts new file mode 100644 index 00000000..d2a08f06 --- /dev/null +++ b/shared/src/i18n/hu/system_notice.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Üdvözöl a TREK', + 'system_notice.welcome_v1.body': + 'Az összes az egyben utazástervező. Készítsen útvonalakat, ossza meg az utakat barátaival, és maradjon szervezett — online és offline.', + 'system_notice.welcome_v1.cta_label': 'Utazás tervezése', + 'system_notice.welcome_v1.hero_alt': 'Festői úticél TREK tervező felülettel', + 'system_notice.welcome_v1.highlight_plan': 'Napi útvonalak minden utazáshoz', + 'system_notice.welcome_v1.highlight_share': 'Együttműködés utazótársakkal', + 'system_notice.welcome_v1.highlight_offline': 'Mobilon offline is működik', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Előző értesítés', + 'system_notice.pager.next': 'Következő értesítés', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': '{n}. értesítésre ugrás', + 'system_notice.pager.position': '{current}/{total}. értesítés', + 'system_notice.v3_photos.title': 'A fotók helye megváltozott 3.0-ban', + 'system_notice.v3_photos.body': + 'Az útiterv-tervező **Fényképek** lapja eltávolításra került. Fényképeid biztonságban vannak — TREK soha nem módosította Immich vagy Synology könyvtáradat.\n\nA fényképek mostantól a **Journey** bővítményben élnek. A Journey opcionális — ha még nem elérhető, kérd meg a rendszergazdát, hogy engedélyezze Admin → Bővítmények alatt.', + 'system_notice.v3_journey.title': 'Ismerje meg a Journey-t — útinnapló', + 'system_notice.v3_journey.body': + 'Dokumentáld utazazsaid gazdag történetekként idővonalakkal, fotgáriákkal és interaktív térképekkel.', + 'system_notice.v3_journey.cta_label': 'Journey megnyitása', + 'system_notice.v3_journey.highlight_timeline': 'Napi idővonal és galéria', + 'system_notice.v3_journey.highlight_photos': + 'Import Immich-ből vagy Synology-ból', + 'system_notice.v3_journey.highlight_share': + 'Nyilvános megosztás — bejelentkezés nélkül', + 'system_notice.v3_journey.highlight_export': 'Exportálás PDF fotkönyvként', + 'system_notice.v3_features.title': 'További újdonságok a 3.0-ban', + 'system_notice.v3_features.body': + 'Néhány további dolog, amit érdemes tudni erről a kiadásról.', + 'system_notice.v3_features.highlight_dashboard': + 'Mobile-first irmütébla újratervezve', + 'system_notice.v3_features.highlight_offline': 'Teljes offline mód PWA-ként', + 'system_notice.v3_features.highlight_search': + 'Valós idejű helykeresés-kiegészítés', + 'system_notice.v3_features.highlight_import': + 'Helyek importálása KMZ/KML fájlokból', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 frissítés', + 'system_notice.v3_mcp.body': + 'Az MCP integráció teljesen megújult. Az OAuth 2.1 mostantól az ajánlott hitelesítési módszer. A statikus tokenek (trek_…) elavultak és egy jövőbeli kiadásban eltávolításra kerülnek.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 ajánlott (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 részletes engedélyezési hatókör', + 'system_notice.v3_mcp.highlight_deprecated': + 'Statikus trek_ tokenek elavultak', + 'system_notice.v3_mcp.highlight_tools': 'Bővített eszközkészlet és promptok', + 'system_notice.v3_thankyou.title': 'Egy személyes gondolat tőlem', + 'system_notice.v3_thankyou.body': + 'Mielőtt továbbmennél — szeretnék egy pillanatra megállni.\n\nA TREK egy hobbiprojektként indult, amit a saját utazásaimhoz építettem. Sosem gondoltam volna, hogy valami olyanná nő, amire 4000-en bízzátok a kalandjaitok tervezését. Minden csillagot, minden issue-t, minden funkciókérést — mindet elolvasom, és ezek tartanak életben a késő éjszakákon a teljes állás és az egyetem között.\n\nSzeretnétek, ha tudnátok: a TREK mindig nyílt forráskódú marad, mindig self-hosted, mindig a tiétek. Nincs nyomkövetés, nincs előfizetés, nincsenek rejtett feltételek. Csak egy eszköz, amit valaki épített, aki ugyanúgy szereti az utazást, mint ti.\n\nKülönleges köszönet [jubnl](https://github.com/jubnl)-nek — hihetetlen társsá váltál. A 3.0 nagyszerűségének nagy része a te kézjegyedet viseli. Köszönöm, hogy hittél ebben a projektben, amikor még nyers volt.\n\nÉs mindannyiótoknak, akik hibát jelentettetek, szöveget fordítottatok, megosztottátok a TREK-et egy baráttal, vagy egyszerűen csak egy utazást terveztetek vele — **köszönöm**. Ti vagytok az ok, amiért ez létezik.\n\nSok további közös kalandért.\n\n— Maurice\n\n---\n\n[Csatlakozz a közösséghez a Discordon](https://discord.gg/7Q6M6jDwzf)\n\nHa a TREK jobbá teszi az utazásaidat, egy [kis kávé](https://ko-fi.com/mauriceboe) mindig segít, hogy égve maradjanak a fények.', + 'system_notice.v3014_whitespace_collision.title': + 'Szükséges beavatkozás: felhasználói fiókütközés', + 'system_notice.v3014_whitespace_collision.body': + 'A 3.0.14-es frissítés egy vagy több felhasználónév- vagy e-mail-ütközést észlelt, amelyeket a tárolt értékek elején vagy végén lévő szóközök okoztak. Az érintett fiókok automatikusan át lettek nevezve. Ellenőrizze a szervernaplókat a **[migration] WHITESPACE COLLISION** kezdetű soroknál a felülvizsgálatot igénylő fiókok azonosításához.', +}; +export default system_notice; diff --git a/shared/src/i18n/hu/todo.ts b/shared/src/i18n/hu/todo.ts new file mode 100644 index 00000000..f1a36454 --- /dev/null +++ b/shared/src/i18n/hu/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Csomagolási lista', + 'todo.subtab.todo': 'Teendők', + 'todo.completed': 'kész', + 'todo.filter.all': 'Mind', + 'todo.filter.open': 'Nyitott', + 'todo.filter.done': 'Kész', + 'todo.uncategorized': 'Kategória nélküli', + 'todo.namePlaceholder': 'Feladat neve', + 'todo.descriptionPlaceholder': 'Leírás (opcionális)', + 'todo.unassigned': 'Nem hozzárendelt', + 'todo.noCategory': 'Nincs kategória', + 'todo.hasDescription': 'Van leírás', + 'todo.addItem': 'Új feladat', + 'todo.sidebar.sortBy': 'Rendezés', + 'todo.priority': 'Prioritás', + 'todo.newCategoryLabel': 'új', + 'todo.newCategory': 'Kategória neve', + 'todo.addCategory': 'Kategória hozzáadása', + 'todo.newItem': 'Új feladat', + 'todo.empty': 'Még nincsenek feladatok. Adj hozzá egyet a kezdéshez!', + 'todo.filter.my': 'Saját feladataim', + 'todo.filter.overdue': 'Lejárt', + 'todo.sidebar.tasks': 'Feladatok', + 'todo.sidebar.categories': 'Kategóriák', + 'todo.detail.title': 'Feladat', + 'todo.detail.description': 'Leírás', + 'todo.detail.category': 'Kategória', + 'todo.detail.dueDate': 'Határidő', + 'todo.detail.assignedTo': 'Hozzárendelve', + 'todo.detail.delete': 'Törlés', + 'todo.detail.save': 'Módosítások mentése', + 'todo.detail.create': 'Feladat létrehozása', + 'todo.detail.priority': 'Prioritás', + 'todo.detail.noPriority': 'Nincs', + 'todo.sortByPrio': 'Prioritás', +}; +export default todo; diff --git a/shared/src/i18n/hu/transport.ts b/shared/src/i18n/hu/transport.ts new file mode 100644 index 00000000..caa50cf5 --- /dev/null +++ b/shared/src/i18n/hu/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Közlekedés hozzáadása', + 'transport.modalTitle.create': 'Közlekedés hozzáadása', + 'transport.modalTitle.edit': 'Közlekedés szerkesztése', + 'transport.title': 'Közlekedés', + 'transport.addManual': 'Kézi közlekedés', +}; +export default transport; diff --git a/shared/src/i18n/hu/trip.ts b/shared/src/i18n/hu/trip.ts new file mode 100644 index 00000000..b823ce9a --- /dev/null +++ b/shared/src/i18n/hu/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Terv', + 'trip.tabs.transports': 'Közlekedés', + 'trip.tabs.reservations': 'Foglalások', + 'trip.tabs.reservationsShort': 'Foglalás', + 'trip.tabs.packing': 'Csomagolási lista', + 'trip.tabs.packingShort': 'Csomag', + 'trip.tabs.lists': 'Listák', + 'trip.tabs.listsShort': 'Listák', + 'trip.tabs.budget': 'Költségvetés', + 'trip.tabs.files': 'Fájlok', + 'trip.loading': 'Utazás betöltése...', + 'trip.mobilePlan': 'Tervezés', + 'trip.mobilePlaces': 'Helyek', + 'trip.toast.placeUpdated': 'Hely frissítve', + 'trip.toast.placeAdded': 'Hely hozzáadva', + 'trip.toast.placeDeleted': 'Hely törölve', + 'trip.toast.selectDay': 'Kérjük, először válassz egy napot', + 'trip.toast.assignedToDay': 'Hely hozzárendelve a naphoz', + 'trip.toast.reorderError': 'Nem sikerült átrendezni', + 'trip.toast.reservationUpdated': 'Foglalás frissítve', + 'trip.toast.reservationAdded': 'Foglalás hozzáadva', + 'trip.toast.deleted': 'Törölve', + 'trip.confirm.deletePlace': 'Biztosan törölni szeretnéd ezt a helyet?', + 'trip.confirm.deletePlaces': '{count} helyet töröl?', + 'trip.toast.placesDeleted': '{count} hely törölve', + 'trip.loadingPhotos': 'Helyek fotóinak betöltése...', +}; +export default trip; diff --git a/shared/src/i18n/hu/trips.ts b/shared/src/i18n/hu/trips.ts new file mode 100644 index 00000000..aafd406b --- /dev/null +++ b/shared/src/i18n/hu/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} eltávolítva', + 'trips.memberRemoveError': 'Eltávolítás sikertelen', + 'trips.memberAdded': '{username} hozzáadva', + 'trips.memberAddError': 'Hozzáadás sikertelen', + 'trips.reminder': 'Emlékeztető', + 'trips.reminderNone': 'Nincs', + 'trips.reminderDay': 'nap', + 'trips.reminderDays': 'nap', + 'trips.reminderCustom': 'Egyéni', + 'trips.reminderDaysBefore': 'nappal indulás előtt', + 'trips.reminderDisabledHint': + 'Az utazási emlékeztetők ki vannak kapcsolva. Kapcsold be az Admin > Beállítások > Értesítések menüben.', +}; +export default trips; diff --git a/shared/src/i18n/hu/undo.ts b/shared/src/i18n/hu/undo.ts new file mode 100644 index 00000000..cd13dd7e --- /dev/null +++ b/shared/src/i18n/hu/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Visszavonás', + 'undo.tooltip': 'Visszavonás: {action}', + 'undo.assignPlace': 'Hely naphoz rendelve', + 'undo.removeAssignment': 'Hely eltávolítva a napról', + 'undo.reorder': 'Helyek átrendezve', + 'undo.optimize': 'Útvonal optimalizálva', + 'undo.deletePlace': 'Hely törölve', + 'undo.deletePlaces': 'Helyek törölve', + 'undo.moveDay': 'Hely áthelyezve másik napra', + 'undo.lock': 'Hely zárolása váltva', + 'undo.importGpx': 'GPX importálás', + 'undo.importKeyholeMarkup': 'KMZ/KML importálás', + 'undo.importGoogleList': 'Google Maps importálás', + 'undo.importNaverList': 'Naver Maps importálás', + 'undo.addPlace': 'Hely hozzáadva', + 'undo.done': 'Visszavonva: {action}', +}; +export default undo; diff --git a/shared/src/i18n/hu/vacay.ts b/shared/src/i18n/hu/vacay.ts new file mode 100644 index 00000000..c97a405c --- /dev/null +++ b/shared/src/i18n/hu/vacay.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Szabadságnapok tervezése és kezelése', + 'vacay.settings': 'Beállítások', + 'vacay.year': 'Év', + 'vacay.addYear': 'Következő év hozzáadása', + 'vacay.addPrevYear': 'Előző év hozzáadása', + 'vacay.removeYear': 'Év eltávolítása', + 'vacay.removeYearConfirm': '{year} eltávolítása?', + 'vacay.removeYearHint': + 'Az adott év összes szabadság-bejegyzése és céges szabadnapja véglegesen törlődik.', + 'vacay.remove': 'Eltávolítás', + 'vacay.persons': 'Személyek', + 'vacay.noPersons': 'Nincsenek személyek hozzáadva', + 'vacay.addPerson': 'Személy hozzáadása', + 'vacay.editPerson': 'Személy szerkesztése', + 'vacay.removePerson': 'Személy eltávolítása', + 'vacay.removePersonConfirm': '{name} eltávolítása?', + 'vacay.removePersonHint': + 'A személy összes szabadság-bejegyzése véglegesen törlődik.', + 'vacay.personName': 'Név', + 'vacay.personNamePlaceholder': 'Név megadása', + 'vacay.color': 'Szín', + 'vacay.add': 'Hozzáadás', + 'vacay.legend': 'Jelmagyarázat', + 'vacay.publicHoliday': 'Ünnepnap', + 'vacay.companyHoliday': 'Céges szabadnap', + 'vacay.weekend': 'Hétvége', + 'vacay.modeVacation': 'Szabadság', + 'vacay.modeCompany': 'Céges szabadnap', + 'vacay.entitlement': 'Szabadságkeret', + 'vacay.entitlementDays': 'nap', + 'vacay.used': 'Felhasznált', + 'vacay.remaining': 'Maradt', + 'vacay.carriedOver': '{year}-ból/ből', + 'vacay.blockWeekends': 'Hétvégék zárolása', + 'vacay.blockWeekendsHint': + 'Szabadság-bejegyzések megakadályozása szombaton és vasárnap', + 'vacay.publicHolidays': 'Ünnepnapok', + 'vacay.publicHolidaysHint': 'Ünnepnapok megjelölése a naptárban', + 'vacay.selectCountry': 'Ország kiválasztása', + 'vacay.selectRegion': 'Régió kiválasztása (opcionális)', + 'vacay.addCalendar': 'Naptár hozzáadása', + 'vacay.calendarLabel': 'Címke (opcionális)', + 'vacay.calendarColor': 'Szín', + 'vacay.noCalendars': 'Még nincsenek ünnepnap-naptárak hozzáadva', + 'vacay.weekendDays': 'Hétvégi napok', + 'vacay.mon': 'Hé', + 'vacay.tue': 'Ke', + 'vacay.wed': 'Sze', + 'vacay.thu': 'Csü', + 'vacay.fri': 'Pé', + 'vacay.sat': 'Szo', + 'vacay.sun': 'Va', + 'vacay.companyHolidays': 'Céges szabadnapok', + 'vacay.companyHolidaysHint': + 'Céges szintű szabadnapok megjelölésének engedélyezése', + 'vacay.companyHolidaysNoDeduct': + 'A céges szabadnapok nem számítanak bele a szabadságkeretbe.', + 'vacay.weekStart': 'A hét kezdőnapja', + 'vacay.weekStartHint': + 'Válaszd ki, hogy a hét hétfőn vagy vasárnap kezdődjön', + 'vacay.carryOver': 'Szabadság átvitele', + 'vacay.carryOverHint': + 'Megmaradt szabadságnapok automatikus átvitele a következő évre', + 'vacay.sharing': 'Megosztás', + 'vacay.sharingHint': 'Szabadságterved megosztása más TREK felhasználókkal', + 'vacay.owner': 'Tulajdonos', + 'vacay.shareEmailPlaceholder': 'TREK felhasználó e-mail címe', + 'vacay.shareSuccess': 'Terv sikeresen megosztva', + 'vacay.shareError': 'Nem sikerült megosztani a tervet', + 'vacay.dissolve': 'Összevonás feloldása', + 'vacay.dissolveHint': + 'Naptárak újbóli szétválasztása. A bejegyzéseid megmaradnak.', + 'vacay.dissolveAction': 'Feloldás', + 'vacay.dissolved': 'Naptár szétválasztva', + 'vacay.fusedWith': 'Összevonva:', + 'vacay.you': 'te', + 'vacay.noData': 'Nincs adat', + 'vacay.changeColor': 'Szín módosítása', + 'vacay.inviteUser': 'Felhasználó meghívása', + 'vacay.inviteHint': + 'Hívj meg egy másik TREK felhasználót közös szabadságnaptár megosztásához.', + 'vacay.selectUser': 'Felhasználó kiválasztása', + 'vacay.sendInvite': 'Meghívó küldése', + 'vacay.inviteSent': 'Meghívó elküldve', + 'vacay.inviteError': 'Nem sikerült elküldeni a meghívót', + 'vacay.pending': 'függőben', + 'vacay.noUsersAvailable': 'Nincsenek elérhető felhasználók', + 'vacay.accept': 'Elfogadás', + 'vacay.decline': 'Elutasítás', + 'vacay.acceptFusion': 'Elfogadás és összevonás', + 'vacay.inviteTitle': 'Összevonási kérelem', + 'vacay.inviteWantsToFuse': 'szeretne megosztani veled egy szabadságnaptárat.', + 'vacay.fuseInfo1': + 'Mindketten látjátok az összes szabadság-bejegyzést egy közös naptárban.', + 'vacay.fuseInfo2': + 'Mindkét fél létrehozhat és szerkeszthet bejegyzéseket a másik számára.', + 'vacay.fuseInfo3': + 'Mindkét fél törölhet bejegyzéseket és módosíthatja a szabadságkeretet.', + 'vacay.fuseInfo4': + 'A beállítások, mint ünnepnapok és céges szabadnapok, közösen érvényesek.', + 'vacay.fuseInfo5': + 'Az összevonás bármikor feloldható bármelyik fél által. A bejegyzések megmaradnak.', +}; +export default vacay; diff --git a/shared/src/i18n/id/admin.ts b/shared/src/i18n/id/admin.ts new file mode 100644 index 00000000..181ba0ca --- /dev/null +++ b/shared/src/i18n/id/admin.ts @@ -0,0 +1,361 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notifikasi', + 'admin.notifications.hint': + 'Pilih satu saluran notifikasi. Hanya satu yang bisa aktif sekaligus.', + 'admin.notifications.none': 'Dinonaktifkan', + 'admin.notifications.email': 'Email (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Simpan pengaturan notifikasi', + 'admin.notifications.saved': 'Pengaturan notifikasi tersimpan', + 'admin.notifications.testWebhook': 'Kirim test webhook', + 'admin.notifications.testWebhookSuccess': 'Test webhook berhasil dikirim', + 'admin.notifications.testWebhookFailed': 'Test webhook gagal', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Notifikasi in-app selalu aktif dan tidak bisa dinonaktifkan secara global.', + 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'Webhook ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari webhook per pengguna dan selalu berjalan jika diatur.', + 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL tersimpan', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Test webhook berhasil dikirim', + 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook gagal', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin webhook selalu berjalan jika URL dikonfigurasi', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Memungkinkan pengguna mengonfigurasi topik ntfy mereka sendiri untuk notifikasi push. Tetapkan server default di bawah untuk mengisi setelan pengguna secara otomatis.', + 'admin.notifications.testNtfy': 'Kirim uji Ntfy', + 'admin.notifications.testNtfySuccess': 'Uji Ntfy berhasil dikirim', + 'admin.notifications.testNtfyFailed': 'Uji Ntfy gagal', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Topik Ntfy ini digunakan khusus untuk notifikasi admin (mis. peringatan versi). Terpisah dari topik per pengguna dan selalu berjalan jika dikonfigurasi.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL Server Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Juga digunakan sebagai server default untuk notifikasi ntfy pengguna. Kosongkan untuk menggunakan ntfy.sh. Pengguna dapat menggantinya di pengaturan mereka sendiri.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Topik Admin', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token Akses (opsional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Token akses admin dihapus', + 'admin.notifications.adminNtfyPanel.saved': 'Pengaturan Ntfy admin tersimpan', + 'admin.notifications.adminNtfyPanel.test': 'Kirim uji Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': 'Uji Ntfy berhasil dikirim', + 'admin.notifications.adminNtfyPanel.testFailed': 'Uji Ntfy gagal', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin Ntfy selalu berjalan jika topik dikonfigurasi', + 'admin.notifications.adminNotificationsHint': + 'Atur saluran mana yang mengirimkan notifikasi khusus admin (mis. peringatan versi).', + 'admin.notifications.tripReminders.title': 'Pengingat Perjalanan', + 'admin.notifications.tripReminders.hint': + 'Mengirim notifikasi pengingat sebelum perjalanan dimulai (memerlukan hari pengingat yang diatur pada perjalanan).', + 'admin.notifications.tripReminders.enabled': + 'Pengingat perjalanan diaktifkan', + 'admin.notifications.tripReminders.disabled': + 'Pengingat perjalanan dinonaktifkan', + 'admin.smtp.title': 'Email & Notifikasi', + 'admin.smtp.hint': 'Konfigurasi SMTP untuk pengiriman notifikasi email.', + 'admin.smtp.testButton': 'Kirim email uji', + 'admin.webhook.hint': + 'Izinkan pengguna mengatur URL webhook sendiri untuk notifikasi (Discord, Slack, dll.).', + 'admin.smtp.testSuccess': 'Email uji berhasil dikirim', + 'admin.smtp.testFailed': 'Email uji gagal', + 'admin.title': 'Administrasi', + 'admin.subtitle': 'Manajemen pengguna dan pengaturan sistem', + 'admin.tabs.users': 'Pengguna', + 'admin.tabs.categories': 'Kategori', + 'admin.tabs.backup': 'Backup', + 'admin.tabs.notifications': 'Notifikasi', + 'admin.tabs.audit': 'Audit', + 'admin.stats.users': 'Pengguna', + 'admin.stats.trips': 'Perjalanan', + 'admin.stats.places': 'Tempat', + 'admin.stats.photos': 'Foto', + 'admin.stats.files': 'File', + 'admin.table.user': 'Pengguna', + 'admin.table.email': 'Email', + 'admin.table.role': 'Peran', + 'admin.table.created': 'Dibuat', + 'admin.table.lastLogin': 'Login Terakhir', + 'admin.table.actions': 'Tindakan', + 'admin.you': '(Kamu)', + 'admin.editUser': 'Edit Pengguna', + 'admin.newPassword': 'Kata Sandi Baru', + 'admin.newPasswordHint': 'Kosongkan untuk mempertahankan kata sandi saat ini', + 'admin.deleteUser': + 'Hapus pengguna "{name}"? Semua perjalanan akan dihapus secara permanen.', + 'admin.deleteUserTitle': 'Hapus pengguna', + 'admin.newPasswordPlaceholder': 'Masukkan kata sandi baru…', + 'admin.toast.loadError': 'Gagal memuat data admin', + 'admin.toast.userUpdated': 'Pengguna diperbarui', + 'admin.toast.updateError': 'Gagal memperbarui', + 'admin.toast.userDeleted': 'Pengguna dihapus', + 'admin.toast.deleteError': 'Gagal menghapus', + 'admin.toast.cannotDeleteSelf': 'Tidak bisa menghapus akun sendiri', + 'admin.toast.userCreated': 'Pengguna dibuat', + 'admin.toast.createError': 'Gagal membuat pengguna', + 'admin.toast.fieldsRequired': + 'Nama pengguna, email, dan kata sandi wajib diisi', + 'admin.createUser': 'Buat Pengguna', + 'admin.invite.title': 'Tautan Undangan', + 'admin.invite.subtitle': 'Buat tautan pendaftaran sekali pakai', + 'admin.invite.create': 'Buat Tautan', + 'admin.invite.createAndCopy': 'Buat & Salin', + 'admin.invite.empty': 'Belum ada tautan undangan yang dibuat', + 'admin.invite.maxUses': 'Maks. Penggunaan', + 'admin.invite.expiry': 'Kedaluwarsa setelah', + 'admin.invite.uses': 'digunakan', + 'admin.invite.expiresAt': 'kedaluwarsa', + 'admin.invite.createdBy': 'oleh', + 'admin.invite.active': 'Aktif', + 'admin.invite.expired': 'Kedaluwarsa', + 'admin.invite.usedUp': 'Habis dipakai', + 'admin.invite.copied': 'Tautan undangan disalin ke clipboard', + 'admin.invite.copyLink': 'Salin tautan', + 'admin.invite.deleted': 'Tautan undangan dihapus', + 'admin.invite.createError': 'Gagal membuat tautan undangan', + 'admin.invite.deleteError': 'Gagal menghapus tautan undangan', + 'admin.tabs.settings': 'Pengaturan', + 'admin.allowRegistration': 'Izinkan Pendaftaran', + 'admin.allowRegistrationHint': 'Pengguna baru dapat mendaftar sendiri', + 'admin.authMethods': 'Metode Autentikasi', + 'admin.passwordLogin': 'Login dengan Kata Sandi', + 'admin.passwordLoginHint': + 'Izinkan pengguna masuk dengan email dan kata sandi', + 'admin.passwordRegistration': 'Pendaftaran dengan Kata Sandi', + 'admin.passwordRegistrationHint': + 'Izinkan pengguna baru mendaftar dengan email dan kata sandi', + 'admin.oidcLogin': 'Login SSO', + 'admin.oidcLoginHint': 'Izinkan pengguna masuk dengan SSO', + 'admin.oidcRegistration': 'Penyediaan Otomatis SSO', + 'admin.oidcRegistrationHint': 'Buat akun otomatis untuk pengguna SSO baru', + 'admin.envOverrideHint': + 'Pengaturan login kata sandi dikendalikan oleh variabel lingkungan OIDC_ONLY dan tidak dapat diubah di sini.', + 'admin.lockoutWarning': 'Minimal satu metode login harus tetap aktif', + 'admin.requireMfa': 'Wajibkan autentikasi dua faktor (2FA)', + 'admin.requireMfaHint': + 'Pengguna tanpa 2FA harus menyelesaikan pengaturan di Pengaturan sebelum menggunakan aplikasi.', + 'admin.apiKeys': 'Kunci API', + 'admin.apiKeysHint': + 'Opsional. Mengaktifkan data tempat yang lebih lengkap seperti foto dan cuaca.', + 'admin.mapsKey': 'Kunci API Google Maps', + 'admin.mapsKeyHint': + 'Diperlukan untuk pencarian tempat. Dapatkan di console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Tanpa kunci API, OpenStreetMap digunakan untuk pencarian tempat. Dengan kunci API Google, foto, rating, dan jam buka juga bisa dimuat. Dapatkan di console.cloud.google.com.', + 'admin.recommended': 'Direkomendasikan', + 'admin.weatherKey': 'Kunci API OpenWeatherMap', + 'admin.weatherKeyHint': 'Untuk data cuaca. Gratis di openweathermap.org', + 'admin.validateKey': 'Uji', + 'admin.keyValid': 'Terhubung', + 'admin.keyInvalid': 'Tidak valid', + 'admin.keySaved': 'Kunci API disimpan', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Izinkan login melalui penyedia eksternal seperti Google, Apple, Authentik, atau Keycloak.', + 'admin.oidcDisplayName': 'Nama Tampilan', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'Issuer URL OpenID Connect dari penyedia. Contoh: https://accounts.google.com', + 'admin.oidcSaved': 'Konfigurasi OIDC disimpan', + 'admin.oidcOnlyMode': 'Nonaktifkan autentikasi kata sandi', + 'admin.oidcOnlyModeHint': + 'Jika diaktifkan, hanya login SSO yang diizinkan. Login dan pendaftaran berbasis kata sandi diblokir.', + 'admin.fileTypes': 'Jenis File yang Diizinkan', + 'admin.fileTypesHint': + 'Atur jenis file apa saja yang boleh diunggah pengguna.', + 'admin.fileTypesFormat': + 'Ekstensi dipisahkan koma (contoh: jpg,png,pdf,doc). Gunakan * untuk mengizinkan semua jenis.', + 'admin.fileTypesSaved': 'Pengaturan jenis file disimpan', + 'admin.placesPhotos.title': 'Foto Tempat', + 'admin.placesPhotos.subtitle': + 'Mengambil foto dari Google Places API. Nonaktifkan untuk menghemat kuota API. Foto Wikimedia tidak terpengaruh.', + 'admin.placesAutocomplete.title': 'Pelengkapan Otomatis Tempat', + 'admin.placesAutocomplete.subtitle': + 'Menggunakan Google Places API untuk saran pencarian. Nonaktifkan untuk menghemat kuota API.', + 'admin.placesDetails.title': 'Detail Tempat', + 'admin.placesDetails.subtitle': + 'Mengambil informasi detail tempat (jam, penilaian, situs web) dari Google Places API. Nonaktifkan untuk menghemat kuota API.', + 'admin.bagTracking.title': 'Pelacak Tas', + 'admin.bagTracking.subtitle': + 'Aktifkan berat dan penugasan tas untuk item packing', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Pesan real-time untuk kolaborasi', + 'admin.collab.notes.title': 'Catatan', + 'admin.collab.notes.subtitle': 'Catatan dan dokumen bersama', + 'admin.collab.polls.title': 'Jajak Pendapat', + 'admin.collab.polls.subtitle': 'Jajak pendapat dan voting grup', + 'admin.collab.whatsnext.title': 'Selanjutnya', + 'admin.collab.whatsnext.subtitle': 'Saran aktivitas dan langkah selanjutnya', + 'admin.tabs.config': 'Personalisasi', + 'admin.tabs.defaults': 'Pengaturan Default Pengguna', + 'admin.defaultSettings.title': 'Pengaturan Default Pengguna', + 'admin.defaultSettings.description': + 'Tetapkan nilai default untuk seluruh instance. Pengguna yang belum mengubah pengaturan akan melihat nilai-nilai ini. Perubahan mereka sendiri selalu diprioritaskan.', + 'admin.defaultSettings.saved': 'Default disimpan', + 'admin.defaultSettings.reset': 'Atur ulang ke default bawaan', + 'admin.defaultSettings.resetToBuiltIn': 'atur ulang', + 'admin.tabs.templates': 'Template Packing', + 'admin.packingTemplates.title': 'Template Packing', + 'admin.packingTemplates.subtitle': + 'Buat daftar packing yang bisa digunakan ulang untuk perjalananmu', + 'admin.packingTemplates.create': 'Template Baru', + 'admin.packingTemplates.namePlaceholder': + 'Nama template (contoh: Liburan Pantai)', + 'admin.packingTemplates.empty': 'Belum ada template yang dibuat', + 'admin.packingTemplates.items': 'item', + 'admin.packingTemplates.categories': 'kategori', + 'admin.packingTemplates.itemName': 'Nama item', + 'admin.packingTemplates.itemCategory': 'Kategori', + 'admin.packingTemplates.categoryName': 'Nama kategori (contoh: Pakaian)', + 'admin.packingTemplates.addCategory': 'Tambah kategori', + 'admin.packingTemplates.created': 'Template dibuat', + 'admin.packingTemplates.deleted': 'Template dihapus', + 'admin.packingTemplates.loadError': 'Gagal memuat template', + 'admin.packingTemplates.createError': 'Gagal membuat template', + 'admin.packingTemplates.deleteError': 'Gagal menghapus template', + 'admin.packingTemplates.saveError': 'Gagal menyimpan', + 'admin.tabs.addons': 'Addon', + 'admin.addons.title': 'Addon', + 'admin.addons.subtitle': + 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan pengalaman TREK kamu.', + 'admin.addons.catalog.packing.name': 'Daftar', + 'admin.addons.catalog.packing.description': + 'Daftar packing dan tugas to-do untuk perjalananmu', + 'admin.addons.catalog.budget.name': 'Anggaran', + 'admin.addons.catalog.budget.description': + 'Lacak pengeluaran dan rencanakan anggaran perjalananmu', + 'admin.addons.catalog.documents.name': 'Dokumen', + 'admin.addons.catalog.documents.description': + 'Simpan dan kelola dokumen perjalanan', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Perencana liburan pribadi dengan tampilan kalender', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Peta dunia dengan negara yang pernah dikunjungi dan statistik perjalanan', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Catatan real-time, polling, dan chat untuk perencanaan perjalanan', + 'admin.addons.catalog.memories.name': 'Foto (Immich)', + 'admin.addons.catalog.memories.description': + 'Bagikan foto perjalanan melalui instans Immich kamu', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol untuk integrasi asisten AI', + 'admin.addons.subtitleBefore': + 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan ', + 'admin.addons.subtitleAfter': ' kamu.', + 'admin.addons.enabled': 'Aktif', + 'admin.addons.disabled': 'Nonaktif', + 'admin.addons.type.trip': 'Perjalanan', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integrasi', + 'admin.addons.tripHint': 'Tersedia sebagai tab di setiap perjalanan', + 'admin.addons.globalHint': + 'Tersedia sebagai bagian mandiri di navigasi utama', + 'admin.addons.integrationHint': + 'Layanan backend dan integrasi API tanpa halaman tersendiri', + 'admin.addons.toast.updated': 'Addon diperbarui', + 'admin.addons.toast.error': 'Gagal memperbarui addon', + 'admin.addons.noAddons': 'Tidak ada addon yang tersedia', + 'admin.weather.title': 'Data Cuaca', + 'admin.weather.badge': 'Sejak 24 Maret 2026', + 'admin.weather.description': + 'TREK menggunakan Open-Meteo sebagai sumber data cuaca. Open-Meteo adalah layanan cuaca gratis dan open-source — tidak perlu kunci API.', + 'admin.weather.forecast': 'Prakiraan 16 hari', + 'admin.weather.forecastDesc': 'Sebelumnya 5 hari (OpenWeatherMap)', + 'admin.weather.climate': 'Data iklim historis', + 'admin.weather.climateDesc': + 'Rata-rata dari 85 tahun terakhir untuk hari di luar prakiraan 16 hari', + 'admin.weather.requests': '10.000 permintaan / hari', + 'admin.weather.requestsDesc': 'Gratis, tidak perlu kunci API', + 'admin.weather.locationHint': + 'Cuaca didasarkan pada tempat pertama dengan koordinat di setiap hari. Jika tidak ada tempat yang ditetapkan untuk suatu hari, tempat mana pun dari daftar tempat digunakan sebagai referensi.', + 'admin.tabs.mcpTokens': 'Akses MCP', + 'admin.mcpTokens.title': 'Akses MCP', + 'admin.mcpTokens.subtitle': + 'Kelola sesi OAuth dan token API di semua pengguna', + 'admin.mcpTokens.sectionTitle': 'Token API', + 'admin.mcpTokens.owner': 'Pemilik', + 'admin.mcpTokens.tokenName': 'Nama Token', + 'admin.mcpTokens.created': 'Dibuat', + 'admin.mcpTokens.lastUsed': 'Terakhir Digunakan', + 'admin.mcpTokens.never': 'Tidak pernah', + 'admin.mcpTokens.empty': 'Belum ada token MCP yang dibuat', + 'admin.mcpTokens.deleteTitle': 'Hapus Token', + 'admin.mcpTokens.deleteMessage': + 'Ini akan mencabut token segera. Pengguna akan kehilangan akses MCP melalui token ini.', + 'admin.mcpTokens.deleteSuccess': 'Token dihapus', + 'admin.mcpTokens.deleteError': 'Gagal menghapus token', + 'admin.mcpTokens.loadError': 'Gagal memuat token', + 'admin.oauthSessions.sectionTitle': 'Sesi OAuth', + 'admin.oauthSessions.clientName': 'Klien', + 'admin.oauthSessions.owner': 'Pemilik', + 'admin.oauthSessions.scopes': 'Cakupan', + 'admin.oauthSessions.created': 'Dibuat', + 'admin.oauthSessions.empty': 'Tidak ada sesi OAuth aktif', + 'admin.oauthSessions.revokeTitle': 'Cabut Sesi', + 'admin.oauthSessions.revokeMessage': + 'Ini akan segera mencabut sesi OAuth. Client akan kehilangan akses MCP.', + 'admin.oauthSessions.revokeSuccess': 'Sesi dicabut', + 'admin.oauthSessions.revokeError': 'Gagal mencabut sesi', + 'admin.oauthSessions.loadError': 'Gagal memuat sesi OAuth', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Peristiwa sensitif keamanan dan administrasi (backup, pengguna, MFA, pengaturan).', + 'admin.audit.empty': 'Belum ada entri audit.', + 'admin.audit.refresh': 'Segarkan', + 'admin.audit.loadMore': 'Muat lebih banyak', + 'admin.audit.showing': '{count} dimuat · {total} total', + 'admin.audit.col.time': 'Waktu', + 'admin.audit.col.user': 'Pengguna', + 'admin.audit.col.action': 'Tindakan', + 'admin.audit.col.resource': 'Sumber Daya', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Detail', + 'admin.github.title': 'Riwayat Rilis', + 'admin.github.subtitle': 'Pembaruan terbaru dari {repo}', + 'admin.github.latest': 'Terbaru', + 'admin.github.prerelease': 'Pra-rilis', + 'admin.github.showDetails': 'Tampilkan detail', + 'admin.github.hideDetails': 'Sembunyikan detail', + 'admin.github.loadMore': 'Muat lebih banyak', + 'admin.github.loading': 'Memuat...', + 'admin.github.error': 'Gagal memuat rilis', + 'admin.github.by': 'oleh', + 'admin.github.support': 'Bantu saya terus mengembangkan TREK', + 'admin.update.available': 'Pembaruan tersedia', + 'admin.update.text': 'TREK {version} tersedia. Kamu menggunakan {current}.', + 'admin.update.button': 'Lihat di GitHub', + 'admin.update.install': 'Pasang Pembaruan', + 'admin.update.confirmTitle': 'Pasang Pembaruan?', + 'admin.update.confirmText': + 'TREK akan diperbarui dari {current} ke {version}. Server akan restart otomatis setelahnya.', + 'admin.update.dataInfo': + 'Semua datamu (perjalanan, pengguna, kunci API, unggahan, Vacay, Atlas, anggaran) akan dipertahankan.', + 'admin.update.warning': + 'Aplikasi akan tidak tersedia sebentar selama restart.', + 'admin.update.confirm': 'Perbarui Sekarang', + 'admin.update.installing': 'Memperbarui…', + 'admin.update.success': 'Pembaruan terpasang! Server sedang restart…', + 'admin.update.failed': 'Pembaruan gagal', + 'admin.update.backupHint': + 'Kami merekomendasikan membuat backup sebelum memperbarui.', + 'admin.update.backupLink': 'Pergi ke Backup', + 'admin.update.howTo': 'Cara Memperbarui', + 'admin.update.dockerText': + 'Instans TREK kamu berjalan di Docker. Untuk memperbarui ke {version}, jalankan perintah berikut di servermu:', + 'admin.update.reloadHint': 'Muat ulang halaman dalam beberapa detik.', + 'admin.tabs.permissions': 'Izin', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + 'Pelacakan perjalanan & jurnal dengan check-in, foto, dan cerita harian', +}; +export default admin; diff --git a/shared/src/i18n/id/airport.ts b/shared/src/i18n/id/airport.ts new file mode 100644 index 00000000..cd206806 --- /dev/null +++ b/shared/src/i18n/id/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Kode bandara atau kota (mis. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/id/atlas.ts b/shared/src/i18n/id/atlas.ts new file mode 100644 index 00000000..f7f9c323 --- /dev/null +++ b/shared/src/i18n/id/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Jejak perjalananmu di seluruh dunia', + 'atlas.countries': 'Negara', + 'atlas.trips': 'Perjalanan', + 'atlas.places': 'Tempat', + 'atlas.unmark': 'Hapus', + 'atlas.confirmMark': 'Tandai negara ini sebagai sudah dikunjungi?', + 'atlas.confirmUnmark': 'Hapus negara ini dari daftar kunjunganmu?', + 'atlas.confirmUnmarkRegion': 'Hapus wilayah ini dari daftar kunjunganmu?', + 'atlas.markVisited': 'Tandai sudah dikunjungi', + 'atlas.markVisitedHint': 'Tambahkan negara ini ke daftar kunjunganmu', + 'atlas.markRegionVisitedHint': 'Tambahkan wilayah ini ke daftar kunjunganmu', + 'atlas.addToBucket': 'Tambah ke bucket list', + 'atlas.addPoi': 'Tambah tempat', + 'atlas.searchCountry': 'Cari negara...', + 'atlas.bucketNamePlaceholder': 'Nama (negara, kota, tempat...)', + 'atlas.month': 'Bulan', + 'atlas.year': 'Tahun', + 'atlas.addToBucketHint': 'Simpan sebagai tempat yang ingin dikunjungi', + 'atlas.bucketWhen': 'Kapan kamu berencana mengunjunginya?', + 'atlas.statsTab': 'Statistik', + 'atlas.bucketTab': 'Daftar Impian', + 'atlas.addBucket': 'Tambah ke daftar impian', + 'atlas.bucketNotesPlaceholder': 'Catatan (opsional)', + 'atlas.bucketEmpty': 'Daftar impianmu masih kosong', + 'atlas.bucketEmptyHint': 'Tambahkan tempat-tempat yang ingin kamu kunjungi', + 'atlas.days': 'Hari', + 'atlas.visitedCountries': 'Negara yang Dikunjungi', + 'atlas.cities': 'Kota', + 'atlas.noData': 'Belum ada data perjalanan', + 'atlas.noDataHint': + 'Buat perjalanan dan tambahkan tempat untuk melihat peta duniamu', + 'atlas.lastTrip': 'Perjalanan terakhir', + 'atlas.nextTrip': 'Perjalanan berikutnya', + 'atlas.daysLeft': 'hari lagi', + 'atlas.streak': 'Rentetan', + 'atlas.years': 'tahun', + 'atlas.yearInRow': 'tahun berturut-turut', + 'atlas.yearsInRow': 'tahun berturut-turut', + 'atlas.tripIn': 'perjalanan ke', + 'atlas.tripsIn': 'perjalanan ke', + 'atlas.since': 'sejak', + 'atlas.europe': 'Eropa', + 'atlas.asia': 'Asia', + 'atlas.northAmerica': 'Amerika Utara', + 'atlas.southAmerica': 'Amerika Selatan', + 'atlas.africa': 'Afrika', + 'atlas.oceania': 'Oseania', + 'atlas.other': 'Lainnya', + 'atlas.firstVisit': 'Perjalanan pertama', + 'atlas.lastVisitLabel': 'Perjalanan terakhir', + 'atlas.tripSingular': 'Perjalanan', + 'atlas.tripPlural': 'Perjalanan', + 'atlas.placeVisited': 'Tempat dikunjungi', + 'atlas.placesVisited': 'Tempat dikunjungi', +}; +export default atlas; diff --git a/shared/src/i18n/id/backup.ts b/shared/src/i18n/id/backup.ts new file mode 100644 index 00000000..e942c459 --- /dev/null +++ b/shared/src/i18n/id/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Pencadangan Data', + 'backup.subtitle': 'Database dan semua file yang diunggah', + 'backup.refresh': 'Segarkan', + 'backup.upload': 'Unggah Cadangan', + 'backup.uploading': 'Mengunggah…', + 'backup.create': 'Buat Cadangan', + 'backup.creating': 'Membuat…', + 'backup.empty': 'Belum ada cadangan', + 'backup.createFirst': 'Buat cadangan pertama', + 'backup.download': 'Unduh', + 'backup.restore': 'Pulihkan', + 'backup.confirm.restore': + 'Pulihkan cadangan "{name}"?\n\nSemua data saat ini akan digantikan dengan cadangan.', + 'backup.confirm.uploadRestore': + 'Unggah dan pulihkan file cadangan "{name}"?\n\nSemua data saat ini akan ditimpa.', + 'backup.confirm.delete': 'Hapus cadangan "{name}"?', + 'backup.toast.loadError': 'Gagal memuat cadangan', + 'backup.toast.created': 'Cadangan berhasil dibuat', + 'backup.toast.createError': 'Gagal membuat cadangan', + 'backup.toast.restored': 'Cadangan dipulihkan. Halaman akan dimuat ulang…', + 'backup.toast.restoreError': 'Gagal memulihkan', + 'backup.toast.uploadError': 'Gagal mengunggah', + 'backup.toast.deleted': 'Cadangan dihapus', + 'backup.toast.deleteError': 'Gagal menghapus', + 'backup.toast.downloadError': 'Unduhan gagal', + 'backup.toast.settingsSaved': 'Pengaturan pencadangan otomatis disimpan', + 'backup.toast.settingsError': 'Gagal menyimpan pengaturan', + 'backup.auto.title': 'Cadangan Otomatis', + 'backup.auto.subtitle': 'Pencadangan otomatis sesuai jadwal', + 'backup.auto.enable': 'Aktifkan cadangan otomatis', + 'backup.auto.enableHint': + 'Cadangan akan dibuat secara otomatis sesuai jadwal yang dipilih', + 'backup.auto.interval': 'Interval', + 'backup.auto.hour': 'Jalankan pada jam', + 'backup.auto.hourHint': 'Waktu lokal server (format {format})', + 'backup.auto.dayOfWeek': 'Hari dalam seminggu', + 'backup.auto.dayOfMonth': 'Tanggal dalam sebulan', + 'backup.auto.dayOfMonthHint': + 'Dibatasi 1–28 agar kompatibel dengan semua bulan', + 'backup.auto.scheduleSummary': 'Jadwal', + 'backup.auto.summaryDaily': 'Setiap hari pukul {hour}:00', + 'backup.auto.summaryWeekly': 'Setiap {day} pukul {hour}:00', + 'backup.auto.summaryMonthly': 'Tanggal {day} setiap bulan pukul {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Cadangan otomatis dikonfigurasi melalui variabel lingkungan Docker. Untuk mengubah pengaturan ini, perbarui docker-compose.yml kamu dan restart container.', + 'backup.auto.copyEnv': 'Salin variabel env Docker', + 'backup.auto.envCopied': 'Variabel env Docker disalin ke clipboard', + 'backup.auto.keepLabel': 'Hapus cadangan lama setelah', + 'backup.dow.sunday': 'Min', + 'backup.dow.monday': 'Sen', + 'backup.dow.tuesday': 'Sel', + 'backup.dow.wednesday': 'Rab', + 'backup.dow.thursday': 'Kam', + 'backup.dow.friday': 'Jum', + 'backup.dow.saturday': 'Sab', + 'backup.interval.hourly': 'Per jam', + 'backup.interval.daily': 'Harian', + 'backup.interval.weekly': 'Mingguan', + 'backup.interval.monthly': 'Bulanan', + 'backup.keep.1day': '1 hari', + 'backup.keep.3days': '3 hari', + 'backup.keep.7days': '7 hari', + 'backup.keep.14days': '14 hari', + 'backup.keep.30days': '30 hari', + 'backup.keep.forever': 'Simpan selamanya', + 'backup.restoreConfirmTitle': 'Pulihkan Cadangan?', + 'backup.restoreWarning': + 'Semua data saat ini (perjalanan, tempat, pengguna, unggahan) akan digantikan secara permanen oleh cadangan ini. Tindakan ini tidak dapat dibatalkan.', + 'backup.restoreTip': + 'Tips: Buat cadangan kondisi saat ini sebelum memulihkan.', + 'backup.restoreConfirm': 'Ya, pulihkan', +}; +export default backup; diff --git a/shared/src/i18n/id/budget.ts b/shared/src/i18n/id/budget.ts new file mode 100644 index 00000000..642f91ec --- /dev/null +++ b/shared/src/i18n/id/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Anggaran', + 'budget.exportCsv': 'Ekspor CSV', + 'budget.emptyTitle': 'Belum ada anggaran', + 'budget.emptyText': + 'Buat kategori dan entri untuk merencanakan anggaran perjalananmu', + 'budget.emptyPlaceholder': 'Masukkan nama kategori...', + 'budget.createCategory': 'Buat Kategori', + 'budget.category': 'Kategori', + 'budget.categoryName': 'Nama Kategori', + 'budget.table.name': 'Nama', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Orang', + 'budget.table.days': 'Hari', + 'budget.table.perPerson': 'Per Orang', + 'budget.table.perDay': 'Per Hari', + 'budget.table.perPersonDay': 'P. o / Hari', + 'budget.table.note': 'Catatan', + 'budget.table.date': 'Tanggal', + 'budget.newEntry': 'Entri Baru', + 'budget.defaultEntry': 'Entri Baru', + 'budget.defaultCategory': 'Kategori Baru', + 'budget.total': 'Total', + 'budget.totalBudget': 'Total Anggaran', + 'budget.byCategory': 'Per Kategori', + 'budget.editTooltip': 'Klik untuk edit', + 'budget.linkedToReservation': 'Terhubung ke reservasi — edit nama di sana', + 'budget.confirm.deleteCategory': + 'Yakin ingin menghapus kategori "{name}" dengan {count} entri?', + 'budget.deleteCategory': 'Hapus Kategori', + 'budget.perPerson': 'Per Orang', + 'budget.paid': 'Sudah dibayar', + 'budget.open': 'Belum dibayar', + 'budget.noMembers': 'Tidak ada anggota yang ditugaskan', + 'budget.settlement': 'Penyelesaian', + 'budget.settlementInfo': + 'Klik foto anggota di item anggaran untuk menandainya hijau — artinya mereka sudah bayar. Penyelesaian lalu menunjukkan siapa berhutang ke siapa dan berapa.', + 'budget.netBalances': 'Saldo Bersih', + 'budget.categoriesLabel': 'kategori', +}; +export default budget; diff --git a/shared/src/i18n/id/categories.ts b/shared/src/i18n/id/categories.ts new file mode 100644 index 00000000..9d08b328 --- /dev/null +++ b/shared/src/i18n/id/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Kategori', + 'categories.subtitle': 'Kelola kategori untuk tempat', + 'categories.new': 'Kategori Baru', + 'categories.empty': 'Belum ada kategori', + 'categories.namePlaceholder': 'Nama kategori', + 'categories.icon': 'Ikon', + 'categories.color': 'Warna', + 'categories.customColor': 'Pilih warna kustom', + 'categories.preview': 'Pratinjau', + 'categories.defaultName': 'Kategori', + 'categories.update': 'Perbarui', + 'categories.create': 'Buat', + 'categories.confirm.delete': + 'Hapus kategori? Tempat dalam kategori ini tidak akan dihapus.', + 'categories.toast.loadError': 'Gagal memuat kategori', + 'categories.toast.nameRequired': 'Harap masukkan nama', + 'categories.toast.updated': 'Kategori diperbarui', + 'categories.toast.created': 'Kategori dibuat', + 'categories.toast.saveError': 'Gagal menyimpan', + 'categories.toast.deleted': 'Kategori dihapus', + 'categories.toast.deleteError': 'Gagal menghapus', +}; +export default categories; diff --git a/shared/src/i18n/id/collab.ts b/shared/src/i18n/id/collab.ts new file mode 100644 index 00000000..04596587 --- /dev/null +++ b/shared/src/i18n/id/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Catatan', + 'collab.tabs.polls': 'Polling', + 'collab.whatsNext.title': 'Berikutnya', + 'collab.whatsNext.today': 'Hari ini', + 'collab.whatsNext.tomorrow': 'Besok', + 'collab.whatsNext.empty': 'Tidak ada aktivitas mendatang', + 'collab.whatsNext.until': 'sampai', + 'collab.whatsNext.emptyHint': 'Aktivitas dengan waktu akan muncul di sini', + 'collab.chat.send': 'Kirim', + 'collab.chat.placeholder': 'Ketik pesan...', + 'collab.chat.empty': 'Mulai percakapan', + 'collab.chat.emptyHint': 'Pesan dibagikan kepada semua anggota perjalanan', + 'collab.chat.emptyDesc': + 'Bagikan ide, rencana, dan update dengan grup perjalananmu', + 'collab.chat.today': 'Hari ini', + 'collab.chat.yesterday': 'Kemarin', + 'collab.chat.deletedMessage': 'menghapus pesan', + 'collab.chat.reply': 'Balas', + 'collab.chat.loadMore': 'Muat pesan lebih lama', + 'collab.chat.justNow': 'baru saja', + 'collab.chat.minutesAgo': '{n}m lalu', + 'collab.chat.hoursAgo': '{n}j lalu', + 'collab.notes.title': 'Catatan', + 'collab.notes.new': 'Catatan Baru', + 'collab.notes.empty': 'Belum ada catatan', + 'collab.notes.emptyHint': 'Mulai catat ide dan rencana', + 'collab.notes.all': 'Semua', + 'collab.notes.titlePlaceholder': 'Judul catatan', + 'collab.notes.contentPlaceholder': 'Tulis sesuatu...', + 'collab.notes.categoryPlaceholder': 'Kategori', + 'collab.notes.newCategory': 'Kategori baru...', + 'collab.notes.category': 'Kategori', + 'collab.notes.noCategory': 'Tanpa kategori', + 'collab.notes.color': 'Warna', + 'collab.notes.save': 'Simpan', + 'collab.notes.cancel': 'Batal', + 'collab.notes.edit': 'Edit', + 'collab.notes.delete': 'Hapus', + 'collab.notes.pin': 'Sematkan', + 'collab.notes.unpin': 'Lepas sematan', + 'collab.notes.daysAgo': '{n}h lalu', + 'collab.notes.categorySettings': 'Kelola Kategori', + 'collab.notes.create': 'Buat', + 'collab.notes.website': 'Website', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Lampirkan file', + 'collab.notes.noCategoriesYet': 'Belum ada kategori', + 'collab.notes.emptyDesc': 'Buat catatan untuk memulai', + 'collab.polls.title': 'Polling', + 'collab.polls.new': 'Polling Baru', + 'collab.polls.empty': 'Belum ada polling', + 'collab.polls.emptyHint': 'Tanyakan ke grup dan voting bersama', + 'collab.polls.question': 'Pertanyaan', + 'collab.polls.questionPlaceholder': 'Apa yang harus kita lakukan?', + 'collab.polls.addOption': '+ Tambah pilihan', + 'collab.polls.optionPlaceholder': 'Pilihan {n}', + 'collab.polls.create': 'Buat Polling', + 'collab.polls.close': 'Tutup', + 'collab.polls.closed': 'Ditutup', + 'collab.polls.votes': '{n} suara', + 'collab.polls.vote': '{n} suara', + 'collab.polls.multipleChoice': 'Pilihan ganda', + 'collab.polls.multiChoice': 'Pilihan ganda', + 'collab.polls.deadline': 'Tenggat waktu', + 'collab.polls.option': 'Pilihan', + 'collab.polls.options': 'Pilihan', + 'collab.polls.delete': 'Hapus', + 'collab.polls.closedSection': 'Ditutup', +}; +export default collab; diff --git a/shared/src/i18n/id/common.ts b/shared/src/i18n/id/common.ts new file mode 100644 index 00000000..d21f4084 --- /dev/null +++ b/shared/src/i18n/id/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Simpan', + 'common.showMore': 'Tampilkan lebih banyak', + 'common.showLess': 'Tampilkan lebih sedikit', + 'common.cancel': 'Batal', + 'common.clear': 'Hapus', + 'common.delete': 'Hapus', + 'common.edit': 'Sunting', + 'common.add': 'Tambah', + 'common.loading': 'Memuat...', + 'common.import': 'Impor', + 'common.select': 'Pilih', + 'common.selectAll': 'Pilih semua', + 'common.deselectAll': 'Batalkan semua pilihan', + 'common.error': 'Kesalahan', + 'common.unknownError': 'Kesalahan tidak diketahui', + 'common.tooManyAttempts': 'Terlalu banyak percobaan. Coba lagi nanti.', + 'common.back': 'Kembali', + 'common.all': 'Semua', + 'common.close': 'Tutup', + 'common.open': 'Buka', + 'common.upload': 'Unggah', + 'common.search': 'Cari', + 'common.confirm': 'Konfirmasi', + 'common.ok': 'OK', + 'common.yes': 'Ya', + 'common.no': 'Tidak', + 'common.or': 'atau', + 'common.none': 'Tidak ada', + 'common.date': 'Tanggal', + 'common.rename': 'Ganti nama', + 'common.discardChanges': 'Buang perubahan', + 'common.discard': 'Buang', + 'common.name': 'Nama', + 'common.email': 'Email', + 'common.password': 'Kata sandi', + 'common.saving': 'Menyimpan...', + 'common.justNow': 'baru saja', + 'common.hoursAgo': '{count}j lalu', + 'common.daysAgo': '{count}h lalu', + 'common.saved': 'Tersimpan', + 'common.update': 'Perbarui', + 'common.change': 'Ubah', + 'common.uploading': 'Mengunggah…', + 'common.backToPlanning': 'Kembali ke Perencanaan', + 'common.reset': 'Atur ulang', + 'common.expand': 'Perluas', + 'common.collapse': 'Ciutkan', + 'common.copy': 'Salin', + 'common.copied': 'Disalin', +}; +export default common; diff --git a/shared/src/i18n/id/dashboard.ts b/shared/src/i18n/id/dashboard.ts new file mode 100644 index 00000000..93aac3f2 --- /dev/null +++ b/shared/src/i18n/id/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Perjalananku', + 'dashboard.subtitle.loading': 'Memuat perjalanan...', + 'dashboard.subtitle.trips': '{count} perjalanan ({archived} diarsipkan)', + 'dashboard.subtitle.empty': 'Mulai perjalanan pertamamu', + 'dashboard.subtitle.activeOne': '{count} perjalanan aktif', + 'dashboard.subtitle.activeMany': '{count} perjalanan aktif', + 'dashboard.subtitle.archivedSuffix': ' · {count} diarsipkan', + 'dashboard.newTrip': 'Perjalanan Baru', + 'dashboard.gridView': 'Tampilan grid', + 'dashboard.listView': 'Tampilan daftar', + 'dashboard.currency': 'Mata uang', + 'dashboard.timezone': 'Zona waktu', + 'dashboard.localTime': 'Lokal', + 'dashboard.timezoneCustomTitle': 'Zona Waktu Kustom', + 'dashboard.timezoneCustomLabelPlaceholder': 'Label (opsional)', + 'dashboard.timezoneCustomTzPlaceholder': 'mis. America/New_York', + 'dashboard.timezoneCustomAdd': 'Tambah', + 'dashboard.timezoneCustomErrorEmpty': 'Masukkan pengenal zona waktu', + 'dashboard.timezoneCustomErrorInvalid': + 'Zona waktu tidak valid. Gunakan format seperti Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Sudah ditambahkan', + 'dashboard.emptyTitle': 'Belum ada perjalanan', + 'dashboard.emptyText': 'Buat perjalanan pertamamu dan mulai merencanakan!', + 'dashboard.emptyButton': 'Buat Perjalanan Pertama', + 'dashboard.nextTrip': 'Perjalanan Berikutnya', + 'dashboard.shared': 'Dibagikan', + 'dashboard.sharedBy': 'Dibagikan oleh {name}', + 'dashboard.days': 'Hari', + 'dashboard.places': 'Tempat', + 'dashboard.members': 'Teman perjalanan', + 'dashboard.archive': 'Arsipkan', + 'dashboard.copyTrip': 'Salin', + 'dashboard.copySuffix': 'salinan', + 'dashboard.restore': 'Pulihkan', + 'dashboard.archived': 'Diarsipkan', + 'dashboard.status.ongoing': 'Sedang berlangsung', + 'dashboard.status.today': 'Hari ini', + 'dashboard.status.tomorrow': 'Besok', + 'dashboard.status.past': 'Sudah lewat', + 'dashboard.status.daysLeft': '{count} hari lagi', + 'dashboard.toast.loadError': 'Gagal memuat perjalanan', + 'dashboard.toast.created': 'Perjalanan berhasil dibuat!', + 'dashboard.toast.createError': 'Gagal membuat perjalanan', + 'dashboard.toast.updated': 'Perjalanan diperbarui!', + 'dashboard.toast.updateError': 'Gagal memperbarui perjalanan', + 'dashboard.toast.deleted': 'Perjalanan dihapus', + 'dashboard.toast.deleteError': 'Gagal menghapus perjalanan', + 'dashboard.toast.archived': 'Perjalanan diarsipkan', + 'dashboard.toast.archiveError': 'Gagal mengarsipkan perjalanan', + 'dashboard.toast.restored': 'Perjalanan dipulihkan', + 'dashboard.toast.restoreError': 'Gagal memulihkan perjalanan', + 'dashboard.toast.copied': 'Perjalanan disalin!', + 'dashboard.toast.copyError': 'Gagal menyalin perjalanan', + 'dashboard.confirm.delete': + 'Hapus perjalanan "{title}"? Semua tempat dan rencana akan dihapus permanen.', + 'dashboard.editTrip': 'Edit Perjalanan', + 'dashboard.createTrip': 'Buat Perjalanan Baru', + 'dashboard.tripTitle': 'Judul', + 'dashboard.tripTitlePlaceholder': 'mis. Musim Panas di Jepang', + 'dashboard.tripDescription': 'Deskripsi', + 'dashboard.tripDescriptionPlaceholder': 'Perjalanan ini tentang apa?', + 'dashboard.startDate': 'Tanggal Mulai', + 'dashboard.endDate': 'Tanggal Selesai', + 'dashboard.dayCount': 'Jumlah Hari', + 'dashboard.dayCountHint': + 'Berapa hari yang ingin direncanakan jika tanggal perjalanan belum diatur.', + 'dashboard.noDateHint': + 'Belum ada tanggal — 7 hari default akan dibuat. Bisa diubah kapan saja.', + 'dashboard.coverImage': 'Gambar Sampul', + 'dashboard.addCoverImage': 'Tambah gambar sampul (atau seret & lepas)', + 'dashboard.addMembers': 'Teman perjalanan', + 'dashboard.addMember': 'Tambah anggota', + 'dashboard.coverSaved': 'Gambar sampul tersimpan', + 'dashboard.coverUploadError': 'Gagal mengunggah', + 'dashboard.coverRemoveError': 'Gagal menghapus', + 'dashboard.titleRequired': 'Judul wajib diisi', + 'dashboard.endDateError': 'Tanggal selesai harus setelah tanggal mulai', + 'dashboard.greeting.morning': 'Selamat pagi,', + 'dashboard.greeting.afternoon': 'Selamat siang,', + 'dashboard.greeting.evening': 'Selamat malam,', + 'dashboard.mobile.liveNow': 'Sedang Berlangsung', + 'dashboard.mobile.tripProgress': 'Progres perjalanan', + 'dashboard.mobile.daysLeft': '{count} hari lagi', + 'dashboard.mobile.places': 'Tempat', + 'dashboard.mobile.buddies': 'Teman', + 'dashboard.mobile.newTrip': 'Perjalanan Baru', + 'dashboard.mobile.currency': 'Mata Uang', + 'dashboard.mobile.timezone': 'Zona Waktu', + 'dashboard.mobile.upcomingTrips': 'Perjalanan Mendatang', + 'dashboard.mobile.yourTrips': 'Perjalananmu', + 'dashboard.mobile.trips': 'perjalanan', + 'dashboard.mobile.starts': 'Mulai', + 'dashboard.mobile.duration': 'Durasi', + 'dashboard.mobile.day': 'hari', + 'dashboard.mobile.days': 'hari', + 'dashboard.mobile.ongoing': 'Sedang berlangsung', + 'dashboard.mobile.startsToday': 'Mulai hari ini', + 'dashboard.mobile.tomorrow': 'Besok', + 'dashboard.mobile.inDays': 'Dalam {count} hari', + 'dashboard.mobile.inMonths': 'Dalam {count} bulan', + 'dashboard.mobile.completed': 'Selesai', + 'dashboard.mobile.currencyConverter': 'Konverter Mata Uang', +}; +export default dashboard; diff --git a/shared/src/i18n/id/day.ts b/shared/src/i18n/id/day.ts new file mode 100644 index 00000000..03e994da --- /dev/null +++ b/shared/src/i18n/id/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Peluang hujan', + 'day.precipitation': 'Curah hujan', + 'day.wind': 'Angin', + 'day.sunrise': 'Matahari terbit', + 'day.sunset': 'Matahari terbenam', + 'day.hourlyForecast': 'Prakiraan Per Jam', + 'day.climateHint': + 'Rata-rata historis — prakiraan nyata tersedia dalam 16 hari dari tanggal ini.', + 'day.noWeather': + 'Data cuaca tidak tersedia. Tambahkan tempat dengan koordinat.', + 'day.overview': 'Ikhtisar Harian', + 'day.accommodation': 'Akomodasi', + 'day.addAccommodation': 'Tambah akomodasi', + 'day.hotelDayRange': 'Terapkan ke hari', + 'day.noPlacesForHotel': 'Tambahkan tempat ke perjalananmu terlebih dahulu', + 'day.allDays': 'Semua', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Sampai', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Konfirmasi', + 'day.editAccommodation': 'Edit akomodasi', + 'day.reservations': 'Reservasi', +}; +export default day; diff --git a/shared/src/i18n/id/dayplan.ts b/shared/src/i18n/id/dayplan.ts new file mode 100644 index 00000000..03d3bc75 --- /dev/null +++ b/shared/src/i18n/id/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Ekspor kalender (ICS)', + 'dayplan.emptyDay': 'Belum ada tempat yang direncanakan untuk hari ini', + 'dayplan.cannotReorderTransport': + 'Pemesanan dengan waktu tetap tidak bisa diurutkan ulang', + 'dayplan.confirmRemoveTimeTitle': 'Hapus waktu?', + 'dayplan.confirmRemoveTimeBody': + 'Tempat ini memiliki waktu tetap ({time}). Memindahkannya akan menghapus waktu dan mengizinkan pengurutan bebas.', + 'dayplan.confirmRemoveTimeAction': 'Hapus waktu & pindahkan', + 'dayplan.cannotDropOnTimed': + 'Item tidak dapat ditempatkan di antara entri yang terikat waktu', + 'dayplan.cannotBreakChronology': + 'Ini akan merusak urutan kronologis item bertanggal dan pemesanan', + 'dayplan.addNote': 'Tambah Catatan', + 'dayplan.editNote': 'Edit Catatan', + 'dayplan.noteAdd': 'Tambah Catatan', + 'dayplan.noteEdit': 'Edit Catatan', + 'dayplan.noteTitle': 'Catatan', + 'dayplan.noteSubtitle': 'Catatan Harian', + 'dayplan.totalCost': 'Total Biaya', + 'dayplan.days': 'Hari', + 'dayplan.dayN': 'Hari {n}', + 'dayplan.calculating': 'Menghitung...', + 'dayplan.route': 'Rute', + 'dayplan.optimize': 'Optimalkan', + 'dayplan.optimized': 'Rute dioptimalkan', + 'dayplan.routeError': 'Gagal menghitung rute', + 'dayplan.toast.needTwoPlaces': + 'Diperlukan minimal dua tempat untuk optimasi rute', + 'dayplan.toast.routeOptimized': 'Rute dioptimalkan', + 'dayplan.toast.noGeoPlaces': + 'Tidak ditemukan tempat dengan koordinat untuk kalkulasi rute', + 'dayplan.confirmed': 'Dikonfirmasi', + 'dayplan.pendingRes': 'Menunggu', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Ekspor rencana hari sebagai PDF', + 'dayplan.pdfError': 'Gagal mengekspor PDF', + 'dayplan.mobile.addPlace': 'Tambah Tempat', + 'dayplan.mobile.searchPlaces': 'Cari tempat...', + 'dayplan.mobile.allAssigned': 'Semua tempat sudah ditugaskan', + 'dayplan.mobile.noMatch': 'Tidak ditemukan', + 'dayplan.mobile.createNew': 'Buat tempat baru', +}; +export default dayplan; diff --git a/shared/src/i18n/id/externalNotifications.ts b/shared/src/i18n/id/externalNotifications.ts new file mode 100644 index 00000000..3ac14b94 --- /dev/null +++ b/shared/src/i18n/id/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const id: NotificationLocale = { + email: { + footer: + 'Anda menerima ini karena Anda telah mengaktifkan notifikasi di TREK.', + manage: 'Kelola preferensi di Pengaturan', + madeWith: 'Dibuat dengan', + openTrek: 'Buka TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Undangan perjalanan: "${p.trip}"`, + body: `${p.actor} mengundang ${p.invitee || 'seorang anggota'} ke perjalanan "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Pemesanan baru: ${p.booking}`, + body: `${p.actor} menambahkan "${p.booking}" (${p.type}) baru ke "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Pengingat perjalanan: ${p.trip}`, + body: `Perjalanan Anda "${p.trip}" akan segera tiba!`, + }), + todo_due: (p) => ({ + title: `Tugas jatuh tempo: ${p.todo}`, + body: `"${p.todo}" di "${p.trip}" jatuh tempo pada ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Undangan Penggabungan Vacay', + body: `${p.actor} mengundang Anda untuk menggabungkan rencana liburan. Buka TREK untuk menerima atau menolak.`, + }), + photos_shared: (p) => ({ + title: `${p.count} foto dibagikan`, + body: `${p.actor} membagikan ${p.count} foto di "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Pesan baru di "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Pengepakan: ${p.category}`, + body: `${p.actor} menugaskan Anda ke kategori "${p.category}" di "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Versi TREK baru tersedia', + body: `TREK ${p.version} sekarang tersedia. Kunjungi panel admin untuk memperbarui.`, + }), + synology_session_cleared: () => ({ + title: 'Sesi Synology dihapus', + body: 'Akun atau URL Synology Anda berubah. Anda telah keluar dari Synology Photos.', + }), + }, + passwordReset: { + subject: 'Setel ulang kata sandi Anda', + greeting: 'Halo', + body: 'Kami menerima permintaan untuk menyetel ulang kata sandi akun TREK Anda. Klik tombol di bawah untuk menetapkan kata sandi baru.', + ctaIntro: 'Setel ulang kata sandi', + expiry: 'Tautan ini kedaluwarsa dalam 60 menit.', + ignore: + 'Jika Anda tidak meminta ini, Anda dapat mengabaikan email ini — kata sandi Anda tidak akan berubah.', + }, +}; + +export default id; diff --git a/shared/src/i18n/id/files.ts b/shared/src/i18n/id/files.ts new file mode 100644 index 00000000..e73c981f --- /dev/null +++ b/shared/src/i18n/id/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'File', + 'files.pageTitle': 'File & Dokumen', + 'files.subtitle': '{count} file untuk {trip}', + 'files.download': 'Unduh', + 'files.openError': 'Tidak dapat membuka file', + 'files.downloadPdf': 'Unduh PDF', + 'files.count': '{count} file', + 'files.countSingular': '1 berkas', + 'files.uploaded': '{count} diunggah', + 'files.uploadError': 'Gagal mengunggah', + 'files.dropzone': 'Jatuhkan file di sini', + 'files.dropzoneHint': 'atau klik untuk memilih', + 'files.allowedTypes': + 'Gambar, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB', + 'files.uploading': 'Mengunggah...', + 'files.filterAll': 'Semua', + 'files.filterPdf': 'PDFs', + 'files.filterImages': 'Gambar', + 'files.filterDocs': 'Dokumen', + 'files.filterCollab': 'Catatan Collab', + 'files.sourceCollab': 'Dari Catatan Collab', + 'files.empty': 'Belum ada file', + 'files.emptyHint': 'Unggah file untuk melampirkannya ke perjalananmu', + 'files.openTab': 'Buka di tab baru', + 'files.confirm.delete': 'Yakin ingin menghapus file ini?', + 'files.toast.deleted': 'File dihapus', + 'files.toast.deleteError': 'Gagal menghapus file', + 'files.sourcePlan': 'Rencana Harian', + 'files.sourceBooking': 'Pemesanan', + 'files.sourceTransport': 'Transportasi', + 'files.attach': 'Lampirkan', + 'files.pasteHint': 'Kamu juga bisa menempel gambar dari clipboard (Ctrl+V)', + 'files.trash': 'Sampah', + 'files.trashEmpty': 'Sampah kosong', + 'files.emptyTrash': 'Kosongkan Sampah', + 'files.restore': 'Pulihkan', + 'files.star': 'Bintang', + 'files.unstar': 'Hapus bintang', + 'files.assign': 'Tugaskan', + 'files.assignTitle': 'Tugaskan File', + 'files.assignPlace': 'Tempat', + 'files.assignBooking': 'Pemesanan', + 'files.assignTransport': 'Transportasi', + 'files.unassigned': 'Tidak ditugaskan', + 'files.unlink': 'Hapus tautan', + 'files.toast.trashed': 'Dipindahkan ke sampah', + 'files.toast.restored': 'File dipulihkan', + 'files.toast.trashEmptied': 'Sampah dikosongkan', + 'files.toast.assigned': 'File ditugaskan', + 'files.toast.assignError': 'Penugasan gagal', + 'files.toast.restoreError': 'Pemulihan gagal', + 'files.confirm.permanentDelete': + 'Hapus file ini secara permanen? Tindakan ini tidak bisa dibatalkan.', + 'files.confirm.emptyTrash': + 'Hapus semua file di sampah secara permanen? Tindakan ini tidak bisa dibatalkan.', + 'files.noteLabel': 'Catatan', + 'files.notePlaceholder': 'Tambahkan catatan...', +}; +export default files; diff --git a/shared/src/i18n/id/index.ts b/shared/src/i18n/id/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/id/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/id/inspector.ts b/shared/src/i18n/id/inspector.ts new file mode 100644 index 00000000..7a4ef420 --- /dev/null +++ b/shared/src/i18n/id/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Buka', + 'inspector.closed': 'Tutup', + 'inspector.openingHours': 'Jam Buka', + 'inspector.showHours': 'Tampilkan jam buka', + 'inspector.files': 'File', + 'inspector.filesCount': '{count} file', + 'inspector.remove': 'Hapus', + 'inspector.removeFromDay': 'Hapus dari Hari', + 'inspector.addToDay': 'Tambah ke Hari', + 'inspector.confirmedRes': 'Reservasi Dikonfirmasi', + 'inspector.pendingRes': 'Reservasi Menunggu', + 'inspector.google': 'Buka di Google Maps', + 'inspector.website': 'Buka Situs Web', + 'inspector.addRes': 'Reservasi', + 'inspector.editRes': 'Edit Reservasi', + 'inspector.participants': 'Peserta', + 'inspector.trackStats': 'Statistik Jalur', +}; +export default inspector; diff --git a/shared/src/i18n/id/journey.ts b/shared/src/i18n/id/journey.ts new file mode 100644 index 00000000..b4d3c941 --- /dev/null +++ b/shared/src/i18n/id/journey.ts @@ -0,0 +1,241 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Cari perjalanan…', + 'journey.search.noResults': + 'Tidak ada perjalanan yang cocok dengan "{query}"', + 'journey.title': 'Journey', + 'journey.subtitle': 'Lacak perjalananmu saat terjadi', + 'journey.new': 'Journey Baru', + 'journey.create': 'Buat', + 'journey.titlePlaceholder': 'Ke mana kamu pergi?', + 'journey.empty': 'Belum ada journey', + 'journey.emptyHint': 'Mulai mendokumentasikan perjalananmu berikutnya', + 'journey.deleted': 'Journey dihapus', + 'journey.createError': 'Tidak dapat membuat journey', + 'journey.deleteError': 'Tidak dapat menghapus journey', + 'journey.deleteConfirmTitle': 'Hapus', + 'journey.deleteConfirmMessage': + 'Hapus "{title}"? Tindakan ini tidak dapat dibatalkan.', + 'journey.deleteConfirmGeneric': 'Apakah kamu yakin ingin menghapus ini?', + 'journey.notFound': 'Journey tidak ditemukan', + 'journey.photos': 'Foto', + 'journey.timelineEmpty': 'Belum ada persinggahan', + 'journey.timelineEmptyHint': + 'Tambahkan check-in atau tulis entri jurnal untuk memulai', + 'journey.status.draft': 'Draf', + 'journey.status.active': 'Aktif', + 'journey.status.completed': 'Selesai', + 'journey.status.upcoming': 'Mendatang', + 'journey.status.archived': 'Diarsipkan', + 'journey.checkin.add': 'Check in', + 'journey.checkin.namePlaceholder': 'Nama lokasi', + 'journey.checkin.notesPlaceholder': 'Catatan (opsional)', + 'journey.checkin.save': 'Simpan', + 'journey.checkin.error': 'Tidak dapat menyimpan check-in', + 'journey.entry.add': 'Jurnal', + 'journey.entry.edit': 'Edit entri', + 'journey.entry.titlePlaceholder': 'Judul (opsional)', + 'journey.entry.bodyPlaceholder': 'Apa yang terjadi hari ini?', + 'journey.entry.save': 'Simpan', + 'journey.entry.error': 'Tidak dapat menyimpan entri', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Unggah gagal', + 'journey.share.share': 'Bagikan', + 'journey.share.public': 'Publik', + 'journey.share.linkCopied': 'Tautan publik disalin', + 'journey.share.disabled': 'Berbagi publik dinonaktifkan', + 'journey.editor.titlePlaceholder': 'Beri nama momen ini...', + 'journey.editor.bodyPlaceholder': 'Ceritakan kisah hari ini...', + 'journey.editor.placePlaceholder': 'Lokasi (opsional)', + 'journey.editor.tagsPlaceholder': + 'Tag: permata tersembunyi, makan terbaik, wajib dikunjungi lagi...', + 'journey.visibility.private': 'Pribadi', + 'journey.visibility.shared': 'Dibagikan', + 'journey.visibility.public': 'Publik', + 'journey.emptyState.title': 'Kisahmu dimulai di sini', + 'journey.emptyState.subtitle': + 'Check in di suatu tempat atau tulis entri jurnal pertamamu', + 'journey.frontpage.subtitle': + 'Ubah perjalananmu menjadi kisah yang tak terlupakan', + 'journey.frontpage.createJourney': 'Buat Journey', + 'journey.frontpage.activeJourney': 'Journey Aktif', + 'journey.frontpage.allJourneys': 'Semua Journey', + 'journey.frontpage.journeys': 'journey', + 'journey.frontpage.createNew': 'Buat Journey baru', + 'journey.frontpage.createNewSub': + 'Pilih perjalanan, tulis cerita, bagikan petualanganmu', + 'journey.frontpage.live': 'Langsung', + 'journey.frontpage.synced': 'Tersinkron', + 'journey.frontpage.continueWriting': 'Lanjutkan menulis', + 'journey.frontpage.updated': 'Diperbarui {time}', + 'journey.frontpage.suggestionLabel': 'Perjalanan baru saja selesai', + 'journey.frontpage.suggestionText': + 'Ubah {title} menjadi Journey', + 'journey.frontpage.dismiss': 'Tutup', + 'journey.frontpage.journeyName': 'Nama Journey', + 'journey.frontpage.namePlaceholder': 'mis. Asia Tenggara 2026', + 'journey.frontpage.selectTrips': 'Pilih Perjalanan', + 'journey.frontpage.tripsSelected': 'perjalanan dipilih', + 'journey.frontpage.trips': 'perjalanan', + 'journey.frontpage.placesImported': 'tempat akan diimpor', + 'journey.frontpage.places': 'tempat', + 'journey.detail.backToJourney': 'Kembali ke Journey', + 'journey.detail.syncedWithTrips': 'Tersinkron dengan Perjalanan', + 'journey.detail.addEntry': 'Tambah Entri', + 'journey.detail.newEntry': 'Entri Baru', + 'journey.detail.editEntry': 'Edit Entri', + 'journey.detail.noEntries': 'Belum ada entri', + 'journey.detail.noEntriesHint': + 'Tambahkan perjalanan untuk mulai dengan entri kerangka', + 'journey.detail.noPhotos': 'Belum ada foto', + 'journey.detail.noPhotosHint': + 'Unggah foto ke entri atau jelajahi galeri Immich/Synology-mu', + 'journey.detail.journeyStats': 'Statistik Journey', + 'journey.detail.syncedTrips': 'Perjalanan Tersinkron', + 'journey.detail.noTripsLinked': 'Belum ada perjalanan yang ditautkan', + 'journey.detail.contributors': 'Kontributor', + 'journey.detail.readMore': 'Baca selengkapnya', + 'journey.detail.prosCons': 'Pro & Kontra', + 'journey.detail.photos': 'foto', + 'journey.detail.day': 'Hari {number}', + 'journey.detail.places': 'tempat', + 'journey.stats.days': 'Hari', + 'journey.stats.cities': 'Kota', + 'journey.stats.entries': 'Entri', + 'journey.stats.photos': 'Foto', + 'journey.stats.places': 'Tempat', + 'journey.skeletons.show': 'Tampilkan saran', + 'journey.skeletons.hide': 'Sembunyikan saran', + 'journey.verdict.lovedIt': 'Sangat suka', + 'journey.verdict.couldBeBetter': 'Bisa lebih baik', + 'journey.synced.places': 'tempat', + 'journey.synced.synced': 'tersinkron', + 'journey.editor.discardChangesConfirm': + 'Anda memiliki perubahan yang belum disimpan. Buang?', + 'journey.editor.uploadFailed': 'Gagal mengunggah foto', + 'journey.editor.uploadPhotos': 'Unggah foto', + 'journey.editor.uploading': 'Mengunggah...', + 'journey.editor.uploadingProgress': 'Mengunggah {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} dari {total} foto gagal — simpan lagi untuk mencoba ulang', + 'journey.editor.fromGallery': 'Dari Galeri', + 'journey.editor.allPhotosAdded': 'Semua foto sudah ditambahkan', + 'journey.editor.writeStory': 'Tulis kisahmu...', + 'journey.editor.prosCons': 'Pro & Kontra', + 'journey.editor.pros': 'Pro', + 'journey.editor.cons': 'Kontra', + 'journey.editor.proPlaceholder': 'Sesuatu yang bagus...', + 'journey.editor.conPlaceholder': 'Tidak begitu bagus...', + 'journey.editor.addAnother': 'Tambah lagi', + 'journey.editor.date': 'Tanggal', + 'journey.editor.location': 'Lokasi', + 'journey.editor.searchLocation': 'Cari lokasi...', + 'journey.editor.mood': 'Suasana Hati', + 'journey.editor.weather': 'Cuaca', + 'journey.editor.photoFirst': '1.', + 'journey.editor.makeFirst': 'Jadikan ke-1', + 'journey.editor.searching': 'Mencari...', + 'journey.mood.amazing': 'Luar biasa', + 'journey.mood.good': 'Baik', + 'journey.mood.neutral': 'Biasa', + 'journey.mood.rough': 'Berat', + 'journey.weather.sunny': 'Cerah', + 'journey.weather.partly': 'Berawan sebagian', + 'journey.weather.cloudy': 'Mendung', + 'journey.weather.rainy': 'Hujan', + 'journey.weather.stormy': 'Badai', + 'journey.weather.cold': 'Bersalju', + 'journey.trips.linkTrip': 'Tautkan Perjalanan', + 'journey.trips.searchTrip': 'Cari Perjalanan', + 'journey.trips.searchPlaceholder': 'Nama perjalanan atau tujuan...', + 'journey.trips.noTripsAvailable': 'Tidak ada perjalanan tersedia', + 'journey.trips.link': 'Tautkan', + 'journey.trips.tripLinked': 'Perjalanan ditautkan', + 'journey.trips.linkFailed': 'Gagal menautkan perjalanan', + 'journey.trips.addTrip': 'Tambah Perjalanan', + 'journey.trips.unlinkTrip': 'Lepas Tautan Perjalanan', + 'journey.trips.unlinkMessage': + 'Lepas tautan "{title}"? Semua entri dan foto yang tersinkron dari perjalanan ini akan dihapus permanen. Tindakan ini tidak dapat dibatalkan.', + 'journey.trips.unlink': 'Lepas Tautan', + 'journey.trips.tripUnlinked': 'Tautan perjalanan dilepas', + 'journey.trips.unlinkFailed': 'Gagal melepas tautan perjalanan', + 'journey.trips.noTripsLinkedSettings': 'Tidak ada perjalanan yang ditautkan', + 'journey.contributors.invite': 'Undang Kontributor', + 'journey.contributors.searchUser': 'Cari Pengguna', + 'journey.contributors.searchPlaceholder': 'Nama pengguna atau email...', + 'journey.contributors.noUsers': 'Tidak ada pengguna ditemukan', + 'journey.contributors.role': 'Peran', + 'journey.contributors.added': 'Kontributor ditambahkan', + 'journey.contributors.addFailed': 'Gagal menambahkan kontributor', + 'journey.share.publicShare': 'Berbagi Publik', + 'journey.share.createLink': 'Buat tautan berbagi', + 'journey.share.linkCreated': 'Tautan berbagi dibuat', + 'journey.share.createFailed': 'Gagal membuat tautan', + 'journey.share.copy': 'Salin', + 'journey.share.copied': 'Disalin!', + 'journey.share.timeline': 'Linimasa', + 'journey.share.gallery': 'Galeri', + 'journey.share.map': 'Peta', + 'journey.share.removeLink': 'Hapus tautan berbagi', + 'journey.share.linkDeleted': 'Tautan berbagi dihapus', + 'journey.share.deleteFailed': 'Gagal menghapus', + 'journey.share.updateFailed': 'Gagal memperbarui', + 'journey.invite.role': 'Peran', + 'journey.invite.viewer': 'Penonton', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Undang', + 'journey.invite.inviting': 'Mengundang...', + 'journey.settings.title': 'Pengaturan Journey', + 'journey.settings.coverImage': 'Gambar Sampul', + 'journey.settings.changeCover': 'Ubah sampul', + 'journey.settings.addCover': 'Tambah gambar sampul', + 'journey.settings.name': 'Nama', + 'journey.settings.subtitle': 'Subjudul', + 'journey.settings.subtitlePlaceholder': 'mis. Thailand, Vietnam & Kamboja', + 'journey.settings.endJourney': 'Arsipkan Perjalanan', + 'journey.settings.reopenJourney': 'Pulihkan Perjalanan', + 'journey.settings.archived': 'Perjalanan diarsipkan', + 'journey.settings.reopened': 'Perjalanan dibuka kembali', + 'journey.settings.endDescription': + 'Menyembunyikan lencana Langsung. Anda dapat membuka kembali kapan saja.', + 'journey.settings.delete': 'Hapus', + 'journey.settings.deleteJourney': 'Hapus Journey', + 'journey.settings.deleteMessage': + 'Hapus "{title}"? Semua entri dan foto akan hilang.', + 'journey.settings.saved': 'Pengaturan disimpan', + 'journey.settings.saveFailed': 'Gagal menyimpan', + 'journey.settings.coverUpdated': 'Sampul diperbarui', + 'journey.settings.coverFailed': 'Unggah gagal', + 'journey.settings.failedToDelete': 'Gagal menghapus', + 'journey.entries.deleteTitle': 'Hapus Entri', + 'journey.photosUploaded': '{count} foto diunggah', + 'journey.photosUploadFailed': 'Beberapa foto gagal diunggah', + 'journey.photosAdded': '{count} foto ditambahkan', + 'journey.public.notFound': 'Tidak Ditemukan', + 'journey.public.notFoundMessage': + 'Journey ini tidak ada atau tautan telah kedaluwarsa.', + 'journey.public.readOnly': 'Hanya baca · Journey Publik', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Dibagikan melalui', + 'journey.public.madeWith': 'Dibuat dengan', + 'journey.pdf.journeyBook': 'Buku Journey', + 'journey.pdf.madeWith': 'Dibuat dengan TREK', + 'journey.pdf.day': 'Hari', + 'journey.pdf.theEnd': 'Tamat', + 'journey.pdf.saveAsPdf': 'Simpan sebagai PDF', + 'journey.pdf.pages': 'halaman', + 'journey.picker.tripPeriod': 'Periode Perjalanan', + 'journey.picker.dateRange': 'Rentang Tanggal', + 'journey.picker.allPhotos': 'Semua Foto', + 'journey.picker.albums': 'Album', + 'journey.picker.selected': 'dipilih', + 'journey.picker.addTo': 'Tambahkan ke', + 'journey.picker.newGallery': 'Galeri Baru', + 'journey.picker.selectAll': 'Pilih semua', + 'journey.picker.deselectAll': 'Batalkan semua', + 'journey.picker.noAlbums': 'Tidak ada album ditemukan', + 'journey.picker.selectDate': 'Pilih tanggal', + 'journey.picker.search': 'Cari', +}; +export default journey; diff --git a/shared/src/i18n/id/login.ts b/shared/src/i18n/id/login.ts new file mode 100644 index 00000000..948382b6 --- /dev/null +++ b/shared/src/i18n/id/login.ts @@ -0,0 +1,97 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Login gagal. Periksa kembali kredensial kamu.', + 'login.tagline': 'Perjalananmu.\nRencanamu.', + 'login.description': + 'Rencanakan perjalanan bersama dengan peta interaktif, anggaran, dan sinkronisasi real-time.', + 'login.features.maps': 'Peta Interaktif', + 'login.features.mapsDesc': 'Google Places, rute & pengelompokan', + 'login.features.realtime': 'Sinkronisasi Real-Time', + 'login.features.realtimeDesc': 'Rencanakan bersama via WebSocket', + 'login.features.budget': 'Pelacak Anggaran', + 'login.features.budgetDesc': 'Kategori, grafik & biaya per orang', + 'login.features.collab': 'Kolaborasi', + 'login.features.collabDesc': 'Multi-pengguna dengan perjalanan bersama', + 'login.features.packing': 'Daftar Packing', + 'login.features.packingDesc': 'Kategori, progres & saran', + 'login.features.bookings': 'Reservasi', + 'login.features.bookingsDesc': 'Penerbangan, hotel, restoran & lainnya', + 'login.features.files': 'Dokumen', + 'login.features.filesDesc': 'Unggah & kelola dokumen', + 'login.features.routes': 'Rute Cerdas', + 'login.features.routesDesc': 'Optimasi otomatis & ekspor ke Google Maps', + 'login.selfHosted': 'Self-hosted · Open Source · Datamu tetap milikmu', + 'login.title': 'Masuk', + 'login.subtitle': 'Selamat datang kembali', + 'login.signingIn': 'Sedang masuk…', + 'login.signIn': 'Masuk', + 'login.createAdmin': 'Buat Akun Admin', + 'login.createAdminHint': 'Siapkan akun admin pertama untuk TREK.', + 'login.setNewPassword': 'Atur Kata Sandi Baru', + 'login.setNewPasswordHint': + 'Kamu harus mengganti kata sandi sebelum melanjutkan.', + 'login.createAccount': 'Buat Akun', + 'login.createAccountHint': 'Daftarkan akun baru.', + 'login.creating': 'Membuat…', + 'login.noAccount': 'Belum punya akun?', + 'login.hasAccount': 'Sudah punya akun?', + 'login.register': 'Daftar', + 'login.emailPlaceholder': 'kamu@email.com', + 'login.username': 'Nama pengguna', + 'login.oidc.registrationDisabled': + 'Pendaftaran dinonaktifkan. Hubungi administrator kamu.', + 'login.oidc.noEmail': 'Tidak ada email yang diterima dari penyedia.', + 'login.oidc.tokenFailed': 'Autentikasi gagal.', + 'login.oidc.invalidState': 'Sesi tidak valid. Coba lagi.', + 'login.demoFailed': 'Login demo gagal', + 'login.oidcSignIn': 'Masuk dengan {name}', + 'login.oidcOnly': + 'Autentikasi kata sandi dinonaktifkan. Masuk menggunakan penyedia SSO kamu.', + 'login.oidcLoggedOut': + 'Kamu telah keluar. Masuk kembali menggunakan penyedia SSO kamu.', + 'login.demoHint': 'Coba demo — tidak perlu registrasi', + 'login.mfaTitle': 'Autentikasi dua faktor', + 'login.mfaSubtitle': 'Masukkan kode 6 digit dari aplikasi autentikator kamu.', + 'login.mfaCodeLabel': 'Kode verifikasi', + 'login.mfaCodeRequired': 'Masukkan kode dari aplikasi autentikator kamu.', + 'login.mfaHint': + 'Buka Google Authenticator, Authy, atau aplikasi TOTP lainnya.', + 'login.mfaBack': '← Kembali ke halaman masuk', + 'login.mfaVerify': 'Verifikasi', + 'login.invalidInviteLink': + 'Tautan undangan tidak valid atau sudah kedaluwarsa', + 'login.oidcFailed': 'Login OIDC gagal', + 'login.usernameRequired': 'Nama pengguna wajib diisi', + 'login.passwordMinLength': 'Kata sandi minimal 8 karakter', + 'login.forgotPassword': 'Lupa kata sandi?', + 'login.forgotPasswordTitle': 'Setel ulang kata sandi', + 'login.forgotPasswordBody': + 'Masukkan alamat email akunmu. Jika akun ada, kami akan mengirim tautan reset.', + 'login.forgotPasswordSubmit': 'Kirim tautan', + 'login.forgotPasswordSentTitle': 'Periksa email kamu', + 'login.forgotPasswordSentBody': + 'Jika ada akun dengan email tersebut, tautannya sedang dikirim. Berlaku 60 menit.', + 'login.forgotPasswordSmtpHintOff': + 'Catatan: administrator belum mengonfigurasi SMTP, jadi tautan reset akan ditulis ke konsol server alih-alih dikirim lewat email.', + 'login.backToLogin': 'Kembali ke login', + 'login.newPassword': 'Kata sandi baru', + 'login.confirmPassword': 'Konfirmasi kata sandi baru', + 'login.passwordsDontMatch': 'Kata sandi tidak cocok', + 'login.mfaCode': 'Kode 2FA', + 'login.resetPasswordTitle': 'Tetapkan kata sandi baru', + 'login.resetPasswordBody': + 'Pilih kata sandi kuat yang belum pernah kamu pakai di sini. Minimal 8 karakter.', + 'login.resetPasswordMfaBody': + 'Masukkan kode 2FA atau kode cadangan untuk menyelesaikan reset.', + 'login.resetPasswordSubmit': 'Setel ulang kata sandi', + 'login.resetPasswordVerify': 'Verifikasi & setel ulang', + 'login.resetPasswordSuccessTitle': 'Kata sandi diperbarui', + 'login.resetPasswordSuccessBody': + 'Sekarang kamu bisa login dengan kata sandi baru.', + 'login.resetPasswordInvalidLink': 'Tautan tidak valid', + 'login.resetPasswordInvalidLinkBody': + 'Tautan hilang atau rusak. Minta tautan baru untuk melanjutkan.', + 'login.resetPasswordFailed': 'Reset gagal. Tautan mungkin sudah kedaluwarsa.', +}; +export default login; diff --git a/shared/src/i18n/id/map.ts b/shared/src/i18n/id/map.ts new file mode 100644 index 00000000..f35ac281 --- /dev/null +++ b/shared/src/i18n/id/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Koneksi', + 'map.showConnections': 'Tampilkan rute pemesanan', + 'map.hideConnections': 'Sembunyikan rute pemesanan', +}; +export default map; diff --git a/shared/src/i18n/id/members.ts b/shared/src/i18n/id/members.ts new file mode 100644 index 00000000..eebdb9f0 --- /dev/null +++ b/shared/src/i18n/id/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Bagikan Perjalanan', + 'members.inviteUser': 'Undang Pengguna', + 'members.selectUser': 'Pilih pengguna…', + 'members.invite': 'Undang', + 'members.allHaveAccess': 'Semua pengguna sudah punya akses.', + 'members.access': 'Akses', + 'members.person': 'orang', + 'members.persons': 'orang', + 'members.you': 'kamu', + 'members.owner': 'Pemilik', + 'members.leaveTrip': 'Keluar dari perjalanan', + 'members.removeAccess': 'Hapus akses', + 'members.confirmLeave': 'Keluar dari perjalanan? Kamu akan kehilangan akses.', + 'members.confirmRemove': 'Hapus akses untuk pengguna ini?', + 'members.loadError': 'Gagal memuat anggota', + 'members.added': 'ditambahkan', + 'members.addError': 'Gagal menambahkan', + 'members.removed': 'Anggota dihapus', + 'members.removeError': 'Gagal menghapus', +}; +export default members; diff --git a/shared/src/i18n/id/memories.ts b/shared/src/i18n/id/memories.ts new file mode 100644 index 00000000..986b632c --- /dev/null +++ b/shared/src/i18n/id/memories.ts @@ -0,0 +1,80 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Foto', + 'memories.notConnected': '{provider_name} belum terhubung', + 'memories.notConnectedHint': + 'Hubungkan instans {provider_name} kamu di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.', + 'memories.notConnectedMultipleHint': + 'Hubungkan salah satu penyedia foto berikut: {provider_names} di Pengaturan untuk dapat menambahkan foto ke perjalanan ini.', + 'memories.noDates': 'Tambahkan tanggal ke perjalananmu untuk memuat foto.', + 'memories.noPhotos': 'Foto tidak ditemukan', + 'memories.noPhotosHint': + 'Tidak ada foto ditemukan di {provider_name} untuk rentang tanggal perjalanan ini.', + 'memories.photosFound': 'foto', + 'memories.fromOthers': 'dari orang lain', + 'memories.sharePhotos': 'Bagikan foto', + 'memories.sharing': 'Berbagi', + 'memories.reviewTitle': 'Tinjau foto kamu', + 'memories.reviewHint': 'Klik foto untuk mengecualikannya dari berbagi.', + 'memories.shareCount': 'Bagikan {count} foto', + 'memories.providerUrl': 'URL Server', + 'memories.providerApiKey': 'API Key', + 'memories.providerUsername': 'Nama pengguna', + 'memories.providerPassword': 'Kata sandi', + 'memories.providerOTP': 'Kode MFA (jika diaktifkan)', + 'memories.skipSSLVerification': 'Lewati verifikasi sertifikat SSL', + 'memories.immichAutoUpload': 'Salin foto journey ke Immich saat diunggah', + 'memories.providerUrlHintSynology': + 'Sertakan path aplikasi Photos di URL, mis. https://nas:5001/photo', + 'memories.testConnection': 'Uji koneksi', + 'memories.testShort': 'Uji', + 'memories.testFirst': 'Uji koneksi terlebih dahulu', + 'memories.connected': 'Terhubung', + 'memories.disconnected': 'Tidak terhubung', + 'memories.connectionSuccess': 'Terhubung ke {provider_name}', + 'memories.connectionError': 'Tidak dapat terhubung ke {provider_name}', + 'memories.saved': 'Pengaturan {provider_name} disimpan', + 'memories.providerDisconnectedBanner': + 'Koneksi {provider_name} kamu terputus. Hubungkan ulang di Pengaturan untuk melihat foto.', + 'memories.saveError': 'Tidak dapat menyimpan pengaturan {provider_name}', + 'memories.addPhotos': 'Tambah foto', + 'memories.linkAlbum': 'Tautkan Album', + 'memories.selectAlbum': 'Pilih Album {provider_name}', + 'memories.selectAlbumMultiple': 'Pilih Album', + 'memories.noAlbums': 'Tidak ada album ditemukan', + 'memories.syncAlbum': 'Sinkronkan album', + 'memories.unlinkAlbum': 'Putuskan tautan album', + 'memories.photos': 'foto', + 'memories.selectPhotos': 'Pilih foto dari {provider_name}', + 'memories.selectPhotosMultiple': 'Pilih Foto', + 'memories.selectHint': 'Ketuk foto untuk memilihnya.', + 'memories.selected': 'dipilih', + 'memories.addSelected': 'Tambah {count} foto', + 'memories.alreadyAdded': 'Sudah ditambahkan', + 'memories.private': 'Privat', + 'memories.stopSharing': 'Berhenti berbagi', + 'memories.oldest': 'Terlama dulu', + 'memories.newest': 'Terbaru dulu', + 'memories.allLocations': 'Semua lokasi', + 'memories.tripDates': 'Tanggal perjalanan', + 'memories.allPhotos': 'Semua foto', + 'memories.confirmShareTitle': 'Bagikan ke anggota perjalanan?', + 'memories.confirmShareHint': + '{count} foto akan terlihat oleh semua anggota perjalanan ini. Kamu bisa membuat foto tertentu menjadi privat nanti.', + 'memories.confirmShareButton': 'Bagikan foto', + 'memories.error.loadAlbums': 'Gagal memuat album', + 'memories.error.linkAlbum': 'Gagal menautkan album', + 'memories.error.unlinkAlbum': 'Gagal memutuskan tautan album', + 'memories.error.syncAlbum': 'Gagal menyinkronkan album', + 'memories.error.loadPhotos': 'Gagal memuat foto', + 'memories.error.addPhotos': 'Gagal menambahkan foto', + 'memories.error.removePhoto': 'Gagal menghapus foto', + 'memories.error.toggleSharing': 'Gagal memperbarui berbagi', + 'memories.saveRouteNotConfigured': + 'Rute simpan tidak dikonfigurasi untuk penyedia ini', + 'memories.testRouteNotConfigured': + 'Rute uji tidak dikonfigurasi untuk penyedia ini', + 'memories.fillRequiredFields': 'Harap isi semua kolom yang wajib diisi', +}; +export default memories; diff --git a/shared/src/i18n/id/nav.ts b/shared/src/i18n/id/nav.ts new file mode 100644 index 00000000..723fa26c --- /dev/null +++ b/shared/src/i18n/id/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Perjalanan', + 'nav.share': 'Bagikan', + 'nav.settings': 'Pengaturan', + 'nav.admin': 'Admin', + 'nav.logout': 'Keluar', + 'nav.lightMode': 'Mode Terang', + 'nav.darkMode': 'Mode Gelap', + 'nav.autoMode': 'Mode Otomatis', + 'nav.administrator': 'Administrator', + 'nav.myTrips': 'Perjalananku', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Pengaturan', + 'nav.bottomAdmin': 'Pengaturan Admin', + 'nav.bottomLogout': 'Keluar', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/id/notif.ts b/shared/src/i18n/id/notif.ts new file mode 100644 index 00000000..81553637 --- /dev/null +++ b/shared/src/i18n/id/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notifikasi', + 'notif.test.simple.text': 'Ini adalah notifikasi uji sederhana.', + 'notif.test.boolean.text': 'Apakah kamu menerima notifikasi uji ini?', + 'notif.test.navigate.text': 'Klik di bawah untuk pergi ke dasbor.', + 'notif.trip_invite.title': 'Undangan Perjalanan', + 'notif.trip_invite.text': '{actor} mengundangmu ke {trip}', + 'notif.booking_change.title': 'Pemesanan Diperbarui', + 'notif.booking_change.text': '{actor} memperbarui pemesanan di {trip}', + 'notif.trip_reminder.title': 'Pengingat Perjalanan', + 'notif.trip_reminder.text': 'Perjalananmu {trip} akan segera dimulai!', + 'notif.todo_due.title': 'Tugas jatuh tempo', + 'notif.todo_due.text': '{todo} di {trip} jatuh tempo pada {due}', + 'notif.vacay_invite.title': 'Undangan Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} mengundangmu untuk menggabungkan rencana liburan', + 'notif.photos_shared.title': 'Foto Dibagikan', + 'notif.photos_shared.text': '{actor} membagikan {count} foto di {trip}', + 'notif.collab_message.title': 'Pesan Baru', + 'notif.collab_message.text': '{actor} mengirim pesan di {trip}', + 'notif.packing_tagged.title': 'Penugasan Perlengkapan', + 'notif.packing_tagged.text': '{actor} menugaskanmu ke {category} di {trip}', + 'notif.version_available.title': 'Versi Baru Tersedia', + 'notif.version_available.text': 'TREK {version} kini tersedia', + 'notif.action.view_trip': 'Lihat Perjalanan', + 'notif.action.view_collab': 'Lihat Pesan', + 'notif.action.view_packing': 'Lihat Perlengkapan', + 'notif.action.view_photos': 'Lihat Foto', + 'notif.action.view_vacay': 'Lihat Vacay', + 'notif.action.view_admin': 'Ke Admin', + 'notif.action.view': 'Lihat', + 'notif.action.accept': 'Terima', + 'notif.action.decline': 'Tolak', + 'notif.generic.title': 'Notifikasi', + 'notif.generic.text': 'Kamu punya notifikasi baru', + 'notif.dev.unknown_event.title': '[DEV] Event Tidak Dikenal', + 'notif.dev.unknown_event.text': + 'Tipe event "{event}" tidak terdaftar di EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/id/notifications.ts b/shared/src/i18n/id/notifications.ts new file mode 100644 index 00000000..f271a9a9 --- /dev/null +++ b/shared/src/i18n/id/notifications.ts @@ -0,0 +1,38 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notifikasi', + 'notifications.markAllRead': 'Tandai semua dibaca', + 'notifications.deleteAll': 'Hapus semua', + 'notifications.showAll': 'Tampilkan semua notifikasi', + 'notifications.empty': 'Tidak ada notifikasi', + 'notifications.emptyDescription': 'Semuanya sudah terbaca!', + 'notifications.all': 'Semua', + 'notifications.unreadOnly': 'Belum dibaca', + 'notifications.markRead': 'Tandai sudah dibaca', + 'notifications.markUnread': 'Tandai belum dibaca', + 'notifications.delete': 'Hapus', + 'notifications.system': 'Sistem', + 'notifications.synologySessionCleared.title': 'Synology Photos terputus', + 'notifications.synologySessionCleared.text': + 'Server atau akun kamu berubah — buka Pengaturan untuk menguji koneksi lagi.', + 'notifications.versionAvailable.title': 'Pembaruan Tersedia', + 'notifications.versionAvailable.text': 'TREK {version} kini tersedia.', + 'notifications.versionAvailable.button': 'Lihat Detail', + 'notifications.test.title': 'Notifikasi uji dari {actor}', + 'notifications.test.text': 'Ini adalah notifikasi uji sederhana.', + 'notifications.test.booleanTitle': '{actor} meminta persetujuanmu', + 'notifications.test.booleanText': + 'Ini adalah notifikasi uji boolean. Pilih tindakan di bawah.', + 'notifications.test.accept': 'Setujui', + 'notifications.test.decline': 'Tolak', + 'notifications.test.navigateTitle': 'Cek sesuatu', + 'notifications.test.navigateText': 'Ini adalah notifikasi uji navigasi.', + 'notifications.test.goThere': 'Ke sana', + 'notifications.test.adminTitle': 'Siaran Admin', + 'notifications.test.adminText': + '{actor} mengirim notifikasi uji ke semua admin.', + 'notifications.test.tripTitle': '{actor} memposting di perjalananmu', + 'notifications.test.tripText': 'Notifikasi uji untuk perjalanan "{trip}".', +}; +export default notifications; diff --git a/shared/src/i18n/id/oauth.ts b/shared/src/i18n/id/oauth.ts new file mode 100644 index 00000000..b3e74f33 --- /dev/null +++ b/shared/src/i18n/id/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Perjalanan', + 'oauth.scope.group.places': 'Tempat', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Perlengkapan', + 'oauth.scope.group.todos': 'To-do', + 'oauth.scope.group.budget': 'Anggaran', + 'oauth.scope.group.reservations': 'Reservasi', + 'oauth.scope.group.collab': 'Kolaborasi', + 'oauth.scope.group.notifications': 'Notifikasi', + 'oauth.scope.group.vacay': 'Liburan', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Cuaca', + 'oauth.scope.group.journey': 'Journey', + 'oauth.scope.trips:read.label': 'Lihat perjalanan & itinerari', + 'oauth.scope.trips:read.description': + 'Baca perjalanan, hari, catatan harian, dan anggota', + 'oauth.scope.trips:write.label': 'Edit perjalanan & itinerari', + 'oauth.scope.trips:write.description': + 'Buat dan perbarui perjalanan, hari, catatan, dan kelola anggota', + 'oauth.scope.trips:delete.label': 'Hapus perjalanan', + 'oauth.scope.trips:delete.description': + 'Hapus permanen seluruh perjalanan — tindakan ini tidak dapat dibatalkan', + 'oauth.scope.trips:share.label': 'Kelola tautan berbagi', + 'oauth.scope.trips:share.description': + 'Buat, perbarui, dan cabut tautan berbagi publik untuk perjalanan', + 'oauth.scope.places:read.label': 'Lihat tempat & data peta', + 'oauth.scope.places:read.description': + 'Baca tempat, penugasan hari, tag, dan kategori', + 'oauth.scope.places:write.label': 'Kelola tempat', + 'oauth.scope.places:write.description': + 'Buat, perbarui, dan hapus tempat, penugasan, dan tag', + 'oauth.scope.atlas:read.label': 'Lihat Atlas', + 'oauth.scope.atlas:read.description': + 'Baca negara yang dikunjungi, wilayah, dan daftar impian', + 'oauth.scope.atlas:write.label': 'Kelola Atlas', + 'oauth.scope.atlas:write.description': + 'Tandai negara dan wilayah yang dikunjungi, kelola daftar impian', + 'oauth.scope.packing:read.label': 'Lihat daftar perlengkapan', + 'oauth.scope.packing:read.description': + 'Baca barang perlengkapan, tas, dan penugasan kategori', + 'oauth.scope.packing:write.label': 'Kelola daftar perlengkapan', + 'oauth.scope.packing:write.description': + 'Tambah, perbarui, hapus, centang, dan urutkan barang dan tas', + 'oauth.scope.todos:read.label': 'Lihat daftar to-do', + 'oauth.scope.todos:read.description': + 'Baca item to-do perjalanan dan penugasan kategori', + 'oauth.scope.todos:write.label': 'Kelola daftar to-do', + 'oauth.scope.todos:write.description': + 'Buat, perbarui, centang, hapus, dan urutkan item to-do', + 'oauth.scope.budget:read.label': 'Lihat anggaran', + 'oauth.scope.budget:read.description': + 'Baca item anggaran dan rincian pengeluaran', + 'oauth.scope.budget:write.label': 'Kelola anggaran', + 'oauth.scope.budget:write.description': + 'Buat, perbarui, dan hapus item anggaran', + 'oauth.scope.reservations:read.label': 'Lihat reservasi', + 'oauth.scope.reservations:read.description': + 'Baca reservasi dan detail akomodasi', + 'oauth.scope.reservations:write.label': 'Kelola reservasi', + 'oauth.scope.reservations:write.description': + 'Buat, perbarui, hapus, dan urutkan reservasi', + 'oauth.scope.collab:read.label': 'Lihat kolaborasi', + 'oauth.scope.collab:read.description': + 'Baca catatan, polling, dan pesan kolaborasi', + 'oauth.scope.collab:write.label': 'Kelola kolaborasi', + 'oauth.scope.collab:write.description': + 'Buat, perbarui, dan hapus catatan, polling, dan pesan kolaborasi', + 'oauth.scope.notifications:read.label': 'Lihat notifikasi', + 'oauth.scope.notifications:read.description': + 'Baca notifikasi dalam aplikasi dan jumlah yang belum dibaca', + 'oauth.scope.notifications:write.label': 'Kelola notifikasi', + 'oauth.scope.notifications:write.description': + 'Tandai notifikasi sebagai telah dibaca dan tanggapi', + 'oauth.scope.vacay:read.label': 'Lihat rencana liburan', + 'oauth.scope.vacay:read.description': + 'Baca data perencanaan liburan, entri, dan statistik', + 'oauth.scope.vacay:write.label': 'Kelola rencana liburan', + 'oauth.scope.vacay:write.description': + 'Buat dan kelola entri liburan, hari libur, dan rencana tim', + 'oauth.scope.geo:read.label': 'Peta & geokoding', + 'oauth.scope.geo:read.description': + 'Cari lokasi, selesaikan URL peta, dan geokode terbalik koordinat', + 'oauth.scope.weather:read.label': 'Prakiraan cuaca', + 'oauth.scope.weather:read.description': + 'Ambil prakiraan cuaca untuk lokasi dan tanggal perjalanan', + 'oauth.scope.journey:read.label': 'Lihat Journey', + 'oauth.scope.journey:read.description': + 'Baca Journey, entri, dan daftar kontributor', + 'oauth.scope.journey:write.label': 'Kelola Journey', + 'oauth.scope.journey:write.description': + 'Buat, perbarui, dan hapus Journey beserta entrinya', + 'oauth.scope.journey:share.label': 'Kelola tautan Journey', + 'oauth.scope.journey:share.description': + 'Buat, perbarui, dan cabut tautan berbagi publik untuk Journey', +}; +export default oauth; diff --git a/shared/src/i18n/id/packing.ts b/shared/src/i18n/id/packing.ts new file mode 100644 index 00000000..04d73887 --- /dev/null +++ b/shared/src/i18n/id/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Daftar Bawaan', + 'packing.empty': 'Daftar bawaan kosong', + 'packing.import': 'Impor', + 'packing.importTitle': 'Impor Daftar Bawaan', + 'packing.importHint': + 'Satu item per baris. Format: Kategori, Nama, Berat dalam g (opsional), Tas (opsional), checked/unchecked (opsional)', + 'packing.importPlaceholder': + 'Kebersihan, Sikat Gigi\nPakaian, Kaos, 200\nDokumen, Paspor, , Kabin\nElektronik, Charger, 50, Koper, checked', + 'packing.importCsv': 'Muat CSV/TXT', + 'packing.importAction': 'Impor {count}', + 'packing.importSuccess': '{count} item berhasil diimpor', + 'packing.importError': 'Impor gagal', + 'packing.importEmpty': 'Tidak ada item untuk diimpor', + 'packing.progress': '{packed} dari {total} sudah dikemas ({percent}%)', + 'packing.clearChecked': 'Hapus {count} yang dicentang', + 'packing.clearCheckedShort': 'Hapus {count}', + 'packing.suggestions': 'Saran', + 'packing.suggestionsTitle': 'Tambah Saran', + 'packing.allSuggested': 'Semua saran sudah ditambahkan', + 'packing.allPacked': 'Semua sudah dikemas!', + 'packing.addPlaceholder': 'Tambah item baru...', + 'packing.categoryPlaceholder': 'Kategori...', + 'packing.filterAll': 'Semua', + 'packing.filterOpen': 'Belum', + 'packing.filterDone': 'Selesai', + 'packing.emptyTitle': 'Daftar bawaan kosong', + 'packing.emptyHint': 'Tambah item atau gunakan saran', + 'packing.emptyFiltered': 'Tidak ada item yang cocok dengan filter ini', + 'packing.menuRename': 'Ganti Nama', + 'packing.menuCheckAll': 'Centang Semua', + 'packing.menuUncheckAll': 'Hapus Centang Semua', + 'packing.menuDeleteCat': 'Hapus Kategori', + 'packing.noMembers': 'Tidak ada anggota perjalanan', + 'packing.addItem': 'Tambah item', + 'packing.addItemPlaceholder': 'Nama item...', + 'packing.addCategory': 'Tambah kategori', + 'packing.newCategoryPlaceholder': 'Nama kategori (contoh: Pakaian)', + 'packing.applyTemplate': 'Terapkan template', + 'packing.template': 'Template', + 'packing.templateApplied': '{count} item ditambahkan dari template', + 'packing.templateError': 'Gagal menerapkan template', + 'packing.saveAsTemplate': 'Simpan sebagai template', + 'packing.templateName': 'Nama template', + 'packing.templateSaved': 'Daftar bawaan disimpan sebagai template', + 'packing.bags': 'Tas', + 'packing.noBag': 'Belum ditugaskan', + 'packing.totalWeight': 'Total berat', + 'packing.bagName': 'Nama tas...', + 'packing.addBag': 'Tambah tas', + 'packing.changeCategory': 'Ganti Kategori', + 'packing.confirm.clearChecked': + 'Yakin ingin menghapus {count} item yang dicentang?', + 'packing.confirm.deleteCat': + 'Yakin ingin menghapus kategori "{name}" beserta {count} item di dalamnya?', + 'packing.defaultCategory': 'Lainnya', + 'packing.toast.saveError': 'Gagal menyimpan', + 'packing.toast.deleteError': 'Gagal menghapus', + 'packing.toast.renameError': 'Gagal mengganti nama', + 'packing.toast.addError': 'Gagal menambahkan', + 'packing.suggestions.items': [ + { + name: 'Paspor', + category: 'Dokumen', + }, + { + name: 'KTP', + category: 'Dokumen', + }, + { + name: 'Asuransi Perjalanan', + category: 'Dokumen', + }, + { + name: 'Tiket Penerbangan', + category: 'Dokumen', + }, + { + name: 'Kartu Kredit', + category: 'Keuangan', + }, + { + name: 'Uang Tunai', + category: 'Keuangan', + }, + { + name: 'Visa', + category: 'Dokumen', + }, + { + name: 'Kaos', + category: 'Pakaian', + }, + { + name: 'Celana', + category: 'Pakaian', + }, + { + name: 'Pakaian Dalam', + category: 'Pakaian', + }, + { + name: 'Kaos Kaki', + category: 'Pakaian', + }, + { + name: 'Jaket', + category: 'Pakaian', + }, + { + name: 'Pakaian Tidur', + category: 'Pakaian', + }, + { + name: 'Pakaian Renang', + category: 'Pakaian', + }, + { + name: 'Jas Hujan', + category: 'Pakaian', + }, + { + name: 'Sepatu Nyaman', + category: 'Pakaian', + }, + { + name: 'Sikat Gigi', + category: 'Perlengkapan Mandi', + }, + { + name: 'Pasta Gigi', + category: 'Perlengkapan Mandi', + }, + { + name: 'Sampo', + category: 'Perlengkapan Mandi', + }, + { + name: 'Deodoran', + category: 'Perlengkapan Mandi', + }, + { + name: 'Tabir Surya', + category: 'Perlengkapan Mandi', + }, + { + name: 'Pisau Cukur', + category: 'Perlengkapan Mandi', + }, + { + name: 'Charger', + category: 'Elektronik', + }, + { + name: 'Power Bank', + category: 'Elektronik', + }, + { + name: 'Headphone', + category: 'Elektronik', + }, + { + name: 'Adaptor Perjalanan', + category: 'Elektronik', + }, + { + name: 'Kamera', + category: 'Elektronik', + }, + { + name: 'Obat Pereda Nyeri', + category: 'Kesehatan', + }, + { + name: 'Plester', + category: 'Kesehatan', + }, + { + name: 'Disinfektan', + category: 'Kesehatan', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/id/pdf.ts b/shared/src/i18n/id/pdf.ts new file mode 100644 index 00000000..c20334b9 --- /dev/null +++ b/shared/src/i18n/id/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Rencana Perjalanan', + 'pdf.planned': 'Direncanakan', + 'pdf.costLabel': 'Biaya EUR', + 'pdf.preview': 'Pratinjau PDF', + 'pdf.saveAsPdf': 'Simpan sebagai PDF', +}; +export default pdf; diff --git a/shared/src/i18n/id/perm.ts b/shared/src/i18n/id/perm.ts new file mode 100644 index 00000000..b216b5c1 --- /dev/null +++ b/shared/src/i18n/id/perm.ts @@ -0,0 +1,66 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Pengaturan Izin', + 'perm.subtitle': 'Kendalikan siapa yang bisa melakukan tindakan di aplikasi', + 'perm.saved': 'Pengaturan izin disimpan', + 'perm.resetDefaults': 'Kembalikan ke default', + 'perm.customized': 'dikustomisasi', + 'perm.level.admin': 'Hanya Admin', + 'perm.level.tripOwner': 'Pemilik perjalanan', + 'perm.level.tripMember': 'Anggota perjalanan', + 'perm.level.everybody': 'Semua orang', + 'perm.cat.trip': 'Manajemen Perjalanan', + 'perm.cat.members': 'Manajemen Anggota', + 'perm.cat.files': 'File', + 'perm.cat.content': 'Konten & Jadwal', + 'perm.cat.extras': 'Anggaran, Perlengkapan & Kolaborasi', + 'perm.action.trip_create': 'Buat perjalanan', + 'perm.action.trip_edit': 'Edit detail perjalanan', + 'perm.action.trip_delete': 'Hapus perjalanan', + 'perm.action.trip_archive': 'Arsipkan / batalkan arsip perjalanan', + 'perm.action.trip_cover_upload': 'Unggah gambar sampul', + 'perm.action.member_manage': 'Tambah / hapus anggota', + 'perm.action.file_upload': 'Unggah file', + 'perm.action.file_edit': 'Edit metadata file', + 'perm.action.file_delete': 'Hapus file', + 'perm.action.place_edit': 'Tambah / edit / hapus tempat', + 'perm.action.day_edit': 'Edit hari, catatan & penugasan', + 'perm.action.reservation_edit': 'Kelola reservasi', + 'perm.action.budget_edit': 'Kelola anggaran', + 'perm.action.packing_edit': 'Kelola daftar perlengkapan', + 'perm.action.collab_edit': 'Kolaborasi (catatan, polling, chat)', + 'perm.action.share_manage': 'Kelola tautan berbagi', + 'perm.actionHint.trip_create': 'Siapa yang bisa membuat perjalanan baru', + 'perm.actionHint.trip_edit': + 'Siapa yang bisa mengubah nama, tanggal, deskripsi, dan mata uang perjalanan', + 'perm.actionHint.trip_delete': + 'Siapa yang bisa menghapus perjalanan secara permanen', + 'perm.actionHint.trip_archive': + 'Siapa yang bisa mengarsipkan atau membatalkan arsip perjalanan', + 'perm.actionHint.trip_cover_upload': + 'Siapa yang bisa mengunggah atau mengganti gambar sampul', + 'perm.actionHint.member_manage': + 'Siapa yang bisa mengundang atau menghapus anggota perjalanan', + 'perm.actionHint.file_upload': + 'Siapa yang bisa mengunggah file ke perjalanan', + 'perm.actionHint.file_edit': + 'Siapa yang bisa mengedit deskripsi dan tautan file', + 'perm.actionHint.file_delete': + 'Siapa yang bisa memindahkan file ke sampah atau menghapusnya secara permanen', + 'perm.actionHint.place_edit': + 'Siapa yang bisa menambah, mengedit, atau menghapus tempat', + 'perm.actionHint.day_edit': + 'Siapa yang bisa mengedit hari, catatan hari, dan penugasan tempat', + 'perm.actionHint.reservation_edit': + 'Siapa yang bisa membuat, mengedit, atau menghapus reservasi', + 'perm.actionHint.budget_edit': + 'Siapa yang bisa membuat, mengedit, atau menghapus item anggaran', + 'perm.actionHint.packing_edit': + 'Siapa yang bisa mengelola item perlengkapan dan tas', + 'perm.actionHint.collab_edit': + 'Siapa yang bisa membuat catatan, polling, dan mengirim pesan', + 'perm.actionHint.share_manage': + 'Siapa yang bisa membuat atau menghapus tautan berbagi publik', +}; +export default perm; diff --git a/shared/src/i18n/id/photos.ts b/shared/src/i18n/id/photos.ts new file mode 100644 index 00000000..44882b0b --- /dev/null +++ b/shared/src/i18n/id/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Foto', + 'photos.subtitle': '{count} foto untuk {trip}', + 'photos.dropHere': 'Lepas foto di sini...', + 'photos.dropHereActive': 'Lepas foto di sini', + 'photos.captionForAll': 'Keterangan (untuk semua)', + 'photos.captionPlaceholder': 'Keterangan opsional...', + 'photos.addCaption': 'Tambah keterangan...', + 'photos.allDays': 'Semua Hari', + 'photos.noPhotos': 'Belum ada foto', + 'photos.uploadHint': 'Unggah foto perjalananmu', + 'photos.clickToSelect': 'atau klik untuk memilih', + 'photos.linkPlace': 'Tautkan Tempat', + 'photos.noPlace': 'Tanpa Tempat', + 'photos.uploadN': 'Unggah {n} foto', + 'photos.linkDay': 'Tautkan Hari', + 'photos.noDay': 'Tanpa Hari', + 'photos.dayLabel': 'Hari {number}', + 'photos.photoSelected': 'Foto dipilih', + 'photos.photosSelected': 'Foto dipilih', + 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · hingga 30 foto', +}; +export default photos; diff --git a/shared/src/i18n/id/places.ts b/shared/src/i18n/id/places.ts new file mode 100644 index 00000000..431f0a38 --- /dev/null +++ b/shared/src/i18n/id/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Tambah Tempat/Aktivitas', + 'places.importFile': 'Impor file', + 'places.sidebarDrop': 'Lepas untuk mengimpor', + 'places.importFileHint': + 'Impor file .gpx, .kml, atau .kmz dari Google My Maps, Google Earth, atau pelacak GPS.', + 'places.importFileDropHere': + 'Klik untuk memilih file atau seret dan lepas di sini', + 'places.importFileDropActive': 'Lepas file untuk memilih', + 'places.importFileUnsupported': + 'Jenis file tidak didukung. Gunakan .gpx, .kml, atau .kmz.', + 'places.importFileTooLarge': + 'File terlalu besar. Ukuran unggah maksimum adalah {maxMb} MB.', + 'places.importFileError': 'Impor gagal', + 'places.importAllSkipped': 'Semua tempat sudah ada di perjalanan.', + 'places.gpxImported': '{count} tempat diimpor dari GPX', + 'places.gpxImportTypes': 'Apa yang ingin diimpor?', + 'places.gpxImportWaypoints': 'Titik jalan', + 'places.gpxImportRoutes': 'Rute', + 'places.gpxImportTracks': 'Trek (dengan geometri jalur)', + 'places.gpxImportNoneSelected': 'Pilih setidaknya satu jenis untuk diimpor.', + 'places.kmlImportTypes': 'Apa yang ingin diimpor?', + 'places.kmlImportPoints': 'Titik (Placemarks)', + 'places.kmlImportPaths': 'Jalur (LineStrings)', + 'places.kmlImportNoneSelected': 'Pilih setidaknya satu jenis.', + 'places.selectionCount': '{count} dipilih', + 'places.deleteSelected': 'Hapus yang dipilih', + 'places.kmlKmzImported': '{count} tempat diimpor dari KMZ/KML', + 'places.urlResolved': 'Tempat diimpor dari URL', + 'places.importList': 'Impor Daftar', + 'places.kmlKmzSummaryValues': + 'Placemark: {total} • Diimpor: {created} • Dilewati: {skipped}', + 'places.importGoogleList': 'Daftar Google', + 'places.importNaverList': 'Daftar Naver', + 'places.googleListHint': + 'Tempel tautan daftar Google Maps yang dibagikan untuk mengimpor semua tempat.', + 'places.googleListImported': '{count} tempat diimpor dari "{list}"', + 'places.googleListError': 'Gagal mengimpor daftar Google Maps', + 'places.naverListHint': + 'Tempel tautan daftar Naver Maps yang dibagikan untuk mengimpor semua tempat.', + 'places.naverListImported': '{count} tempat diimpor dari "{list}"', + 'places.naverListError': 'Gagal mengimpor daftar Naver Maps', + 'places.viewDetails': 'Lihat Detail', + 'places.assignToDay': 'Tambah ke hari mana?', + 'places.all': 'Semua', + 'places.unplanned': 'Belum direncanakan', + 'places.filterTracks': 'Trek', + 'places.search': 'Cari tempat...', + 'places.allCategories': 'Semua Kategori', + 'places.categoriesSelected': 'kategori', + 'places.clearFilter': 'Hapus filter', + 'places.count': '{count} tempat', + 'places.countSingular': '1 tempat', + 'places.allPlanned': 'Semua tempat sudah direncanakan', + 'places.noneFound': 'Tidak ada tempat ditemukan', + 'places.editPlace': 'Edit Tempat', + 'places.formName': 'Nama', + 'places.formNamePlaceholder': 'mis. Menara Eiffel', + 'places.formDescription': 'Deskripsi', + 'places.formDescriptionPlaceholder': 'Deskripsi singkat...', + 'places.formAddress': 'Alamat', + 'places.formAddressPlaceholder': 'Jalan, Kota, Negara', + 'places.formLat': 'Lintang (mis. 48.8566)', + 'places.formLng': 'Bujur (mis. 2.3522)', + 'places.formCategory': 'Kategori', + 'places.noCategory': 'Tanpa Kategori', + 'places.categoryNamePlaceholder': 'Nama kategori', + 'places.formTime': 'Waktu', + 'places.startTime': 'Mulai', + 'places.endTime': 'Selesai', + 'places.endTimeBeforeStart': 'Waktu selesai lebih awal dari waktu mulai', + 'places.timeCollision': 'Waktu tumpang tindih dengan:', + 'places.formWebsite': 'Situs web', + 'places.formNotes': 'Catatan', + 'places.formNotesPlaceholder': 'Catatan pribadi...', + 'places.formReservation': 'Reservasi', + 'places.reservationNotesPlaceholder': + 'Catatan reservasi, nomor konfirmasi...', + 'places.mapsSearchPlaceholder': 'Cari tempat...', + 'places.mapsSearchError': 'Pencarian tempat gagal.', + 'places.loadingDetails': 'Memuat detail tempat…', + 'places.osmHint': + 'Menggunakan pencarian OpenStreetMap (tanpa foto, jam buka, atau penilaian). Tambahkan Google API key di pengaturan untuk detail lengkap.', + 'places.osmActive': + 'Pencarian via OpenStreetMap (tanpa foto, penilaian, atau jam buka). Tambahkan Google API key di Pengaturan untuk data yang lebih lengkap.', + 'places.categoryCreateError': 'Gagal membuat kategori', + 'places.nameRequired': 'Harap masukkan nama', + 'places.saveError': 'Gagal menyimpan', +}; +export default places; diff --git a/shared/src/i18n/id/planner.ts b/shared/src/i18n/id/planner.ts new file mode 100644 index 00000000..e5a37380 --- /dev/null +++ b/shared/src/i18n/id/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Tempat', + 'planner.bookings': 'Pemesanan', + 'planner.packingList': 'Daftar Bawaan', + 'planner.documents': 'Dokumen', + 'planner.dayPlan': 'Rencana Hari', + 'planner.reservations': 'Reservasi', + 'planner.minTwoPlaces': 'Dibutuhkan minimal 2 tempat dengan koordinat', + 'planner.noGeoPlaces': 'Tidak ada tempat dengan koordinat yang tersedia', + 'planner.routeCalculated': 'Rute berhasil dihitung', + 'planner.routeCalcFailed': 'Rute tidak dapat dihitung', + 'planner.routeError': 'Terjadi kesalahan saat menghitung rute', + 'planner.icsExportFailed': 'Ekspor ICS gagal', + 'planner.routeOptimized': 'Rute dioptimalkan', + 'planner.reservationUpdated': 'Reservasi diperbarui', + 'planner.reservationAdded': 'Reservasi ditambahkan', + 'planner.confirmDeleteReservation': 'Hapus reservasi?', + 'planner.reservationDeleted': 'Reservasi dihapus', + 'planner.days': 'Hari', + 'planner.allPlaces': 'Semua Tempat', + 'planner.totalPlaces': '{n} tempat total', + 'planner.noDaysPlanned': 'Belum ada hari yang direncanakan', + 'planner.editTrip': 'Edit perjalanan →', + 'planner.placeOne': '1 tempat', + 'planner.placeN': '{n} tempat', + 'planner.addNote': 'Tambah catatan', + 'planner.noEntries': 'Tidak ada entri untuk hari ini', + 'planner.addPlace': 'Tambah tempat/aktivitas', + 'planner.addPlaceShort': '+ Tambah tempat/aktivitas', + 'planner.resPending': 'Reservasi tertunda · ', + 'planner.resConfirmed': 'Reservasi dikonfirmasi · ', + 'planner.notePlaceholder': 'Catatan…', + 'planner.noteTimePlaceholder': 'Waktu (opsional)', + 'planner.noteExamplePlaceholder': + 'mis. S3 pukul 14:30 dari stasiun pusat, feri dari dermaga 7, istirahat makan siang…', + 'planner.totalCost': 'Total biaya', + 'planner.searchPlaces': 'Cari tempat…', + 'planner.allCategories': 'Semua Kategori', + 'planner.noPlacesFound': 'Tempat tidak ditemukan', + 'planner.addFirstPlace': 'Tambah tempat pertama', + 'planner.noReservations': 'Tidak ada reservasi', + 'planner.addFirstReservation': 'Tambah reservasi pertama', + 'planner.new': 'Baru', + 'planner.addToDay': '+ Hari', + 'planner.calculating': 'Menghitung…', + 'planner.route': 'Rute', + 'planner.optimize': 'Optimalkan', + 'planner.openGoogleMaps': 'Buka di Google Maps', + 'planner.selectDayHint': + 'Pilih hari dari daftar kiri untuk melihat rencana hari', + 'planner.noPlacesForDay': 'Belum ada tempat untuk hari ini', + 'planner.addPlacesLink': 'Tambah tempat →', + 'planner.minTotal': 'total min.', + 'planner.noReservation': 'Tidak ada reservasi', + 'planner.removeFromDay': 'Hapus dari hari', + 'planner.addToThisDay': 'Tambah ke hari ini', + 'planner.overview': 'Ikhtisar', + 'planner.noDays': 'Belum ada hari', + 'planner.editTripToAddDays': 'Edit perjalanan untuk menambah hari', + 'planner.dayCount': '{n} Hari', + 'planner.clickToUnlock': 'Klik untuk membuka kunci', + 'planner.keepPosition': 'Pertahankan posisi saat mengoptimalkan rute', + 'planner.dayDetails': 'Detail hari', + 'planner.dayN': 'Hari {n}', +}; +export default planner; diff --git a/shared/src/i18n/id/register.ts b/shared/src/i18n/id/register.ts new file mode 100644 index 00000000..130d1feb --- /dev/null +++ b/shared/src/i18n/id/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Kata sandi tidak cocok', + 'register.passwordTooShort': 'Kata sandi minimal 8 karakter', + 'register.failed': 'Pendaftaran gagal', + 'register.getStarted': 'Mulai Sekarang', + 'register.subtitle': 'Buat akun dan mulai rencanakan perjalanan impianmu.', + 'register.feature1': 'Rencana perjalanan tak terbatas', + 'register.feature2': 'Tampilan peta interaktif', + 'register.feature3': 'Kelola tempat dan kategori', + 'register.feature4': 'Lacak reservasi', + 'register.feature5': 'Buat daftar packing', + 'register.feature6': 'Simpan foto dan file', + 'register.createAccount': 'Buat Akun', + 'register.startPlanning': 'Mulai rencanakan perjalananmu', + 'register.minChars': 'Min. 6 karakter', + 'register.confirmPassword': 'Konfirmasi Kata Sandi', + 'register.repeatPassword': 'Ulangi kata sandi', + 'register.registering': 'Mendaftar...', + 'register.register': 'Daftar', + 'register.hasAccount': 'Sudah punya akun?', + 'register.signIn': 'Masuk', +}; +export default register; diff --git a/shared/src/i18n/id/reservations.ts b/shared/src/i18n/id/reservations.ts new file mode 100644 index 00000000..2279e3bc --- /dev/null +++ b/shared/src/i18n/id/reservations.ts @@ -0,0 +1,118 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Pemesanan', + 'reservations.empty': 'Belum ada reservasi', + 'reservations.emptyHint': + 'Tambahkan reservasi untuk penerbangan, hotel, dan lainnya', + 'reservations.add': 'Tambah Reservasi', + 'reservations.addManual': 'Pemesanan Manual', + 'reservations.placeHint': + 'Tips: Reservasi paling baik dibuat langsung dari sebuah tempat agar terhubung dengan rencana harianmu.', + 'reservations.confirmed': 'Dikonfirmasi', + 'reservations.pending': 'Tertunda', + 'reservations.summary': '{confirmed} dikonfirmasi, {pending} tertunda', + 'reservations.fromPlan': 'Dari Rencana', + 'reservations.showFiles': 'Tampilkan File', + 'reservations.editTitle': 'Edit Reservasi', + 'reservations.status': 'Status', + 'reservations.datetime': 'Tanggal & Waktu', + 'reservations.startTime': 'Waktu mulai', + 'reservations.endTime': 'Waktu selesai', + 'reservations.date': 'Tanggal', + 'reservations.time': 'Waktu', + 'reservations.timeAlt': 'Waktu (alternatif, mis. 19:30)', + 'reservations.notes': 'Catatan', + 'reservations.notesPlaceholder': 'Catatan tambahan...', + 'reservations.meta.airline': 'Maskapai', + 'reservations.meta.flightNumber': 'No. Penerbangan', + 'reservations.meta.from': 'Dari', + 'reservations.meta.to': 'Ke', + 'reservations.needsReview': 'Tinjau', + 'reservations.needsReviewHint': + 'Bandara tidak dapat dicocokkan otomatis — konfirmasi lokasi.', + 'reservations.searchLocation': 'Cari stasiun, pelabuhan, alamat...', + 'reservations.meta.trainNumber': 'No. Kereta', + 'reservations.meta.platform': 'Peron', + 'reservations.meta.seat': 'Kursi', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in sampai', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Akomodasi', + 'reservations.meta.pickAccommodation': 'Hubungkan ke akomodasi', + 'reservations.meta.noAccommodation': 'Tidak ada', + 'reservations.meta.hotelPlace': 'Akomodasi', + 'reservations.meta.pickHotel': 'Pilih akomodasi', + 'reservations.meta.fromDay': 'Dari', + 'reservations.meta.toDay': 'Sampai', + 'reservations.meta.selectDay': 'Pilih hari', + 'reservations.type.flight': 'Penerbangan', + 'reservations.type.hotel': 'Akomodasi', + 'reservations.type.restaurant': 'Restoran', + 'reservations.type.train': 'Kereta', + 'reservations.type.car': 'Mobil', + 'reservations.type.cruise': 'Kapal Pesiar', + 'reservations.type.event': 'Acara', + 'reservations.type.tour': 'Tur', + 'reservations.type.other': 'Lainnya', + 'reservations.confirm.delete': 'Yakin ingin menghapus reservasi "{name}"?', + 'reservations.confirm.deleteTitle': 'Hapus pemesanan?', + 'reservations.confirm.deleteBody': '"{name}" akan dihapus permanen.', + 'reservations.toast.updated': 'Reservasi diperbarui', + 'reservations.toast.removed': 'Reservasi dihapus', + 'reservations.toast.fileUploaded': 'File diunggah', + 'reservations.toast.uploadError': 'Gagal mengunggah', + 'reservations.newTitle': 'Reservasi Baru', + 'reservations.bookingType': 'Jenis Pemesanan', + 'reservations.titleLabel': 'Judul', + 'reservations.titlePlaceholder': 'mis. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Lokasi / Alamat', + 'reservations.locationPlaceholder': 'Alamat, Bandara, Hotel...', + 'reservations.confirmationCode': 'Kode Pemesanan', + 'reservations.confirmationPlaceholder': 'mis. ABC12345', + 'reservations.day': 'Hari', + 'reservations.noDay': 'Tanpa Hari', + 'reservations.place': 'Tempat', + 'reservations.noPlace': 'Tanpa Tempat', + 'reservations.pendingSave': 'akan disimpan…', + 'reservations.uploading': 'Mengunggah...', + 'reservations.attachFile': 'Lampirkan file', + 'reservations.linkExisting': 'Hubungkan file yang ada', + 'reservations.toast.saveError': 'Gagal menyimpan', + 'reservations.toast.updateError': 'Gagal memperbarui', + 'reservations.toast.deleteError': 'Gagal menghapus', + 'reservations.confirm.remove': 'Hapus reservasi untuk "{name}"?', + 'reservations.linkAssignment': 'Hubungkan ke jadwal harian', + 'reservations.pickAssignment': 'Pilih jadwal dari rencanamu...', + 'reservations.noAssignment': 'Tanpa tautan (mandiri)', + 'reservations.price': 'Harga', + 'reservations.budgetCategory': 'Kategori anggaran', + 'reservations.budgetCategoryPlaceholder': 'mis. Transportasi, Akomodasi', + 'reservations.budgetCategoryAuto': 'Otomatis (dari jenis pemesanan)', + 'reservations.budgetHint': + 'Entri anggaran akan dibuat otomatis saat menyimpan.', + 'reservations.departureDate': 'Keberangkatan', + 'reservations.arrivalDate': 'Kedatangan', + 'reservations.departureTime': 'Waktu berangkat', + 'reservations.arrivalTime': 'Waktu tiba', + 'reservations.pickupDate': 'Penjemputan', + 'reservations.returnDate': 'Pengembalian', + 'reservations.pickupTime': 'Waktu jemput', + 'reservations.returnTime': 'Waktu kembali', + 'reservations.endDate': 'Tanggal selesai', + 'reservations.meta.departureTimezone': 'TZ Berangkat', + 'reservations.meta.arrivalTimezone': 'TZ Tiba', + 'reservations.span.departure': 'Keberangkatan', + 'reservations.span.arrival': 'Kedatangan', + 'reservations.span.inTransit': 'Dalam perjalanan', + 'reservations.span.pickup': 'Penjemputan', + 'reservations.span.return': 'Pengembalian', + 'reservations.span.active': 'Aktif', + 'reservations.span.start': 'Mulai', + 'reservations.span.end': 'Selesai', + 'reservations.span.ongoing': 'Berlangsung', + 'reservations.validation.endBeforeStart': + 'Tanggal/waktu selesai harus setelah tanggal/waktu mulai', + 'reservations.addBooking': 'Tambah pemesanan', +}; +export default reservations; diff --git a/shared/src/i18n/id/settings.ts b/shared/src/i18n/id/settings.ts new file mode 100644 index 00000000..91ccc6b1 --- /dev/null +++ b/shared/src/i18n/id/settings.ts @@ -0,0 +1,299 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Pengaturan', + 'settings.subtitle': 'Atur pengaturan pribadimu', + 'settings.tabs.display': 'Tampilan', + 'settings.tabs.map': 'Peta', + 'settings.tabs.notifications': 'Notifikasi', + 'settings.tabs.integrations': 'Integrasi', + 'settings.tabs.account': 'Akun', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Tentang', + 'settings.map': 'Peta', + 'settings.mapTemplate': 'Template Peta', + 'settings.mapTemplatePlaceholder.select': 'Pilih template...', + 'settings.mapDefaultHint': 'Kosongkan untuk OpenStreetMap (default)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'Template URL untuk tile peta', + 'settings.mapProvider': 'Penyedia peta', + 'settings.mapProviderHint': + 'Berlaku untuk peta Trip Planner dan Journey. Atlas selalu menggunakan Leaflet.', + 'settings.mapLeafletSubtitle': 'Klasik 2D, tile raster apa pun', + 'settings.mapMapboxSubtitle': 'Tile vektor, bangunan 3D & medan', + 'settings.mapExperimental': 'Eksperimental', + 'settings.mapMapboxToken': 'Token akses Mapbox', + 'settings.mapMapboxTokenHint': 'Token publik (pk.*) dari', + 'settings.mapMapboxTokenLink': 'mapbox.com → Token akses', + 'settings.mapStyle': 'Gaya peta', + 'settings.mapStylePlaceholder': 'Pilih gaya Mapbox', + 'settings.mapStyleHint': 'Preset atau URL mapbox://styles/USER/ID milikmu', + 'settings.map3dBuildings': 'Bangunan 3D & medan', + 'settings.map3dHint': + 'Kemiringan + ekstrusi bangunan 3D nyata — bekerja di semua gaya, termasuk satelit.', + 'settings.mapHighQuality': 'Mode kualitas tinggi', + 'settings.mapHighQualityHint': + 'Antialiasing + proyeksi globe untuk tepi yang lebih tajam dan tampilan dunia realistis.', + 'settings.mapHighQualityWarning': + 'Dapat memengaruhi performa pada perangkat kelas bawah.', + 'settings.mapTipLabel': 'Tip:', + 'settings.mapTip': + 'Klik kanan dan seret untuk memutar/memiringkan peta. Klik tengah untuk menambah tempat (klik kanan untuk rotasi).', + 'settings.latitude': 'Lintang', + 'settings.longitude': 'Bujur', + 'settings.saveMap': 'Simpan Peta', + 'settings.apiKeys': 'API Keys', + 'settings.mapsKey': 'Google Maps API Key', + 'settings.mapsKeyHint': + 'Untuk pencarian tempat. Memerlukan Places API (New). Dapatkan di console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API Key', + 'settings.weatherKeyHint': + 'Untuk data cuaca. Gratis di openweathermap.org/api', + 'settings.keyPlaceholder': 'Masukkan key...', + 'settings.configured': 'Sudah dikonfigurasi', + 'settings.saveKeys': 'Simpan Keys', + 'settings.display': 'Tampilan', + 'settings.colorMode': 'Mode Warna', + 'settings.light': 'Terang', + 'settings.dark': 'Gelap', + 'settings.auto': 'Otomatis', + 'settings.language': 'Bahasa', + 'settings.temperature': 'Satuan Suhu', + 'settings.timeFormat': 'Format Waktu', + 'settings.blurBookingCodes': 'Sembunyikan Kode Pemesanan', + 'settings.notifications': 'Notifikasi', + 'settings.notifyTripInvite': 'Undangan perjalanan', + 'settings.notifyBookingChange': 'Perubahan pemesanan', + 'settings.notifyTripReminder': 'Pengingat perjalanan', + 'settings.notifyTodoDue': 'Tugas jatuh tempo', + 'settings.notifyVacayInvite': 'Undangan Vacay fusion', + 'settings.notifyPhotosShared': 'Foto dibagikan (Immich)', + 'settings.notifyCollabMessage': 'Pesan chat (Collab)', + 'settings.notifyPackingTagged': 'Daftar bawaan: penugasan', + 'settings.notifyWebhook': 'Notifikasi webhook', + 'settings.notifyVersionAvailable': 'Versi baru tersedia', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + 'Belum ada saluran notifikasi yang dikonfigurasi. Minta admin untuk mengatur notifikasi email atau webhook.', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Masukkan URL webhook Discord, Slack, atau kustom untuk menerima notifikasi.', + 'settings.webhookUrl.saved': 'Webhook URL tersimpan', + 'settings.webhookUrl.test': 'Uji', + 'settings.webhookUrl.testSuccess': 'Test webhook berhasil dikirim', + 'settings.webhookUrl.testFailed': 'Test webhook gagal', + 'settings.ntfyUrl.topicLabel': 'Topik Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL Server Ntfy (opsional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Masukkan topik Ntfy Anda untuk menerima notifikasi push. Kosongkan bidang server untuk menggunakan default yang dikonfigurasi oleh admin Anda.', + 'settings.ntfyUrl.tokenLabel': 'Token Akses (opsional)', + 'settings.ntfyUrl.tokenHint': + 'Diperlukan untuk topik yang dilindungi kata sandi.', + 'settings.ntfyUrl.saved': 'Pengaturan Ntfy tersimpan', + 'settings.ntfyUrl.test': 'Uji', + 'settings.ntfyUrl.testSuccess': 'Notifikasi uji Ntfy berhasil dikirim', + 'settings.ntfyUrl.testFailed': 'Notifikasi uji Ntfy gagal', + 'settings.ntfyUrl.tokenCleared': 'Token akses dihapus', + 'settings.notificationsDisabled': + 'Notifikasi belum dikonfigurasi. Minta admin untuk mengaktifkan notifikasi email atau webhook.', + 'settings.notificationsActive': 'Saluran aktif', + 'settings.notificationsManagedByAdmin': + 'Acara notifikasi dikonfigurasi oleh administrator.', + 'settings.on': 'Aktif', + 'settings.off': 'Nonaktif', + 'settings.mcp.title': 'Konfigurasi MCP', + 'settings.mcp.endpoint': 'MCP Endpoint', + 'settings.mcp.clientConfig': 'Konfigurasi Client', + 'settings.mcp.clientConfigHint': + 'Ganti dengan API token dari daftar di bawah. Path ke npx mungkin perlu disesuaikan untuk sistemmu (mis. C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Ganti dan dengan kredensial dari klien OAuth 2.1 yang kamu buat di atas. mcp-remote akan membuka browser untuk menyelesaikan otorisasi pertama kali kamu terhubung. Path ke npx mungkin perlu disesuaikan untuk sistemmu (misalnya C:\\PROGRA~1\\nodejs\\npx.cmd di Windows).', + 'settings.mcp.copy': 'Salin', + 'settings.mcp.copied': 'Disalin!', + 'settings.mcp.apiTokens': 'API Tokens', + 'settings.mcp.createToken': 'Buat Token Baru', + 'settings.mcp.noTokens': + 'Belum ada token. Buat satu untuk menghubungkan MCP client.', + 'settings.mcp.tokenCreatedAt': 'Dibuat', + 'settings.mcp.tokenUsedAt': 'Digunakan', + 'settings.mcp.deleteTokenTitle': 'Hapus Token', + 'settings.mcp.deleteTokenMessage': + 'Token ini akan langsung berhenti bekerja. MCP client yang menggunakannya akan kehilangan akses.', + 'settings.mcp.modal.createTitle': 'Buat API Token', + 'settings.mcp.modal.tokenName': 'Nama Token', + 'settings.mcp.modal.tokenNamePlaceholder': + 'mis. Claude Desktop, Laptop kerja', + 'settings.mcp.modal.creating': 'Membuat…', + 'settings.mcp.modal.create': 'Buat Token', + 'settings.mcp.modal.createdTitle': 'Token Dibuat', + 'settings.mcp.modal.createdWarning': + 'Token ini hanya ditampilkan sekali. Salin dan simpan sekarang — tidak bisa dipulihkan.', + 'settings.mcp.modal.done': 'Selesai', + 'settings.mcp.toast.created': 'Token dibuat', + 'settings.mcp.toast.createError': 'Gagal membuat token', + 'settings.mcp.toast.deleted': 'Token dihapus', + 'settings.mcp.toast.deleteError': 'Gagal menghapus token', + 'settings.mcp.apiTokensDeprecated': + 'API Token sudah tidak digunakan dan akan dihapus di rilis mendatang. Gunakan OAuth 2.1 Client sebagai gantinya.', + 'settings.oauth.clients': 'Klien OAuth 2.1', + 'settings.oauth.clientsHint': + 'Daftarkan klien OAuth 2.1 agar aplikasi MCP pihak ketiga (Claude Web, Cursor, dll.) dapat terhubung tanpa token statis.', + 'settings.oauth.createClient': 'Klien Baru', + 'settings.oauth.noClients': 'Belum ada klien OAuth yang terdaftar.', + 'settings.oauth.clientId': 'ID Klien', + 'settings.oauth.clientSecret': 'Rahasia Klien', + 'settings.oauth.deleteClient': 'Hapus Klien', + 'settings.oauth.deleteClientMessage': + 'Klien ini dan semua sesi aktif akan dihapus permanen. Aplikasi yang menggunakannya akan langsung kehilangan akses.', + 'settings.oauth.rotateSecret': 'Putar Ulang Secret', + 'settings.oauth.rotateSecretMessage': + 'Secret klien baru akan dibuat dan semua sesi yang ada langsung dibatalkan. Perbarui aplikasimu sebelum menutup dialog ini.', + 'settings.oauth.rotateSecretConfirm': 'Putar Ulang', + 'settings.oauth.rotateSecretConfirming': 'Memutar ulang…', + 'settings.oauth.rotateSecretDoneTitle': 'Secret Baru Dibuat', + 'settings.oauth.rotateSecretDoneWarning': + 'Secret ini hanya ditampilkan sekali. Salin sekarang dan perbarui aplikasimu — semua sesi sebelumnya telah dibatalkan.', + 'settings.oauth.activeSessions': 'Sesi OAuth Aktif', + 'settings.oauth.sessionScopes': 'Cakupan', + 'settings.oauth.sessionExpires': 'Kedaluwarsa', + 'settings.oauth.revoke': 'Cabut', + 'settings.oauth.revokeSession': 'Cabut Sesi', + 'settings.oauth.revokeSessionMessage': + 'Ini akan segera mencabut akses untuk sesi OAuth ini.', + 'settings.oauth.modal.createTitle': 'Daftarkan OAuth Client', + 'settings.oauth.modal.presets': 'Preset cepat', + 'settings.oauth.modal.clientName': 'Nama Aplikasi', + 'settings.oauth.modal.clientNamePlaceholder': + 'mis. Claude Web, Aplikasi MCP Saya', + 'settings.oauth.modal.redirectUris': 'Redirect URI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://aplikasiku.com/callback\nhttps://aplikasiku.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Satu URI per baris. HTTPS wajib (localhost dikecualikan). Kecocokan tepat diberlakukan.', + 'settings.oauth.modal.scopes': 'Cakupan yang Diizinkan', + 'settings.oauth.modal.scopesHint': + 'list_trips dan get_trip_summary selalu tersedia — tidak perlu cakupan. Keduanya memungkinkan AI menemukan ID perjalanan yang diperlukan untuk menggunakan alat lainnya.', + 'settings.oauth.modal.selectAll': 'Pilih semua', + 'settings.oauth.modal.deselectAll': 'Batalkan semua', + 'settings.oauth.modal.creating': 'Mendaftarkan…', + 'settings.oauth.modal.create': 'Daftarkan Client', + 'settings.oauth.modal.createdTitle': 'Client Terdaftar', + 'settings.oauth.modal.createdWarning': + 'Client secret hanya ditampilkan sekali. Salin sekarang — tidak dapat dipulihkan.', + 'settings.oauth.toast.createError': 'Gagal mendaftarkan klien OAuth', + 'settings.oauth.toast.deleted': 'Klien OAuth dihapus', + 'settings.oauth.toast.deleteError': 'Gagal menghapus klien OAuth', + 'settings.oauth.toast.revoked': 'Sesi dicabut', + 'settings.oauth.toast.revokeError': 'Gagal mencabut sesi', + 'settings.oauth.toast.rotateError': 'Gagal memutar ulang client secret', + 'settings.oauth.modal.machineClient': 'Klien mesin (tanpa login browser)', + 'settings.oauth.modal.machineClientHint': + 'Menggunakan grant client_credentials — tidak perlu URI pengalihan. Token diterbitkan langsung melalui client_id + client_secret dan bertindak sebagai Anda dalam cakupan yang dipilih.', + 'settings.oauth.modal.machineClientUsage': + 'Dapatkan token: POST /oauth/token dengan grant_type=client_credentials, client_id, dan client_secret. Tanpa browser, tanpa refresh token.', + 'settings.oauth.badge.machine': 'mesin', + 'settings.account': 'Akun', + 'settings.about': 'Tentang', + 'settings.about.reportBug': 'Laporkan Bug', + 'settings.about.reportBugHint': 'Menemukan masalah? Beri tahu kami', + 'settings.about.featureRequest': 'Permintaan Fitur', + 'settings.about.featureRequestHint': 'Sarankan fitur baru', + 'settings.about.wikiHint': 'Dokumentasi & panduan', + 'settings.about.supporters.badge': 'Pendukung Bulanan', + 'settings.about.supporters.title': 'Rekan perjalanan untuk TREK', + 'settings.about.supporters.subtitle': + 'Saat kamu merencanakan rute berikutnya, orang-orang ini ikut merencanakan masa depan TREK. Kontribusi bulanan mereka langsung masuk ke pengembangan dan jam kerja nyata — supaya TREK tetap Open Source.', + 'settings.about.supporters.since': 'pendukung sejak {date}', + 'settings.about.supporters.tierEmpty': 'Jadilah yang pertama', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK adalah perencana perjalanan self-hosted yang membantu kamu mengatur perjalanan dari ide pertama hingga kenangan terakhir. Perencanaan harian, anggaran, daftar bawaan, foto dan masih banyak lagi — semua di satu tempat, di servermu sendiri.', + 'settings.about.madeWith': 'Dibuat dengan', + 'settings.about.madeBy': + 'oleh Maurice dan komunitas open-source yang terus berkembang.', + 'settings.username': 'Nama pengguna', + 'settings.email': 'Email', + 'settings.role': 'Peran', + 'settings.roleAdmin': 'Administrator', + 'settings.oidcLinked': 'Terhubung dengan', + 'settings.changePassword': 'Ganti Kata Sandi', + 'settings.currentPassword': 'Kata sandi saat ini', + 'settings.currentPasswordRequired': 'Kata sandi saat ini wajib diisi', + 'settings.newPassword': 'Kata sandi baru', + 'settings.confirmPassword': 'Konfirmasi kata sandi baru', + 'settings.updatePassword': 'Perbarui kata sandi', + 'settings.passwordRequired': + 'Masukkan kata sandi saat ini dan kata sandi baru', + 'settings.passwordTooShort': 'Kata sandi minimal 8 karakter', + 'settings.passwordMismatch': 'Kata sandi tidak cocok', + 'settings.passwordWeak': + 'Kata sandi harus mengandung huruf besar, huruf kecil, angka, dan karakter khusus', + 'settings.passwordChanged': 'Kata sandi berhasil diubah', + 'settings.mustChangePassword': + 'Kamu harus mengubah kata sandi sebelum melanjutkan. Atur kata sandi baru di bawah ini.', + 'settings.deleteAccount': 'Hapus akun', + 'settings.deleteAccountTitle': 'Hapus akunmu?', + 'settings.deleteAccountWarning': + 'Akunmu beserta semua perjalanan, tempat, dan file akan dihapus permanen. Tindakan ini tidak bisa dibatalkan.', + 'settings.deleteAccountConfirm': 'Hapus permanen', + 'settings.deleteBlockedTitle': 'Penghapusan tidak memungkinkan', + 'settings.deleteBlockedMessage': + 'Kamu satu-satunya administrator. Promosikan pengguna lain menjadi admin sebelum menghapus akunmu.', + 'settings.roleUser': 'Pengguna', + 'settings.saveProfile': 'Simpan Profil', + 'settings.toast.mapSaved': 'Pengaturan peta tersimpan', + 'settings.toast.keysSaved': 'API keys tersimpan', + 'settings.toast.displaySaved': 'Pengaturan tampilan tersimpan', + 'settings.toast.profileSaved': 'Profil tersimpan', + 'settings.uploadAvatar': 'Unggah Foto Profil', + 'settings.removeAvatar': 'Hapus Foto Profil', + 'settings.avatarUploaded': 'Foto profil diperbarui', + 'settings.avatarRemoved': 'Foto profil dihapus', + 'settings.avatarError': 'Gagal mengunggah', + 'settings.mfa.title': 'Autentikasi dua faktor (2FA)', + 'settings.mfa.description': + 'Menambahkan langkah kedua saat masuk dengan email dan kata sandi. Gunakan aplikasi autentikator (Google Authenticator, Authy, dll.).', + 'settings.mfa.requiredByPolicy': + 'Administrator mengharuskan autentikasi dua faktor. Atur aplikasi autentikator di bawah ini sebelum melanjutkan.', + 'settings.mfa.backupTitle': 'Kode cadangan', + 'settings.mfa.backupDescription': + 'Gunakan kode cadangan sekali pakai ini jika kamu kehilangan akses ke aplikasi autentikator.', + 'settings.mfa.backupWarning': + 'Simpan kode ini sekarang. Setiap kode hanya bisa digunakan sekali.', + 'settings.mfa.backupCopy': 'Salin kode', + 'settings.mfa.backupDownload': 'Unduh TXT', + 'settings.mfa.backupPrint': 'Cetak / PDF', + 'settings.mfa.backupCopied': 'Kode cadangan disalin', + 'settings.mfa.enabled': '2FA aktif di akunmu.', + 'settings.mfa.disabled': '2FA belum diaktifkan.', + 'settings.mfa.setup': 'Atur autentikator', + 'settings.mfa.scanQr': + 'Pindai kode QR ini dengan aplikasimu, atau masukkan secret secara manual.', + 'settings.mfa.secretLabel': 'Kunci secret (entri manual)', + 'settings.mfa.codePlaceholder': 'Kode 6 digit', + 'settings.mfa.enable': 'Aktifkan 2FA', + 'settings.mfa.cancelSetup': 'Batal', + 'settings.mfa.disableTitle': 'Nonaktifkan 2FA', + 'settings.mfa.disableHint': + 'Masukkan kata sandi akun dan kode terkini dari aplikasi autentikatormu.', + 'settings.mfa.disable': 'Nonaktifkan 2FA', + 'settings.mfa.toastEnabled': 'Autentikasi dua faktor diaktifkan', + 'settings.mfa.toastDisabled': 'Autentikasi dua faktor dinonaktifkan', + 'settings.mfa.demoBlocked': 'Tidak tersedia dalam mode demo', + 'settings.bookingLabels': 'Label rute pemesanan', + 'settings.bookingLabelsHint': + 'Menampilkan nama stasiun / bandara di peta. Jika mati, hanya ikon ditampilkan.', +}; +export default settings; diff --git a/shared/src/i18n/id/share.ts b/shared/src/i18n/id/share.ts new file mode 100644 index 00000000..7a8cae6d --- /dev/null +++ b/shared/src/i18n/id/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Tautan Publik', + 'share.linkHint': + 'Buat tautan yang bisa digunakan siapa saja untuk melihat perjalanan ini tanpa masuk. Hanya baca — tidak bisa diedit.', + 'share.createLink': 'Buat tautan', + 'share.deleteLink': 'Hapus tautan', + 'share.createError': 'Gagal membuat tautan', + 'share.permMap': 'Peta & Rencana', + 'share.permBookings': 'Pemesanan', + 'share.permPacking': 'Bawaan', + 'share.permBudget': 'Anggaran', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/id/shared.ts b/shared/src/i18n/id/shared.ts new file mode 100644 index 00000000..9762ba7b --- /dev/null +++ b/shared/src/i18n/id/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Tautan kedaluwarsa atau tidak valid', + 'shared.expiredHint': 'Tautan perjalanan bersama ini tidak lagi aktif.', + 'shared.readOnly': 'Tampilan bersama — hanya baca', + 'shared.tabPlan': 'Rencana', + 'shared.tabBookings': 'Pemesanan', + 'shared.tabPacking': 'Bawaan', + 'shared.tabBudget': 'Anggaran', + 'shared.tabChat': 'Chat', + 'shared.days': 'hari', + 'shared.places': 'tempat', + 'shared.other': 'Lainnya', + 'shared.totalBudget': 'Total Anggaran', + 'shared.messages': 'pesan', + 'shared.sharedVia': 'Dibagikan via', + 'shared.confirmed': 'Dikonfirmasi', + 'shared.pending': 'Menunggu', +}; +export default shared; diff --git a/shared/src/i18n/id/stats.ts b/shared/src/i18n/id/stats.ts new file mode 100644 index 00000000..e5368e90 --- /dev/null +++ b/shared/src/i18n/id/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Negara', + 'stats.cities': 'Kota', + 'stats.trips': 'Perjalanan', + 'stats.places': 'Tempat', + 'stats.worldProgress': 'Progres Dunia', + 'stats.visited': 'dikunjungi', + 'stats.remaining': 'tersisa', + 'stats.visitedCountries': 'Negara yang Dikunjungi', +}; +export default stats; diff --git a/shared/src/i18n/id/system_notice.ts b/shared/src/i18n/id/system_notice.ts new file mode 100644 index 00000000..b0d7e564 --- /dev/null +++ b/shared/src/i18n/id/system_notice.ts @@ -0,0 +1,61 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Selamat datang di TREK', + 'system_notice.welcome_v1.body': + 'Perencana perjalanan lengkap Anda. Buat itinerari, bagikan perjalanan dengan teman, dan tetap terorganisir — online maupun offline.', + 'system_notice.welcome_v1.cta_label': 'Rencanakan perjalanan', + 'system_notice.welcome_v1.hero_alt': + 'Destinasi wisata indah dengan antarmuka TREK', + 'system_notice.welcome_v1.highlight_plan': + 'Itinerari harian untuk setiap perjalanan', + 'system_notice.welcome_v1.highlight_share': + 'Berkolaborasi dengan teman perjalanan', + 'system_notice.welcome_v1.highlight_offline': 'Bekerja offline di ponsel', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Pemberitahuan sebelumnya', + 'system_notice.pager.next': 'Pemberitahuan berikutnya', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Pergi ke pemberitahuan {n}', + 'system_notice.pager.position': 'Pemberitahuan {current} dari {total}', + 'system_notice.v3_photos.title': 'Foto dipindahkan di 3.0', + 'system_notice.v3_photos.body': + '**Foto** di Perencana Perjalanan telah dihapus. Foto Anda aman — TREK tidak pernah mengubah perpustakaan Immich atau Synology Anda.\n\nFoto kini ada di addon **Journey**. Journey bersifat opsional — jika belum tersedia, minta admin untuk mengaktifkannya di Admin → Addon.', + 'system_notice.v3_journey.title': 'Kenali Journey — jurnal perjalanan', + 'system_notice.v3_journey.body': + 'Dokumentasikan perjalanan Anda sebagai cerita hidup dengan linimasa, galeri foto, dan peta interaktif.', + 'system_notice.v3_journey.cta_label': 'Buka Journey', + 'system_notice.v3_journey.highlight_timeline': 'Linimasa & galeri', + 'system_notice.v3_journey.highlight_photos': + 'Impor dari Immich atau Synology', + 'system_notice.v3_journey.highlight_share': + 'Bagikan secara publik — tanpa login', + 'system_notice.v3_journey.highlight_export': 'Ekspor sebagai buku foto PDF', + 'system_notice.v3_features.title': 'Sorotan lain di 3.0', + 'system_notice.v3_features.body': 'Beberapa pembaruan lain dalam rilis ini.', + 'system_notice.v3_features.highlight_dashboard': + 'Desain ulang dashboard mobile-first', + 'system_notice.v3_features.highlight_offline': + 'Mode offline penuh sebagai PWA', + 'system_notice.v3_features.highlight_search': + 'Pelengkapan otomatis tempat secara real-time', + 'system_notice.v3_features.highlight_import': + 'Impor tempat dari file KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: pembaruan OAuth 2.1', + 'system_notice.v3_mcp.body': + 'Integrasi MCP telah sepenuhnya diperbarui. OAuth 2.1 kini menjadi metode autentikasi yang direkomendasikan. Token statis (trek_…) sudah usang dan akan dihapus pada versi mendatang.', + 'system_notice.v3_mcp.highlight_oauth': + 'OAuth 2.1 direkomendasikan (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 cakupan izin yang terperinci', + 'system_notice.v3_mcp.highlight_deprecated': 'Token statis trek_ sudah usang', + 'system_notice.v3_mcp.highlight_tools': 'Perangkat dan prompt yang diperluas', + 'system_notice.v3_thankyou.title': 'Catatan pribadi dari saya', + 'system_notice.v3_thankyou.body': + 'Sebelum kamu lanjut — saya ingin berhenti sejenak.\n\nTREK dimulai sebagai proyek sampingan yang saya buat untuk perjalanan saya sendiri. Saya tidak pernah membayangkan ia akan tumbuh menjadi sesuatu yang dipercaya oleh 4.000 dari kalian untuk merencanakan petualangan. Setiap bintang, setiap issue, setiap permintaan fitur — saya membaca semuanya, dan itulah yang membuat saya terus bertahan di malam-malam larut antara pekerjaan penuh waktu dan kuliah.\n\nSaya ingin kalian tahu: TREK akan selalu open source, selalu self-hosted, selalu milik kalian. Tanpa pelacakan, tanpa langganan, tanpa syarat tersembunyi. Hanya sebuah alat yang dibuat oleh seseorang yang mencintai traveling sama seperti kalian.\n\nTerima kasih khusus untuk [jubnl](https://github.com/jubnl) — kamu telah menjadi kolaborator yang luar biasa. Begitu banyak hal yang membuat versi 3.0 hebat memiliki jejakmu. Terima kasih telah percaya pada proyek ini ketika masih kasar.\n\nDan untuk setiap dari kalian yang melaporkan bug, menerjemahkan string, membagikan TREK kepada teman, atau sekadar menggunakannya untuk merencanakan perjalanan — **terima kasih**. Kalianlah alasan semua ini ada.\n\nUntuk lebih banyak petualangan bersama.\n\n— Maurice\n\n---\n\n[Bergabunglah dengan komunitas di Discord](https://discord.gg/7Q6M6jDwzf)\n\nJika TREK membuat perjalananmu lebih baik, [secangkir kopi kecil](https://ko-fi.com/mauriceboe) selalu membantu menjaga lampu tetap menyala.', + 'system_notice.v3014_whitespace_collision.title': + 'Tindakan diperlukan: konflik akun pengguna', + 'system_notice.v3014_whitespace_collision.body': + 'Pembaruan 3.0.14 mendeteksi satu atau lebih konflik nama pengguna atau email yang disebabkan oleh spasi di awal atau akhir nilai yang tersimpan. Akun yang terpengaruh telah diganti nama secara otomatis. Periksa log server untuk baris yang dimulai dengan **[migration] WHITESPACE COLLISION** guna mengidentifikasi akun mana yang perlu ditinjau.', +}; +export default system_notice; diff --git a/shared/src/i18n/id/todo.ts b/shared/src/i18n/id/todo.ts new file mode 100644 index 00000000..0304d741 --- /dev/null +++ b/shared/src/i18n/id/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Daftar Perlengkapan', + 'todo.subtab.todo': 'Tugas', + 'todo.completed': 'selesai', + 'todo.filter.all': 'Semua', + 'todo.filter.open': 'Terbuka', + 'todo.filter.done': 'Selesai', + 'todo.uncategorized': 'Tanpa kategori', + 'todo.namePlaceholder': 'Nama tugas', + 'todo.descriptionPlaceholder': 'Deskripsi (opsional)', + 'todo.unassigned': 'Belum ditugaskan', + 'todo.noCategory': 'Tanpa kategori', + 'todo.hasDescription': 'Ada deskripsi', + 'todo.addItem': 'Tugas baru', + 'todo.sidebar.sortBy': 'Urutkan', + 'todo.priority': 'Prioritas', + 'todo.newCategoryLabel': 'baru', + 'todo.newCategory': 'Nama kategori', + 'todo.addCategory': 'Tambah kategori', + 'todo.newItem': 'Tugas baru', + 'todo.empty': 'Belum ada tugas. Tambah tugas untuk memulai!', + 'todo.filter.my': 'Tugasku', + 'todo.filter.overdue': 'Terlambat', + 'todo.sidebar.tasks': 'Tugas', + 'todo.sidebar.categories': 'Kategori', + 'todo.detail.title': 'Tugas', + 'todo.detail.description': 'Deskripsi', + 'todo.detail.category': 'Kategori', + 'todo.detail.dueDate': 'Tenggat waktu', + 'todo.detail.assignedTo': 'Ditugaskan ke', + 'todo.detail.delete': 'Hapus', + 'todo.detail.save': 'Simpan perubahan', + 'todo.sortByPrio': 'Prioritas', + 'todo.detail.priority': 'Prioritas', + 'todo.detail.noPriority': 'Tidak ada', + 'todo.detail.create': 'Buat tugas', +}; +export default todo; diff --git a/shared/src/i18n/id/transport.ts b/shared/src/i18n/id/transport.ts new file mode 100644 index 00000000..55eb314d --- /dev/null +++ b/shared/src/i18n/id/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Tambah transportasi', + 'transport.modalTitle.create': 'Tambah transportasi', + 'transport.modalTitle.edit': 'Edit transportasi', + 'transport.title': 'Transportasi', + 'transport.addManual': 'Transportasi Manual', +}; +export default transport; diff --git a/shared/src/i18n/id/trip.ts b/shared/src/i18n/id/trip.ts new file mode 100644 index 00000000..a42a48a1 --- /dev/null +++ b/shared/src/i18n/id/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Rencana', + 'trip.tabs.transports': 'Transportasi', + 'trip.tabs.reservations': 'Pemesanan', + 'trip.tabs.reservationsShort': 'Pesan', + 'trip.tabs.packing': 'Daftar Perlengkapan', + 'trip.tabs.packingShort': 'Perlengkapan', + 'trip.tabs.lists': 'Daftar', + 'trip.tabs.listsShort': 'Daftar', + 'trip.tabs.budget': 'Anggaran', + 'trip.tabs.files': 'File', + 'trip.loading': 'Memuat perjalanan...', + 'trip.loadingPhotos': 'Memuat foto tempat...', + 'trip.mobilePlan': 'Rencana', + 'trip.mobilePlaces': 'Tempat', + 'trip.toast.placeUpdated': 'Tempat diperbarui', + 'trip.toast.placeAdded': 'Tempat ditambahkan', + 'trip.toast.placeDeleted': 'Tempat dihapus', + 'trip.toast.selectDay': 'Pilih hari terlebih dahulu', + 'trip.toast.assignedToDay': 'Tempat ditambahkan ke hari', + 'trip.toast.reorderError': 'Gagal mengurutkan ulang', + 'trip.toast.reservationUpdated': 'Reservasi diperbarui', + 'trip.toast.reservationAdded': 'Reservasi ditambahkan', + 'trip.toast.deleted': 'Dihapus', + 'trip.confirm.deletePlace': 'Apakah kamu yakin ingin menghapus tempat ini?', + 'trip.confirm.deletePlaces': 'Hapus {count} tempat?', + 'trip.toast.placesDeleted': '{count} tempat dihapus', +}; +export default trip; diff --git a/shared/src/i18n/id/trips.ts b/shared/src/i18n/id/trips.ts new file mode 100644 index 00000000..760d8870 --- /dev/null +++ b/shared/src/i18n/id/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} dihapus', + 'trips.memberRemoveError': 'Gagal menghapus', + 'trips.memberAdded': '{username} ditambahkan', + 'trips.memberAddError': 'Gagal menambahkan', + 'trips.reminder': 'Pengingat', + 'trips.reminderNone': 'Tidak ada', + 'trips.reminderDay': 'hari', + 'trips.reminderDays': 'hari', + 'trips.reminderCustom': 'Kustom', + 'trips.reminderDaysBefore': 'hari sebelum keberangkatan', + 'trips.reminderDisabledHint': + 'Pengingat perjalanan dinonaktifkan. Aktifkan di Admin > Pengaturan > Notifikasi.', +}; +export default trips; diff --git a/shared/src/i18n/id/undo.ts b/shared/src/i18n/id/undo.ts new file mode 100644 index 00000000..98fdf305 --- /dev/null +++ b/shared/src/i18n/id/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Batalkan', + 'undo.tooltip': 'Batalkan: {action}', + 'undo.assignPlace': 'Tempat ditambahkan ke hari', + 'undo.removeAssignment': 'Tempat dihapus dari hari', + 'undo.reorder': 'Tempat diurutkan ulang', + 'undo.optimize': 'Rute dioptimalkan', + 'undo.deletePlace': 'Tempat dihapus', + 'undo.deletePlaces': 'Tempat dihapus', + 'undo.moveDay': 'Tempat dipindah ke hari lain', + 'undo.lock': 'Kunci tempat diubah', + 'undo.importGpx': 'Impor GPX', + 'undo.importKeyholeMarkup': 'Impor KMZ/KML', + 'undo.importGoogleList': 'Impor Google Maps', + 'undo.importNaverList': 'Impor Naver Maps', + 'undo.addPlace': 'Tempat ditambahkan', + 'undo.done': 'Dibatalkan: {action}', +}; +export default undo; diff --git a/shared/src/i18n/id/vacay.ts b/shared/src/i18n/id/vacay.ts new file mode 100644 index 00000000..a189efdb --- /dev/null +++ b/shared/src/i18n/id/vacay.ts @@ -0,0 +1,106 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Rencanakan dan kelola hari cuti', + 'vacay.settings': 'Pengaturan', + 'vacay.year': 'Tahun', + 'vacay.addYear': 'Tambah tahun berikutnya', + 'vacay.addPrevYear': 'Tambah tahun sebelumnya', + 'vacay.removeYear': 'Hapus tahun', + 'vacay.removeYearConfirm': 'Hapus {year}?', + 'vacay.removeYearHint': + 'Semua entri cuti dan hari libur perusahaan untuk tahun ini akan dihapus secara permanen.', + 'vacay.remove': 'Hapus', + 'vacay.persons': 'Orang', + 'vacay.noPersons': 'Belum ada orang ditambahkan', + 'vacay.addPerson': 'Tambah Orang', + 'vacay.editPerson': 'Edit Orang', + 'vacay.removePerson': 'Hapus Orang', + 'vacay.removePersonConfirm': 'Hapus {name}?', + 'vacay.removePersonHint': + 'Semua entri cuti untuk orang ini akan dihapus secara permanen.', + 'vacay.personName': 'Nama', + 'vacay.personNamePlaceholder': 'Masukkan nama', + 'vacay.color': 'Warna', + 'vacay.add': 'Tambah', + 'vacay.legend': 'Keterangan', + 'vacay.publicHoliday': 'Hari Libur Nasional', + 'vacay.companyHoliday': 'Hari Libur Perusahaan', + 'vacay.weekend': 'Akhir Pekan', + 'vacay.modeVacation': 'Cuti', + 'vacay.modeCompany': 'Hari Libur Perusahaan', + 'vacay.entitlement': 'Jatah Cuti', + 'vacay.entitlementDays': 'Hari', + 'vacay.used': 'Terpakai', + 'vacay.remaining': 'Sisa', + 'vacay.carriedOver': 'dari {year}', + 'vacay.blockWeekends': 'Blokir Akhir Pekan', + 'vacay.blockWeekendsHint': 'Cegah entri cuti pada hari akhir pekan', + 'vacay.weekendDays': 'Hari akhir pekan', + 'vacay.mon': 'Sen', + 'vacay.tue': 'Sel', + 'vacay.wed': 'Rab', + 'vacay.thu': 'Kam', + 'vacay.fri': 'Jum', + 'vacay.sat': 'Sab', + 'vacay.sun': 'Min', + 'vacay.publicHolidays': 'Hari Libur Nasional', + 'vacay.publicHolidaysHint': 'Tandai hari libur nasional di kalender', + 'vacay.selectCountry': 'Pilih negara', + 'vacay.selectRegion': 'Pilih wilayah (opsional)', + 'vacay.addCalendar': 'Tambah kalender', + 'vacay.calendarLabel': 'Label (opsional)', + 'vacay.calendarColor': 'Warna', + 'vacay.noCalendars': 'Belum ada kalender hari libur ditambahkan', + 'vacay.companyHolidays': 'Hari Libur Perusahaan', + 'vacay.companyHolidaysHint': + 'Izinkan penandaan hari libur seluruh perusahaan', + 'vacay.companyHolidaysNoDeduct': + 'Hari libur perusahaan tidak dipotong dari jatah cuti.', + 'vacay.weekStart': 'Awal minggu', + 'vacay.weekStartHint': + 'Pilih apakah minggu kalender dimulai pada hari Senin atau Minggu', + 'vacay.carryOver': 'Carry Over Cuti', + 'vacay.carryOverHint': + 'Otomatis pindahkan sisa hari cuti ke tahun berikutnya', + 'vacay.sharing': 'Berbagi', + 'vacay.sharingHint': 'Bagikan rencana cuti kamu dengan pengguna TREK lainnya', + 'vacay.owner': 'Pemilik', + 'vacay.shareEmailPlaceholder': 'Email pengguna TREK', + 'vacay.shareSuccess': 'Rencana berhasil dibagikan', + 'vacay.shareError': 'Gagal membagikan rencana', + 'vacay.dissolve': 'Pisahkan Gabungan', + 'vacay.dissolveHint': + 'Pisahkan kalender kembali. Entri kamu akan tetap disimpan.', + 'vacay.dissolveAction': 'Pisahkan', + 'vacay.dissolved': 'Kalender dipisahkan', + 'vacay.fusedWith': 'Digabung dengan', + 'vacay.you': 'kamu', + 'vacay.noData': 'Tidak ada data', + 'vacay.changeColor': 'Ganti warna', + 'vacay.inviteUser': 'Undang Pengguna', + 'vacay.inviteHint': + 'Undang pengguna TREK lain untuk berbagi kalender cuti bersama.', + 'vacay.selectUser': 'Pilih pengguna', + 'vacay.sendInvite': 'Kirim Undangan', + 'vacay.inviteSent': 'Undangan terkirim', + 'vacay.inviteError': 'Gagal mengirim undangan', + 'vacay.pending': 'menunggu', + 'vacay.noUsersAvailable': 'Tidak ada pengguna tersedia', + 'vacay.accept': 'Terima', + 'vacay.decline': 'Tolak', + 'vacay.acceptFusion': 'Terima & Gabung', + 'vacay.inviteTitle': 'Permintaan Penggabungan', + 'vacay.inviteWantsToFuse': 'ingin berbagi kalender cuti bersamamu.', + 'vacay.fuseInfo1': + 'Kalian berdua akan melihat semua entri cuti dalam satu kalender bersama.', + 'vacay.fuseInfo2': + 'Kedua pihak dapat membuat dan mengedit entri satu sama lain.', + 'vacay.fuseInfo3': + 'Kedua pihak dapat menghapus entri dan mengubah jatah cuti.', + 'vacay.fuseInfo4': + 'Pengaturan seperti hari libur nasional dan hari libur perusahaan dibagikan bersama.', + 'vacay.fuseInfo5': + 'Penggabungan dapat dipisahkan kapan saja oleh salah satu pihak. Entri kamu akan tetap disimpan.', +}; +export default vacay; diff --git a/shared/src/i18n/index.ts b/shared/src/i18n/index.ts new file mode 100644 index 00000000..1a3d2f23 --- /dev/null +++ b/shared/src/i18n/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './languages'; diff --git a/shared/src/i18n/it/admin.ts b/shared/src/i18n/it/admin.ts new file mode 100644 index 00000000..f804ed30 --- /dev/null +++ b/shared/src/i18n/it/admin.ts @@ -0,0 +1,369 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notifiche', + 'admin.notifications.hint': + 'Scegli un canale di notifica. Solo uno può essere attivo alla volta.', + 'admin.notifications.none': 'Disattivato', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Salva impostazioni notifiche', + 'admin.notifications.saved': 'Impostazioni notifiche salvate', + 'admin.notifications.testWebhook': 'Invia webhook di test', + 'admin.notifications.testWebhookSuccess': + 'Webhook di test inviato con successo', + 'admin.notifications.testWebhookFailed': 'Invio webhook di test fallito', + 'admin.smtp.title': 'Email e notifiche', + 'admin.smtp.hint': + "Configurazione SMTP per l'invio delle notifiche via e-mail.", + 'admin.smtp.testButton': 'Invia email di prova', + 'admin.webhook.hint': + 'Invia notifiche a un webhook esterno (Discord, Slack, ecc.).', + 'admin.smtp.testSuccess': 'Email di prova inviata con successo', + 'admin.smtp.testFailed': 'Invio email di prova fallito', + 'admin.title': 'Amministrazione', + 'admin.subtitle': 'Gestione utenti e impostazioni di sistema', + 'admin.tabs.users': 'Utenti', + 'admin.tabs.categories': 'Categorie', + 'admin.tabs.backup': 'Backup', + 'admin.stats.users': 'Utenti', + 'admin.stats.trips': 'Viaggi', + 'admin.stats.places': 'Luoghi', + 'admin.stats.photos': 'Foto', + 'admin.stats.files': 'File', + 'admin.table.user': 'Utente', + 'admin.table.email': 'Email', + 'admin.table.role': 'Ruolo', + 'admin.table.created': 'Creato', + 'admin.table.lastLogin': 'Ultimo Accesso', + 'admin.table.actions': 'Azioni', + 'admin.you': '(Tu)', + 'admin.editUser': 'Modifica Utente', + 'admin.newPassword': 'Nuova Password', + 'admin.newPasswordHint': 'Lascia vuoto per mantenere la password attuale', + 'admin.deleteUser': + 'Eliminare l\'utente "{name}"? Tutti i viaggi verranno eliminati in modo permanente.', + 'admin.deleteUserTitle': 'Elimina utente', + 'admin.newPasswordPlaceholder': 'Inserisci nuova password…', + 'admin.toast.loadError': 'Impossibile caricare i dati di amministrazione', + 'admin.toast.userUpdated': 'Utente aggiornato', + 'admin.toast.updateError': 'Impossibile aggiornare', + 'admin.toast.userDeleted': 'Utente eliminato', + 'admin.toast.deleteError': 'Impossibile eliminare', + 'admin.toast.cannotDeleteSelf': 'Impossibile eliminare il proprio account', + 'admin.toast.userCreated': 'Utente creato', + 'admin.toast.createError': "Impossibile creare l'utente", + 'admin.toast.fieldsRequired': 'Username, email e password sono obbligatori', + 'admin.createUser': 'Crea Utente', + 'admin.invite.title': 'Link di Invito', + 'admin.invite.subtitle': 'Crea link di registrazione monouso', + 'admin.invite.create': 'Crea Link', + 'admin.invite.createAndCopy': 'Crea & Copia', + 'admin.invite.empty': 'Nessun link di invito ancora creato', + 'admin.invite.maxUses': 'Usi Max.', + 'admin.invite.expiry': 'Scade tra', + 'admin.invite.uses': 'usato', + 'admin.invite.expiresAt': 'scade', + 'admin.invite.createdBy': 'da', + 'admin.invite.active': 'Attivo', + 'admin.invite.expired': 'Scaduto', + 'admin.invite.usedUp': 'Esaurito', + 'admin.invite.copied': 'Link di invito copiato negli appunti', + 'admin.invite.copyLink': 'Copia link', + 'admin.invite.deleted': 'Link di invito eliminato', + 'admin.invite.createError': 'Impossibile creare il link di invito', + 'admin.invite.deleteError': 'Impossibile eliminare il link di invito', + 'admin.tabs.settings': 'Impostazioni', + 'admin.allowRegistration': 'Consenti Registrazione', + 'admin.allowRegistrationHint': + 'I nuovi utenti possono registrarsi autonomamente', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Richiedi autenticazione a due fattori (2FA)', + 'admin.requireMfaHint': + "Gli utenti senza 2FA devono completare la configurazione in Impostazioni prima di usare l'app.", + 'admin.apiKeys': 'Chiavi API', + 'admin.apiKeysHint': + 'Opzionale. Abilita dati estesi per i luoghi come foto e meteo.', + 'admin.mapsKey': 'Chiave API Google Maps', + 'admin.mapsKeyHint': + 'Richiesta per la ricerca dei luoghi. Ottienila su console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Senza una chiave API, OpenStreetMap viene utilizzato per la ricerca dei luoghi. Con una chiave API di Google, è possibile caricare anche foto, valutazioni e orari di apertura. Ottienine una su console.cloud.google.com.', + 'admin.recommended': 'Consigliato', + 'admin.weatherKey': 'Chiave API OpenWeatherMap', + 'admin.weatherKeyHint': 'Per i dati meteo. Gratuita su openweathermap.org', + 'admin.validateKey': 'Testa', + 'admin.keyValid': 'Connessa', + 'admin.keyInvalid': 'Non valida', + 'admin.keySaved': 'Chiavi API salvate', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + "Consenti l'accesso tramite provider esterni come Google, Apple, Authentik o Keycloak.", + 'admin.oidcDisplayName': 'Nome Visualizzato', + 'admin.oidcIssuer': 'URL Emittente', + 'admin.oidcIssuerHint': + "L'URL dell'Emittente OpenID Connect del provider. es. https://accounts.google.com", + 'admin.oidcSaved': 'Configurazione OIDC salvata', + 'admin.oidcOnlyMode': 'Disabilita autenticazione con password', + 'admin.oidcOnlyModeHint': + "Se abilitato, è consentito solo l'accesso SSO. L'accesso basato su password e la registrazione sono bloccati.", + 'admin.fileTypes': 'Tipi di File Consentiti', + 'admin.fileTypesHint': + 'Configura quali tipi di file gli utenti possono caricare.', + 'admin.fileTypesFormat': + 'Estensioni separate da virgola (es. jpg,png,pdf,doc). Usa * per consentire tutti i tipi.', + 'admin.fileTypesSaved': 'Impostazioni dei tipi di file salvate', + 'admin.placesPhotos.title': 'Foto dei luoghi', + 'admin.placesPhotos.subtitle': + "Recupera le foto dall'API Google Places. Disabilita per risparmiare la quota API. Le foto di Wikimedia non sono interessate.", + 'admin.placesAutocomplete.title': 'Completamento automatico dei luoghi', + 'admin.placesAutocomplete.subtitle': + "Utilizza l'API Google Places per i suggerimenti di ricerca. Disabilita per risparmiare la quota API.", + 'admin.placesDetails.title': 'Dettagli del luogo', + 'admin.placesDetails.subtitle': + "Recupera informazioni dettagliate sul luogo (orari, valutazione, sito web) dall'API Google Places. Disabilita per risparmiare la quota API.", + 'admin.bagTracking.title': 'Tracciamento valigia', + 'admin.bagTracking.subtitle': + "Abilita il peso e l'assegnazione della valigia per gli elementi della lista valigia", + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': + 'Messaggistica in tempo reale per la collaborazione', + 'admin.collab.notes.title': 'Note', + 'admin.collab.notes.subtitle': 'Note e documenti condivisi', + 'admin.collab.polls.title': 'Sondaggi', + 'admin.collab.polls.subtitle': 'Sondaggi e votazioni di gruppo', + 'admin.collab.whatsnext.title': 'Prossimi passi', + 'admin.collab.whatsnext.subtitle': 'Suggerimenti attività e prossimi passi', + 'admin.tabs.config': 'Personalizzazione', + 'admin.tabs.defaults': 'Impostazioni predefinite', + 'admin.defaultSettings.title': 'Impostazioni predefinite utente', + 'admin.defaultSettings.description': + "Imposta i valori predefiniti per l'intera istanza. Gli utenti che non hanno modificato un'impostazione vedranno questi valori. Le loro modifiche hanno sempre la priorità.", + 'admin.defaultSettings.saved': 'Predefinito salvato', + 'admin.defaultSettings.reset': 'Ripristina il predefinito integrato', + 'admin.defaultSettings.resetToBuiltIn': 'ripristina', + 'admin.tabs.templates': 'Modelli lista valigia', + 'admin.packingTemplates.title': 'Modelli lista valigia', + 'admin.packingTemplates.subtitle': + 'Crea liste valigia riutilizzabili per i tuoi viaggi', + 'admin.packingTemplates.create': 'Nuovo modello', + 'admin.packingTemplates.namePlaceholder': + 'Nome modello (es. Vacanza al mare)', + 'admin.packingTemplates.empty': 'Ancora nessun modello creato', + 'admin.packingTemplates.items': 'elementi', + 'admin.packingTemplates.categories': 'categorie', + 'admin.packingTemplates.itemName': 'Nome elemento', + 'admin.packingTemplates.itemCategory': 'Categoria', + 'admin.packingTemplates.categoryName': 'Nome categoria (es. Abbigliamento)', + 'admin.packingTemplates.addCategory': 'Aggiungi categoria', + 'admin.packingTemplates.created': 'Modello creato', + 'admin.packingTemplates.deleted': 'Modello eliminato', + 'admin.packingTemplates.loadError': 'Impossibile caricare i modelli', + 'admin.packingTemplates.createError': 'Impossibile creare il modello', + 'admin.packingTemplates.deleteError': 'Impossibile eliminare il modello', + 'admin.packingTemplates.saveError': 'Impossibile salvare', + 'admin.tabs.addons': 'Moduli', + 'admin.addons.title': 'Moduli', + 'admin.addons.subtitle': + 'Abilita o disabilita le funzionalità per personalizzare la tua esperienza TREK.', + 'admin.addons.catalog.packing.name': 'Liste', + 'admin.addons.catalog.packing.description': + 'Liste di imballaggio e attività da svolgere per i tuoi viaggi', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Tieni traccia delle spese e pianifica il budget del tuo viaggio', + 'admin.addons.catalog.documents.name': 'Documenti', + 'admin.addons.catalog.documents.description': + 'Archivia e gestisci i documenti di viaggio', + 'admin.addons.catalog.vacay.name': 'Ferie', + 'admin.addons.catalog.vacay.description': + 'Pianificatore personale delle ferie con vista calendario', + 'admin.addons.catalog.atlas.name': 'Atlante', + 'admin.addons.catalog.atlas.description': + 'Mappa del mondo con paesi visitati e statistiche di viaggio', + 'admin.addons.catalog.collab.name': 'Collaborazione', + 'admin.addons.catalog.collab.description': + 'Note, sondaggi e chat in tempo reale per la pianificazione del viaggio', + 'admin.addons.catalog.memories.name': 'Foto (Immich)', + 'admin.addons.catalog.memories.description': + 'Condividi le foto del viaggio tramite la tua istanza Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + "Model Context Protocol per l'integrazione di assistenti AI", + 'admin.addons.subtitleBefore': + 'Abilita o disabilita le funzionalità per personalizzare la tua ', + 'admin.addons.subtitleAfter': ' esperienza.', + 'admin.addons.enabled': 'Abilitato', + 'admin.addons.disabled': 'Disabilitato', + 'admin.addons.type.trip': 'Viaggio', + 'admin.addons.type.global': 'Globale', + 'admin.addons.type.integration': 'Integrazione', + 'admin.addons.tripHint': + "Disponibile come scheda all'interno di ciascun viaggio", + 'admin.addons.globalHint': + 'Disponibile come sezione autonoma nella navigazione principale', + 'admin.addons.integrationHint': + 'Servizi backend e integrazioni API senza pagina dedicata', + 'admin.addons.toast.updated': 'Modulo aggiornato', + 'admin.addons.toast.error': 'Impossibile aggiornare il modulo', + 'admin.addons.noAddons': 'Nessun modulo disponibile', + 'admin.weather.title': 'Dati meteo', + 'admin.weather.badge': 'Dal 24 marzo 2026', + 'admin.weather.description': + 'TREK utilizza Open-Meteo come fonte dei dati meteo. Open-Meteo è un servizio meteo gratuito e open-source — non è richiesta alcuna chiave API.', + 'admin.weather.forecast': 'Previsioni a 16 giorni', + 'admin.weather.forecastDesc': 'In precedenza 5 giorni (OpenWeatherMap)', + 'admin.weather.climate': 'Dati climatici storici', + 'admin.weather.climateDesc': + 'Medie degli ultimi 85 anni per i giorni oltre le previsioni a 16 giorni', + 'admin.weather.requests': '10.000 richieste / giorno', + 'admin.weather.requestsDesc': 'Gratis, nessuna chiave API richiesta', + 'admin.weather.locationHint': + "Il meteo si basa sul primo luogo con coordinate di ogni giorno. Se a un giorno non è assegnato alcun luogo, viene utilizzato come riferimento un qualsiasi luogo dell'elenco.", + 'admin.tabs.audit': 'Audit', + 'admin.audit.subtitle': + 'Eventi sensibili di sicurezza e amministrazione (backup, utenti, 2FA, impostazioni).', + 'admin.audit.empty': 'Nessuna voce di audit.', + 'admin.audit.refresh': 'Aggiorna', + 'admin.audit.loadMore': 'Carica altro', + 'admin.audit.showing': '{count} caricati · {total} totali', + 'admin.audit.col.time': 'Ora', + 'admin.audit.col.user': 'Utente', + 'admin.audit.col.action': 'Azione', + 'admin.audit.col.resource': 'Risorsa', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Dettagli', + 'admin.tabs.mcpTokens': 'Accesso MCP', + 'admin.mcpTokens.title': 'Accesso MCP', + 'admin.mcpTokens.subtitle': + 'Gestisci le sessioni OAuth e i token API di tutti gli utenti', + 'admin.mcpTokens.sectionTitle': 'Token API', + 'admin.mcpTokens.owner': 'Proprietario', + 'admin.mcpTokens.tokenName': 'Nome token', + 'admin.mcpTokens.created': 'Creato', + 'admin.mcpTokens.lastUsed': 'Ultimo utilizzo', + 'admin.mcpTokens.never': 'Mai', + 'admin.mcpTokens.empty': 'Non sono ancora stati creati token MCP', + 'admin.mcpTokens.deleteTitle': 'Elimina token', + 'admin.mcpTokens.deleteMessage': + "Questo token verrà revocato immediatamente. L'utente perderà l'accesso MCP tramite questo token.", + 'admin.mcpTokens.deleteSuccess': 'Token eliminato', + 'admin.mcpTokens.deleteError': 'Impossibile eliminare il token', + 'admin.mcpTokens.loadError': 'Impossibile caricare i token', + 'admin.oauthSessions.sectionTitle': 'Sessioni OAuth', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Proprietario', + 'admin.oauthSessions.scopes': 'Ambiti', + 'admin.oauthSessions.created': 'Creato', + 'admin.oauthSessions.empty': 'Nessuna sessione OAuth attiva', + 'admin.oauthSessions.revokeTitle': 'Revoca sessione', + 'admin.oauthSessions.revokeMessage': + "Questa sessione OAuth verrà revocata immediatamente. Il client perderà l'accesso MCP.", + 'admin.oauthSessions.revokeSuccess': 'Sessione revocata', + 'admin.oauthSessions.revokeError': 'Impossibile revocare la sessione', + 'admin.oauthSessions.loadError': 'Impossibile caricare le sessioni OAuth', + 'admin.tabs.github': 'GitHub', + 'admin.github.title': 'Cronologia rilasci', + 'admin.github.subtitle': 'Ultimi aggiornamenti da {repo}', + 'admin.github.latest': 'Ultimo', + 'admin.github.prerelease': 'Pre-release', + 'admin.github.showDetails': 'Mostra dettagli', + 'admin.github.hideDetails': 'Nascondi dettagli', + 'admin.github.loadMore': 'Carica altro', + 'admin.github.loading': 'Caricamento...', + 'admin.github.error': 'Impossibile caricare i rilasci', + 'admin.github.by': 'da', + 'admin.github.support': 'Mi aiuta a continuare a sviluppare TREK', + 'admin.update.available': 'Aggiornamento disponibile', + 'admin.update.text': + 'TREK {version} è disponibile. Stai eseguendo {current}.', + 'admin.update.button': 'Vedi su GitHub', + 'admin.update.install': 'Installa aggiornamento', + 'admin.update.confirmTitle': "Installare l'aggiornamento?", + 'admin.update.confirmText': + 'TREK verrà aggiornato da {current} a {version}. Il server si riavvierà automaticamente in seguito.', + 'admin.update.dataInfo': + 'Tutti i tuoi dati (viaggi, utenti, chiavi API, caricamenti, Ferie, Atlante, budget) saranno preservati.', + 'admin.update.warning': + "L'app sarà temporaneamente non disponibile durante il riavvio.", + 'admin.update.confirm': 'Aggiorna ora', + 'admin.update.installing': 'Aggiornamento in corso…', + 'admin.update.success': + 'Aggiornamento installato! Il server si sta riavviando…', + 'admin.update.failed': 'Aggiornamento non riuscito', + 'admin.update.backupHint': + 'Ti consigliamo di creare un backup prima di aggiornare.', + 'admin.update.backupLink': 'Vai a Backup', + 'admin.update.howTo': 'Come aggiornare', + 'admin.update.dockerText': + 'La tua istanza TREK è in esecuzione in Docker. Per aggiornare alla versione {version}, esegui i seguenti comandi sul tuo server:', + 'admin.update.reloadHint': 'Ricarica la pagina tra qualche secondo.', + 'admin.tabs.permissions': 'Permessi', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Le notifiche in-app sono sempre attive e non possono essere disabilitate globalmente.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook admin', + 'admin.notifications.adminWebhookPanel.hint': + 'Questo webhook viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dai webhook utente e si attiva automaticamente quando è configurato un URL.', + 'admin.notifications.adminWebhookPanel.saved': 'URL webhook admin salvato', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Webhook di test inviato con successo', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Invio webhook di test fallito', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Il webhook admin si attiva automaticamente quando è configurato un URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Consente agli utenti di configurare i propri argomenti ntfy per le notifiche push. Imposta il server predefinito di seguito per precompilare le impostazioni utente.', + 'admin.notifications.testNtfy': 'Invia Ntfy di test', + 'admin.notifications.testNtfySuccess': 'Ntfy di test inviato con successo', + 'admin.notifications.testNtfyFailed': 'Invio Ntfy di test fallito', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy admin', + 'admin.notifications.adminNtfyPanel.hint': + 'Questo argomento Ntfy viene usato esclusivamente per le notifiche admin (es. avvisi di versione). È separato dagli argomenti per utente e si attiva sempre quando è configurato.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL server Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Usato anche come server predefinito per le notifiche ntfy degli utenti. Lasciare vuoto per usare ntfy.sh. Gli utenti possono sovrascriverlo nelle proprie impostazioni.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Argomento admin', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': + 'Token di accesso (opzionale)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Token di accesso admin rimosso', + 'admin.notifications.adminNtfyPanel.saved': 'Impostazioni Ntfy admin salvate', + 'admin.notifications.adminNtfyPanel.test': 'Invia Ntfy di test', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Ntfy di test inviato con successo', + 'admin.notifications.adminNtfyPanel.testFailed': 'Invio Ntfy di test fallito', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Il Ntfy admin si attiva sempre quando un argomento è configurato', + 'admin.notifications.adminNotificationsHint': + 'Configura quali canali consegnano le notifiche admin (es. avvisi di versione). Il webhook si attiva automaticamente se è impostato un URL webhook admin.', + 'admin.notifications.tripReminders.title': 'Promemoria viaggio', + 'admin.notifications.tripReminders.hint': + "Invia una notifica promemoria prima dell'inizio di un viaggio (richiede giorni di promemoria impostati sul viaggio).", + 'admin.notifications.tripReminders.enabled': 'Promemoria viaggio attivati', + 'admin.notifications.tripReminders.disabled': + 'Promemoria viaggio disattivati', + 'admin.tabs.notifications': 'Notifiche', + 'admin.addons.catalog.journey.name': 'Diario di viaggio', + 'admin.addons.catalog.journey.description': + 'Tracciamento viaggi e diario con check-in, foto e storie quotidiane', +}; +export default admin; diff --git a/shared/src/i18n/it/airport.ts b/shared/src/i18n/it/airport.ts new file mode 100644 index 00000000..c05ae376 --- /dev/null +++ b/shared/src/i18n/it/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': "Codice o città dell'aeroporto (es. FRA)", +}; +export default airport; diff --git a/shared/src/i18n/it/atlas.ts b/shared/src/i18n/it/atlas.ts new file mode 100644 index 00000000..36d91a1b --- /dev/null +++ b/shared/src/i18n/it/atlas.ts @@ -0,0 +1,61 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'La tua impronta di viaggio nel mondo', + 'atlas.countries': 'Paesi', + 'atlas.trips': 'Viaggi', + 'atlas.places': 'Luoghi', + 'atlas.unmark': 'Rimuovi', + 'atlas.confirmMark': 'Segnare questo paese come visitato?', + 'atlas.confirmUnmark': 'Rimuovere questo paese dalla tua lista dei visitati?', + 'atlas.confirmUnmarkRegion': + 'Rimuovere questa regione dalla tua lista dei visitati?', + 'atlas.markVisited': 'Segna come visitato', + 'atlas.markVisitedHint': 'Aggiungi questo paese alla tua lista dei visitati', + 'atlas.markRegionVisitedHint': + 'Aggiungi questa regione alla tua lista dei visitati', + 'atlas.addToBucket': 'Aggiungi alla lista desideri', + 'atlas.addPoi': 'Aggiungi luogo', + 'atlas.bucketNamePlaceholder': 'Nome (paese, città, luogo...)', + 'atlas.month': 'Mese', + 'atlas.addToBucketHint': 'Salvalo come luogo che vuoi visitare', + 'atlas.bucketWhen': 'Quando pensi di visitarlo?', + 'atlas.statsTab': 'Statistiche', + 'atlas.bucketTab': 'Lista desideri', + 'atlas.addBucket': 'Aggiungi alla lista desideri', + 'atlas.bucketNotesPlaceholder': 'Note (opzionale)', + 'atlas.bucketEmpty': 'La tua lista desideri è vuota', + 'atlas.bucketEmptyHint': 'Aggiungi luoghi che sogni di visitare', + 'atlas.days': 'Giorni', + 'atlas.visitedCountries': 'Paesi visitati', + 'atlas.cities': 'Città', + 'atlas.noData': 'Ancora nessun dato di viaggio', + 'atlas.noDataHint': + 'Crea un viaggio e aggiungi luoghi per vedere la tua mappa del mondo', + 'atlas.lastTrip': 'Ultimo viaggio', + 'atlas.nextTrip': 'Prossimo viaggio', + 'atlas.daysLeft': 'giorni rimasti', + 'atlas.streak': 'Serie', + 'atlas.year': 'anno', + 'atlas.years': 'anni', + 'atlas.yearInRow': 'anno consecutivo', + 'atlas.yearsInRow': 'anni consecutivi', + 'atlas.tripIn': 'viaggio in', + 'atlas.tripsIn': 'viaggi in', + 'atlas.since': 'dal', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Asia', + 'atlas.northAmerica': 'Nord America', + 'atlas.southAmerica': 'Sud America', + 'atlas.africa': 'Africa', + 'atlas.oceania': 'Oceania', + 'atlas.other': 'Altro', + 'atlas.firstVisit': 'Primo viaggio', + 'atlas.lastVisitLabel': 'Ultimo viaggio', + 'atlas.tripSingular': 'Viaggio', + 'atlas.tripPlural': 'Viaggi', + 'atlas.placeVisited': 'Luogo visitato', + 'atlas.placesVisited': 'Luoghi visitati', + 'atlas.searchCountry': 'Cerca un paese...', +}; +export default atlas; diff --git a/shared/src/i18n/it/backup.ts b/shared/src/i18n/it/backup.ts new file mode 100644 index 00000000..1ea227fd --- /dev/null +++ b/shared/src/i18n/it/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Backup dati', + 'backup.subtitle': 'Database e tutti i file caricati', + 'backup.refresh': 'Aggiorna', + 'backup.upload': 'Carica backup', + 'backup.uploading': 'Caricamento...', + 'backup.create': 'Crea backup', + 'backup.creating': 'Creazione...', + 'backup.empty': 'Ancora nessun backup', + 'backup.createFirst': 'Crea primo backup', + 'backup.download': 'Scarica', + 'backup.restore': 'Ripristina', + 'backup.confirm.restore': + 'Ripristinare il backup "{name}"?\n\nTutti i dati attuali verranno sostituiti con il backup.', + 'backup.confirm.uploadRestore': + 'Scaricare e ripristinare il file di backup "{name}"?\n\nTutti i dati attuali verranno sovrascritti.', + 'backup.confirm.delete': 'Eliminare il backup "{name}"?', + 'backup.toast.loadError': 'Impossibile caricare i backup', + 'backup.toast.created': 'Backup creato con successo', + 'backup.toast.createError': 'Impossibile creare il backup', + 'backup.toast.restored': 'Backup ripristinato. La pagina verrà ricaricata...', + 'backup.toast.restoreError': 'Impossibile ripristinare', + 'backup.toast.uploadError': 'Impossibile caricare', + 'backup.toast.deleted': 'Backup eliminato', + 'backup.toast.deleteError': 'Impossibile eliminare', + 'backup.toast.downloadError': 'Download non riuscito', + 'backup.toast.settingsSaved': 'Impostazioni auto-backup salvate', + 'backup.toast.settingsError': 'Impossibile salvare le impostazioni', + 'backup.auto.title': 'Auto-Backup', + 'backup.auto.subtitle': 'Backup automatico pianificato', + 'backup.auto.enable': 'Abilita auto-backup', + 'backup.auto.enableHint': + 'I backup verranno creati automaticamente in base alla pianificazione scelta', + 'backup.auto.interval': 'Intervallo', + 'backup.auto.hour': "Esegui all'ora", + 'backup.auto.hourHint': 'Ora locale del server (formato {format})', + 'backup.auto.dayOfWeek': 'Giorno della settimana', + 'backup.auto.dayOfMonth': 'Giorno del mese', + 'backup.auto.dayOfMonthHint': + 'Limitato a 1–28 per compatibilità con tutti i mesi', + 'backup.auto.scheduleSummary': 'Pianificazione', + 'backup.auto.summaryDaily': 'Ogni giorno alle {hour}:00', + 'backup.auto.summaryWeekly': 'Ogni {day} alle {hour}:00', + 'backup.auto.summaryMonthly': 'Giorno {day} di ogni mese alle {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + "L'auto-backup è configurato tramite variabili d'ambiente Docker. Per modificare queste impostazioni, aggiorna il tuo docker-compose.yml e riavvia il container.", + 'backup.auto.copyEnv': 'Copia variabili env Docker', + 'backup.auto.envCopied': 'Variabili env Docker copiate negli appunti', + 'backup.auto.keepLabel': 'Elimina i vecchi backup dopo', + 'backup.dow.sunday': 'Dom', + 'backup.dow.monday': 'Lun', + 'backup.dow.tuesday': 'Mar', + 'backup.dow.wednesday': 'Mer', + 'backup.dow.thursday': 'Gio', + 'backup.dow.friday': 'Ven', + 'backup.dow.saturday': 'Sab', + 'backup.interval.hourly': 'Ogni ora', + 'backup.interval.daily': 'Giornaliero', + 'backup.interval.weekly': 'Settimanale', + 'backup.interval.monthly': 'Mensile', + 'backup.keep.1day': '1 giorno', + 'backup.keep.3days': '3 giorni', + 'backup.keep.7days': '7 giorni', + 'backup.keep.14days': '14 giorni', + 'backup.keep.30days': '30 giorni', + 'backup.keep.forever': 'Conserva per sempre', + 'backup.restoreConfirmTitle': 'Ripristinare il backup?', + 'backup.restoreWarning': + 'Tutti i dati attuali (viaggi, luoghi, utenti, caricamenti) verranno sostituiti in modo permanente dal backup. Questa azione non può essere annullata.', + 'backup.restoreTip': + 'Suggerimento: Crea un backup dello stato attuale prima di ripristinare.', + 'backup.restoreConfirm': 'Sì, ripristina', +}; +export default backup; diff --git a/shared/src/i18n/it/budget.ts b/shared/src/i18n/it/budget.ts new file mode 100644 index 00000000..ed9d8063 --- /dev/null +++ b/shared/src/i18n/it/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'Esporta CSV', + 'budget.emptyTitle': 'Ancora nessun budget creato', + 'budget.emptyText': + 'Crea categorie e voci per pianificare il budget del tuo viaggio', + 'budget.emptyPlaceholder': 'Inserisci nome categoria...', + 'budget.createCategory': 'Crea categoria', + 'budget.category': 'Categoria', + 'budget.categoryName': 'Nome categoria', + 'budget.table.name': 'Nome', + 'budget.table.total': 'Totale', + 'budget.table.persons': 'Persone', + 'budget.table.days': 'Giorni', + 'budget.table.perPerson': 'Per persona', + 'budget.table.perDay': 'Per giorno', + 'budget.table.perPersonDay': 'P. p / gio.', + 'budget.table.note': 'Nota', + 'budget.table.date': 'Data', + 'budget.newEntry': 'Nuova voce', + 'budget.defaultEntry': 'Nuova voce', + 'budget.defaultCategory': 'Nuova categoria', + 'budget.total': 'Totale', + 'budget.totalBudget': 'Budget totale', + 'budget.byCategory': 'Per categoria', + 'budget.editTooltip': 'Clicca per modificare', + 'budget.linkedToReservation': + 'Collegato a una prenotazione — modifica il nome lì', + 'budget.confirm.deleteCategory': + 'Sei sicuro di voler eliminare la categoria "{name}" con {count} voci?', + 'budget.deleteCategory': 'Elimina categoria', + 'budget.perPerson': 'Per persona', + 'budget.paid': 'Pagato', + 'budget.open': 'Aperto', + 'budget.noMembers': 'Nessun membro assegnato', + 'budget.settlement': 'Regolamento', + 'budget.settlementInfo': + "Clicca sull'avatar di un membro su una voce di budget per contrassegnarlo in verde — significa che ha pagato. Il regolamento mostra poi chi deve quanto a chi.", + 'budget.netBalances': 'Saldi netti', + 'budget.categoriesLabel': 'categorie', +}; +export default budget; diff --git a/shared/src/i18n/it/categories.ts b/shared/src/i18n/it/categories.ts new file mode 100644 index 00000000..d6d639e7 --- /dev/null +++ b/shared/src/i18n/it/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categorie', + 'categories.subtitle': 'Gestisci le categorie per i luoghi', + 'categories.new': 'Nuova categoria', + 'categories.empty': 'Ancora nessuna categoria', + 'categories.namePlaceholder': 'Nome categoria', + 'categories.icon': 'Icona', + 'categories.color': 'Colore', + 'categories.customColor': 'Scegli colore personalizzato', + 'categories.preview': 'Anteprima', + 'categories.defaultName': 'Categoria', + 'categories.update': 'Aggiorna', + 'categories.create': 'Crea', + 'categories.confirm.delete': + 'Eliminare la categoria? I luoghi in questa categoria non verranno eliminati.', + 'categories.toast.loadError': 'Impossibile caricare le categorie', + 'categories.toast.nameRequired': 'Inserisci un nome', + 'categories.toast.updated': 'Categoria aggiornata', + 'categories.toast.created': 'Categoria creata', + 'categories.toast.saveError': 'Impossibile salvare', + 'categories.toast.deleted': 'Categoria eliminata', + 'categories.toast.deleteError': 'Impossibile eliminare', +}; +export default categories; diff --git a/shared/src/i18n/it/collab.ts b/shared/src/i18n/it/collab.ts new file mode 100644 index 00000000..d339ec40 --- /dev/null +++ b/shared/src/i18n/it/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Note', + 'collab.tabs.polls': 'Sondaggi', + 'collab.whatsNext.title': "Cosa c'è dopo", + 'collab.whatsNext.today': 'Oggi', + 'collab.whatsNext.tomorrow': 'Domani', + 'collab.whatsNext.empty': 'Nessuna attività imminente', + 'collab.whatsNext.until': 'a', + 'collab.whatsNext.emptyHint': 'Le attività con orari appariranno qui', + 'collab.chat.send': 'Invia', + 'collab.chat.placeholder': 'Scrivi un messaggio...', + 'collab.chat.empty': 'Inizia la conversazione', + 'collab.chat.emptyHint': + 'I messaggi sono condivisi con tutti i membri del viaggio', + 'collab.chat.emptyDesc': + 'Condividi idee, programmi e aggiornamenti con il tuo gruppo di viaggio', + 'collab.chat.today': 'Oggi', + 'collab.chat.yesterday': 'Ieri', + 'collab.chat.deletedMessage': 'ha eliminato un messaggio', + 'collab.chat.reply': 'Rispondi', + 'collab.chat.loadMore': 'Carica messaggi precedenti', + 'collab.chat.justNow': 'ora', + 'collab.chat.minutesAgo': '{n}m fa', + 'collab.chat.hoursAgo': '{n}h fa', + 'collab.notes.title': 'Note', + 'collab.notes.new': 'Nuova nota', + 'collab.notes.empty': 'Ancora nessuna nota', + 'collab.notes.emptyHint': 'Inizia a raccogliere idee e programmi', + 'collab.notes.all': 'Tutte', + 'collab.notes.titlePlaceholder': 'Titolo della nota', + 'collab.notes.contentPlaceholder': 'Scrivi qualcosa...', + 'collab.notes.categoryPlaceholder': 'Categoria', + 'collab.notes.newCategory': 'Nuova categoria...', + 'collab.notes.category': 'Categoria', + 'collab.notes.noCategory': 'Nessuna categoria', + 'collab.notes.color': 'Colore', + 'collab.notes.save': 'Salva', + 'collab.notes.cancel': 'Annulla', + 'collab.notes.edit': 'Modifica', + 'collab.notes.delete': 'Elimina', + 'collab.notes.pin': 'Fissa', + 'collab.notes.unpin': 'Rimuovi', + 'collab.notes.daysAgo': '{n}g fa', + 'collab.notes.categorySettings': 'Gestisci categorie', + 'collab.notes.create': 'Crea', + 'collab.notes.website': 'Sito web', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Allega file', + 'collab.notes.noCategoriesYet': 'Ancora nessuna categoria', + 'collab.notes.emptyDesc': 'Crea una nota per iniziare', + 'collab.polls.title': 'Sondaggi', + 'collab.polls.new': 'Nuovo sondaggio', + 'collab.polls.empty': 'Ancora nessun sondaggio', + 'collab.polls.emptyHint': 'Chiedi al gruppo e votate insieme', + 'collab.polls.question': 'Domanda', + 'collab.polls.questionPlaceholder': 'Cosa dovremmo fare?', + 'collab.polls.addOption': '+ Aggiungi opzione', + 'collab.polls.optionPlaceholder': 'Opzione {n}', + 'collab.polls.create': 'Crea sondaggio', + 'collab.polls.close': 'Chiudi', + 'collab.polls.closed': 'Chiuso', + 'collab.polls.votes': '{n} voti', + 'collab.polls.vote': '{n} voto', + 'collab.polls.multipleChoice': 'Scelta multipla', + 'collab.polls.multiChoice': 'Scelta multipla', + 'collab.polls.deadline': 'Scadenza', + 'collab.polls.option': 'Opzione', + 'collab.polls.options': 'Opzioni', + 'collab.polls.delete': 'Elimina', + 'collab.polls.closedSection': 'Chiusi', +}; +export default collab; diff --git a/shared/src/i18n/it/common.ts b/shared/src/i18n/it/common.ts new file mode 100644 index 00000000..de553139 --- /dev/null +++ b/shared/src/i18n/it/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Salva', + 'common.showMore': 'Mostra di più', + 'common.showLess': 'Mostra meno', + 'common.cancel': 'Annulla', + 'common.clear': 'Cancella', + 'common.delete': 'Elimina', + 'common.edit': 'Modifica', + 'common.add': 'Aggiungi', + 'common.loading': 'Caricamento...', + 'common.import': 'Importa', + 'common.select': 'Seleziona', + 'common.selectAll': 'Seleziona tutto', + 'common.deselectAll': 'Deseleziona tutto', + 'common.error': 'Errore', + 'common.unknownError': 'Errore sconosciuto', + 'common.tooManyAttempts': 'Troppi tentativi. Riprova più tardi.', + 'common.back': 'Indietro', + 'common.all': 'Tutti', + 'common.close': 'Chiudi', + 'common.open': 'Apri', + 'common.upload': 'Carica', + 'common.search': 'Cerca', + 'common.confirm': 'Conferma', + 'common.ok': 'OK', + 'common.yes': 'Sì', + 'common.no': 'No', + 'common.or': 'o', + 'common.none': 'Nessuno', + 'common.date': 'Data', + 'common.rename': 'Rinomina', + 'common.discardChanges': 'Scarta modifiche', + 'common.discard': 'Scarta', + 'common.name': 'Nome', + 'common.email': 'Email', + 'common.password': 'Password', + 'common.saving': 'Salvataggio...', + 'common.saved': 'Salvato', + 'common.expand': 'Espandi', + 'common.collapse': 'Comprimi', + 'common.update': 'Aggiorna', + 'common.change': 'Cambia', + 'common.uploading': 'Caricamento…', + 'common.backToPlanning': 'Torna al Programma', + 'common.reset': 'Reimposta', + 'common.copy': 'Copia', + 'common.copied': 'Copiato', + 'common.justNow': 'proprio ora', + 'common.hoursAgo': '{count}h fa', + 'common.daysAgo': '{count}g fa', +}; +export default common; diff --git a/shared/src/i18n/it/dashboard.ts b/shared/src/i18n/it/dashboard.ts new file mode 100644 index 00000000..4ff9d05a --- /dev/null +++ b/shared/src/i18n/it/dashboard.ts @@ -0,0 +1,110 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'I miei Viaggi', + 'dashboard.subtitle.loading': 'Caricamento viaggi...', + 'dashboard.subtitle.trips': '{count} viaggi ({archived} archiviati)', + 'dashboard.subtitle.empty': 'Inizia il tuo primo viaggio', + 'dashboard.subtitle.activeOne': '{count} viaggio attivo', + 'dashboard.subtitle.activeMany': '{count} viaggi attivi', + 'dashboard.subtitle.archivedSuffix': ' · {count} archiviati', + 'dashboard.newTrip': 'Nuovo Viaggio', + 'dashboard.gridView': 'Vista a griglia', + 'dashboard.listView': 'Vista a lista', + 'dashboard.currency': 'Valuta', + 'dashboard.timezone': 'Fusi orari', + 'dashboard.localTime': 'Locale', + 'dashboard.timezoneCustomTitle': 'Fuso orario personalizzato', + 'dashboard.timezoneCustomLabelPlaceholder': 'Etichetta (opzionale)', + 'dashboard.timezoneCustomTzPlaceholder': 'es. Europe/Rome', + 'dashboard.timezoneCustomAdd': 'Aggiungi', + 'dashboard.timezoneCustomErrorEmpty': + 'Inserisci un identificatore di fuso orario', + 'dashboard.timezoneCustomErrorInvalid': + 'Fuso orario non valido. Usa formati come Europe/Rome', + 'dashboard.timezoneCustomErrorDuplicate': 'Già aggiunto', + 'dashboard.emptyTitle': 'Ancora nessun viaggio', + 'dashboard.emptyText': 'Crea il tuo primo viaggio e inizia a programmare!', + 'dashboard.emptyButton': 'Crea il primo viaggio', + 'dashboard.nextTrip': 'Prossimo Viaggio', + 'dashboard.shared': 'Condiviso', + 'dashboard.sharedBy': 'Condiviso da {name}', + 'dashboard.days': 'Giorni', + 'dashboard.places': 'Luoghi', + 'dashboard.members': 'Compagni di viaggio', + 'dashboard.archive': 'Archivia', + 'dashboard.copyTrip': 'Copia', + 'dashboard.copySuffix': 'copia', + 'dashboard.restore': 'Ripristina', + 'dashboard.archived': 'Archiviati', + 'dashboard.status.ongoing': 'In corso', + 'dashboard.status.today': 'Oggi', + 'dashboard.status.tomorrow': 'Domani', + 'dashboard.status.past': 'Passato', + 'dashboard.status.daysLeft': '-{count} giorni', + 'dashboard.toast.loadError': 'Impossibile caricare i viaggi', + 'dashboard.toast.created': 'Viaggio creato con successo!', + 'dashboard.toast.createError': 'Impossibile creare il viaggio', + 'dashboard.toast.updated': 'Viaggio aggiornato!', + 'dashboard.toast.updateError': 'Impossibile aggiornare il viaggio', + 'dashboard.toast.deleted': 'Viaggio eliminato', + 'dashboard.toast.deleteError': 'Impossibile eliminare il viaggio', + 'dashboard.toast.archived': 'Viaggio archiviato', + 'dashboard.toast.archiveError': 'Impossibile archiviare il viaggio', + 'dashboard.toast.restored': 'Viaggio ripristinato', + 'dashboard.toast.restoreError': 'Impossibile ripristinare il viaggio', + 'dashboard.toast.copied': 'Viaggio copiato!', + 'dashboard.toast.copyError': 'Impossibile copiare il viaggio', + 'dashboard.confirm.delete': + 'Eliminare il viaggio "{title}"? Tutti i luoghi e i programmi verranno eliminati in modo permanente.', + 'dashboard.editTrip': 'Modifica Viaggio', + 'dashboard.createTrip': 'Crea Nuovo Viaggio', + 'dashboard.tripTitle': 'Titolo', + 'dashboard.tripTitlePlaceholder': 'es. Estate in Giappone', + 'dashboard.tripDescription': 'Descrizione', + 'dashboard.tripDescriptionPlaceholder': 'Di cosa tratta questo viaggio?', + 'dashboard.startDate': 'Data di inizio', + 'dashboard.endDate': 'Data di fine', + 'dashboard.dayCount': 'Numero di giorni', + 'dashboard.dayCountHint': + 'Quanti giorni pianificare quando non sono impostate date di viaggio.', + 'dashboard.noDateHint': + 'Nessuna data impostata — verranno creati 7 giorni predefiniti. Puoi cambiarlo in qualsiasi momento.', + 'dashboard.coverImage': 'Immagine di copertina', + 'dashboard.addCoverImage': + 'Aggiungi immagine di copertina (o trascinala qui)', + 'dashboard.addMembers': 'Compagni di viaggio', + 'dashboard.addMember': 'Aggiungi membro', + 'dashboard.coverSaved': 'Immagine di copertina salvata', + 'dashboard.coverUploadError': 'Impossibile caricare', + 'dashboard.coverRemoveError': 'Impossibile rimuovere', + 'dashboard.titleRequired': 'Il titolo è obbligatorio', + 'dashboard.endDateError': + 'La data di fine deve essere successiva alla data di inizio', + 'dashboard.greeting.morning': 'Buongiorno,', + 'dashboard.greeting.afternoon': 'Buon pomeriggio,', + 'dashboard.greeting.evening': 'Buonasera,', + 'dashboard.mobile.liveNow': 'In diretta', + 'dashboard.mobile.tripProgress': 'Progresso del viaggio', + 'dashboard.mobile.daysLeft': '{count} giorni rimanenti', + 'dashboard.mobile.places': 'Luoghi', + 'dashboard.mobile.buddies': 'Compagni', + 'dashboard.mobile.newTrip': 'Nuovo viaggio', + 'dashboard.mobile.currency': 'Valuta', + 'dashboard.mobile.timezone': 'Fuso orario', + 'dashboard.mobile.upcomingTrips': 'Viaggi in arrivo', + 'dashboard.mobile.yourTrips': 'I tuoi viaggi', + 'dashboard.mobile.trips': 'viaggi', + 'dashboard.mobile.starts': 'Inizio', + 'dashboard.mobile.duration': 'Durata', + 'dashboard.mobile.day': 'giorno', + 'dashboard.mobile.days': 'giorni', + 'dashboard.mobile.ongoing': 'In corso', + 'dashboard.mobile.startsToday': 'Inizia oggi', + 'dashboard.mobile.tomorrow': 'Domani', + 'dashboard.mobile.inDays': 'Tra {count} giorni', + 'dashboard.mobile.inMonths': 'Tra {count} mesi', + 'dashboard.mobile.completed': 'Completato', + 'dashboard.mobile.currencyConverter': 'Convertitore di valuta', +}; +export default dashboard; diff --git a/shared/src/i18n/it/day.ts b/shared/src/i18n/it/day.ts new file mode 100644 index 00000000..ea08b3c3 --- /dev/null +++ b/shared/src/i18n/it/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Probabilità di pioggia', + 'day.precipitation': 'Precipitazioni', + 'day.wind': 'Vento', + 'day.sunrise': 'Alba', + 'day.sunset': 'Tramonto', + 'day.hourlyForecast': 'Previsione oraria', + 'day.climateHint': + 'Medie storiche — previsioni reali disponibili entro 16 giorni da questa data.', + 'day.noWeather': + 'Nessun dato meteo disponibile. Aggiungi un luogo con coordinate.', + 'day.overview': 'Panoramica giornaliera', + 'day.accommodation': 'Alloggio', + 'day.addAccommodation': 'Aggiungi alloggio', + 'day.hotelDayRange': 'Applica ai giorni', + 'day.noPlacesForHotel': 'Aggiungi prima i luoghi al tuo viaggio', + 'day.allDays': 'Tutti', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Fino a', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Conferma', + 'day.editAccommodation': 'Modifica alloggio', + 'day.reservations': 'Prenotazioni', +}; +export default day; diff --git a/shared/src/i18n/it/dayplan.ts b/shared/src/i18n/it/dayplan.ts new file mode 100644 index 00000000..bcaab98e --- /dev/null +++ b/shared/src/i18n/it/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Esporta calendario (ICS)', + 'dayplan.emptyDay': 'Nessun luogo programmato per questo giorno', + 'dayplan.addNote': 'Aggiungi nota', + 'dayplan.editNote': 'Modifica nota', + 'dayplan.noteAdd': 'Aggiungi nota', + 'dayplan.noteEdit': 'Modifica nota', + 'dayplan.noteTitle': 'Nota', + 'dayplan.noteSubtitle': 'Nota giornaliera', + 'dayplan.totalCost': 'Costo totale', + 'dayplan.days': 'Giorni', + 'dayplan.dayN': 'Giorno {n}', + 'dayplan.calculating': 'Calcolo in corso...', + 'dayplan.route': 'Percorso', + 'dayplan.optimize': 'Ottimizza', + 'dayplan.optimized': 'Percorso ottimizzato', + 'dayplan.routeError': 'Impossibile calcolare il percorso', + 'dayplan.toast.needTwoPlaces': + "Servono almeno due luoghi per l'ottimizzazione del percorso", + 'dayplan.toast.routeOptimized': 'Percorso ottimizzato', + 'dayplan.toast.noGeoPlaces': + 'Nessun luogo con coordinate trovato per il calcolo del percorso', + 'dayplan.confirmed': 'Confermata', + 'dayplan.pendingRes': 'In attesa', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Esporta il programma del giorno come PDF', + 'dayplan.pdfError': 'Impossibile esportare il PDF', + 'dayplan.cannotReorderTransport': + 'Le prenotazioni con un orario fisso non possono essere riordinate', + 'dayplan.confirmRemoveTimeTitle': "Rimuovere l'orario?", + 'dayplan.confirmRemoveTimeBody': + "Questo luogo ha un orario fisso ({time}). Spostarlo rimuoverà l'orario e consentirà l'ordinamento libero.", + 'dayplan.confirmRemoveTimeAction': 'Rimuovi orario e sposta', + 'dayplan.cannotDropOnTimed': + 'Gli elementi non possono essere posizionati tra voci con orario fisso', + 'dayplan.cannotBreakChronology': + "Ciò interromperebbe l'ordine cronologico degli elementi e delle prenotazioni pianificati", + 'dayplan.mobile.addPlace': 'Aggiungi luogo', + 'dayplan.mobile.searchPlaces': 'Cerca luoghi...', + 'dayplan.mobile.allAssigned': 'Tutti i luoghi assegnati', + 'dayplan.mobile.noMatch': 'Nessun risultato', + 'dayplan.mobile.createNew': 'Crea nuovo luogo', +}; +export default dayplan; diff --git a/shared/src/i18n/it/externalNotifications.ts b/shared/src/i18n/it/externalNotifications.ts new file mode 100644 index 00000000..1895def7 --- /dev/null +++ b/shared/src/i18n/it/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const it: NotificationLocale = { + email: { + footer: + 'Hai ricevuto questa email perché hai le notifiche abilitate in TREK.', + manage: 'Gestisci le preferenze nelle impostazioni', + madeWith: 'Made with', + openTrek: 'Apri TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Invito a "${p.trip}"`, + body: `${p.actor} ha invitato ${p.invitee || 'un membro'} al viaggio "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nuova prenotazione: ${p.booking}`, + body: `${p.actor} ha aggiunto una prenotazione "${p.booking}" (${p.type}) a "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Promemoria viaggio: ${p.trip}`, + body: `Il tuo viaggio "${p.trip}" si avvicina!`, + }), + todo_due: (p) => ({ + title: `Attività in scadenza: ${p.todo}`, + body: `"${p.todo}" in "${p.trip}" scade il ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Invito Vacay Fusion', + body: `${p.actor} ti ha invitato a fondere i piani vacanza. Apri TREK per accettare o rifiutare.`, + }), + photos_shared: (p) => ({ + title: `${p.count} foto condivise`, + body: `${p.actor} ha condiviso ${p.count} foto in "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nuovo messaggio in "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Bagagli: ${p.category}`, + body: `${p.actor} ti ha assegnato alla categoria "${p.category}" in "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nuova versione TREK disponibile', + body: `TREK ${p.version} è ora disponibile. Visita il pannello di amministrazione per aggiornare.`, + }), + synology_session_cleared: () => ({ + title: 'Sessione Synology rimossa', + body: 'Il tuo account o URL Synology è cambiato. Sei stato disconnesso da Synology Photos.', + }), + }, + passwordReset: { + subject: 'Reimposta la tua password', + greeting: 'Ciao', + body: 'Abbiamo ricevuto una richiesta di reimpostazione della password per il tuo account TREK. Clicca il pulsante qui sotto per impostare una nuova password.', + ctaIntro: 'Reimposta password', + expiry: 'Questo link scade tra 60 minuti.', + ignore: + 'Se non hai richiesto questa operazione, ignora questa email — la tua password non cambierà.', + }, +}; + +export default it; diff --git a/shared/src/i18n/it/files.ts b/shared/src/i18n/it/files.ts new file mode 100644 index 00000000..2f388c27 --- /dev/null +++ b/shared/src/i18n/it/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'File', + 'files.pageTitle': 'File e documenti', + 'files.subtitle': '{count} file per {trip}', + 'files.download': 'Scarica', + 'files.openError': 'Impossibile aprire il file', + 'files.downloadPdf': 'Scarica PDF', + 'files.count': '{count} file', + 'files.countSingular': '1 documento', + 'files.uploaded': '{count} caricati', + 'files.uploadError': 'Caricamento non riuscito', + 'files.dropzone': 'Trascina qui i file', + 'files.dropzoneHint': 'oppure clicca per sfogliare', + 'files.allowedTypes': + 'Immagini, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Caricamento...', + 'files.filterAll': 'Tutti', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Immagini', + 'files.filterDocs': 'Documenti', + 'files.filterCollab': 'Note Collaborazione', + 'files.sourceCollab': 'Da Note Collaborazione', + 'files.empty': 'Ancora nessun file', + 'files.emptyHint': 'Carica file per allegarli al tuo viaggio', + 'files.openTab': 'Apri in una nuova scheda', + 'files.confirm.delete': 'Sei sicuro di voler eliminare questo file?', + 'files.toast.deleted': 'File eliminato', + 'files.toast.deleteError': 'Impossibile eliminare il file', + 'files.sourcePlan': 'Programma giornaliero', + 'files.sourceBooking': 'Prenotazione', + 'files.sourceTransport': 'Trasporto', + 'files.attach': 'Allega', + 'files.pasteHint': 'Puoi anche incollare immagini dagli appunti (Ctrl+V)', + 'files.trash': 'Cestino', + 'files.trashEmpty': 'Il cestino è vuoto', + 'files.emptyTrash': 'Svuota cestino', + 'files.restore': 'Ripristina', + 'files.star': 'Aggiungi ai preferiti', + 'files.unstar': 'Rimuovi dai preferiti', + 'files.assign': 'Assegna', + 'files.assignTitle': 'Assegna file', + 'files.assignPlace': 'Luogo', + 'files.assignBooking': 'Prenotazione', + 'files.assignTransport': 'Trasporto', + 'files.unassigned': 'Non assegnato', + 'files.unlink': 'Rimuovi collegamento', + 'files.toast.trashed': 'Spostato nel cestino', + 'files.toast.restored': 'File ripristinato', + 'files.toast.trashEmptied': 'Cestino svuotato', + 'files.toast.assigned': 'File assegnato', + 'files.toast.assignError': 'Assegnazione fallita', + 'files.toast.restoreError': 'Ripristino fallito', + 'files.confirm.permanentDelete': + 'Eliminare questo file in modo permanente? Questa operazione non può essere annullata.', + 'files.confirm.emptyTrash': + 'Eliminare in modo permanente tutti i file nel cestino? Questa operazione non può essere annullata.', + 'files.noteLabel': 'Nota', + 'files.notePlaceholder': 'Aggiungi una nota...', +}; +export default files; diff --git a/shared/src/i18n/it/index.ts b/shared/src/i18n/it/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/it/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/it/inspector.ts b/shared/src/i18n/it/inspector.ts new file mode 100644 index 00000000..7927eb87 --- /dev/null +++ b/shared/src/i18n/it/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Aperto', + 'inspector.closed': 'Chiuso', + 'inspector.openingHours': 'Orari di apertura', + 'inspector.showHours': 'Mostra orari di apertura', + 'inspector.files': 'File', + 'inspector.filesCount': '{count} file', + 'inspector.removeFromDay': 'Rimuovi dal giorno', + 'inspector.remove': 'Rimuovi', + 'inspector.addToDay': 'Aggiungi al giorno', + 'inspector.confirmedRes': 'Prenotazione confermata', + 'inspector.pendingRes': 'Prenotazione in attesa', + 'inspector.google': 'Apri in Google Maps', + 'inspector.website': 'Apri sito web', + 'inspector.addRes': 'Prenotazione', + 'inspector.editRes': 'Modifica prenotazione', + 'inspector.participants': 'Partecipanti', + 'inspector.trackStats': 'Dati del percorso', +}; +export default inspector; diff --git a/shared/src/i18n/it/journey.ts b/shared/src/i18n/it/journey.ts new file mode 100644 index 00000000..fbdb3744 --- /dev/null +++ b/shared/src/i18n/it/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Cerca viaggi…', + 'journey.search.noResults': 'Nessun viaggio corrisponde a "{query}"', + 'journey.title': 'Diario di viaggio', + 'journey.subtitle': 'Segui i tuoi viaggi in tempo reale', + 'journey.new': 'Nuovo diario', + 'journey.create': 'Crea', + 'journey.titlePlaceholder': 'Dove stai andando?', + 'journey.empty': 'Nessun diario ancora', + 'journey.emptyHint': 'Inizia a documentare il tuo prossimo viaggio', + 'journey.deleted': 'Diario eliminato', + 'journey.createError': 'Impossibile creare il diario', + 'journey.deleteError': 'Impossibile eliminare il diario', + 'journey.deleteConfirmTitle': 'Elimina', + 'journey.deleteConfirmMessage': + 'Eliminare "{title}"? Questa azione non può essere annullata.', + 'journey.deleteConfirmGeneric': 'Sei sicuro di voler eliminare questo?', + 'journey.notFound': 'Diario non trovato', + 'journey.photos': 'Foto', + 'journey.timelineEmpty': 'Nessuna tappa ancora', + 'journey.timelineEmptyHint': + 'Aggiungi un check-in o scrivi una voce di diario per iniziare', + 'journey.status.draft': 'Bozza', + 'journey.status.active': 'Attivo', + 'journey.status.completed': 'Completato', + 'journey.status.upcoming': 'In arrivo', + 'journey.status.archived': 'Archiviato', + 'journey.checkin.add': 'Check-in', + 'journey.checkin.namePlaceholder': 'Nome del luogo', + 'journey.checkin.notesPlaceholder': 'Note (facoltativo)', + 'journey.checkin.save': 'Salva', + 'journey.checkin.error': 'Impossibile salvare il check-in', + 'journey.entry.add': 'Diario', + 'journey.entry.edit': 'Modifica voce', + 'journey.entry.titlePlaceholder': 'Titolo (facoltativo)', + 'journey.entry.bodyPlaceholder': 'Cosa è successo oggi?', + 'journey.entry.save': 'Salva', + 'journey.entry.error': 'Impossibile salvare la voce', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Caricamento fallito', + 'journey.share.share': 'Condividi', + 'journey.share.public': 'Pubblico', + 'journey.share.linkCopied': 'Link pubblico copiato', + 'journey.share.disabled': 'Condivisione pubblica disattivata', + 'journey.editor.titlePlaceholder': 'Dai un nome a questo momento...', + 'journey.editor.bodyPlaceholder': 'Racconta la storia di questa giornata...', + 'journey.editor.placePlaceholder': 'Luogo (facoltativo)', + 'journey.editor.tagsPlaceholder': + 'Tag: gioiello nascosto, miglior pasto, da rivisitare...', + 'journey.visibility.private': 'Privato', + 'journey.visibility.shared': 'Condiviso', + 'journey.visibility.public': 'Pubblico', + 'journey.emptyState.title': 'La tua storia inizia qui', + 'journey.emptyState.subtitle': + 'Fai un check-in o scrivi la tua prima voce di diario', + 'journey.frontpage.subtitle': + 'Trasforma i tuoi viaggi in storie indimenticabili', + 'journey.frontpage.createJourney': 'Crea diario', + 'journey.frontpage.activeJourney': 'Diario attivo', + 'journey.frontpage.allJourneys': 'Tutti i diari', + 'journey.frontpage.journeys': 'diari', + 'journey.frontpage.createNew': 'Crea un nuovo diario', + 'journey.frontpage.createNewSub': + 'Scegli viaggi, scrivi storie, condividi le tue avventure', + 'journey.frontpage.live': 'In diretta', + 'journey.frontpage.synced': 'Sincronizzato', + 'journey.frontpage.continueWriting': 'Continua a scrivere', + 'journey.frontpage.updated': 'Aggiornato {time}', + 'journey.frontpage.suggestionLabel': 'Viaggio appena terminato', + 'journey.frontpage.suggestionText': + 'Trasforma {title} in un diario di viaggio', + 'journey.frontpage.dismiss': 'Ignora', + 'journey.frontpage.journeyName': 'Nome del diario', + 'journey.frontpage.namePlaceholder': 'es. Sud-est asiatico 2026', + 'journey.frontpage.selectTrips': 'Seleziona viaggi', + 'journey.frontpage.tripsSelected': 'viaggi selezionati', + 'journey.frontpage.trips': 'viaggi', + 'journey.frontpage.placesImported': 'luoghi saranno importati', + 'journey.frontpage.places': 'luoghi', + 'journey.detail.backToJourney': 'Torna al diario', + 'journey.detail.syncedWithTrips': 'Sincronizzato con i viaggi', + 'journey.detail.addEntry': 'Aggiungi voce', + 'journey.detail.newEntry': 'Nuova voce', + 'journey.detail.editEntry': 'Modifica voce', + 'journey.detail.noEntries': 'Nessuna voce ancora', + 'journey.detail.noEntriesHint': + 'Aggiungi un viaggio per iniziare con voci precompilate', + 'journey.detail.noPhotos': 'Nessuna foto ancora', + 'journey.detail.noPhotosHint': + 'Carica foto nelle voci o sfoglia la tua libreria Immich/Synology', + 'journey.detail.journeyStats': 'Statistiche del diario', + 'journey.detail.syncedTrips': 'Viaggi sincronizzati', + 'journey.detail.noTripsLinked': 'Nessun viaggio collegato ancora', + 'journey.detail.contributors': 'Contributori', + 'journey.detail.readMore': 'Leggi di più', + 'journey.detail.prosCons': 'Pro e contro', + 'journey.detail.photos': 'foto', + 'journey.detail.day': 'Giorno {number}', + 'journey.detail.places': 'luoghi', + 'journey.stats.days': 'Giorni', + 'journey.stats.cities': 'Città', + 'journey.stats.entries': 'Voci', + 'journey.stats.photos': 'Foto', + 'journey.stats.places': 'Luoghi', + 'journey.skeletons.show': 'Mostra suggerimenti', + 'journey.skeletons.hide': 'Nascondi suggerimenti', + 'journey.verdict.lovedIt': 'Adorato', + 'journey.verdict.couldBeBetter': 'Potrebbe essere meglio', + 'journey.synced.places': 'luoghi', + 'journey.synced.synced': 'sincronizzato', + 'journey.editor.discardChangesConfirm': + 'Hai modifiche non salvate. Vuoi scartarle?', + 'journey.editor.uploadFailed': 'Caricamento foto non riuscito', + 'journey.editor.uploadPhotos': 'Carica foto', + 'journey.editor.uploading': 'Caricamento...', + 'journey.editor.uploadingProgress': 'Caricamento {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} di {total} foto non riuscite — salva di nuovo per riprovare', + 'journey.editor.fromGallery': 'Dalla galleria', + 'journey.editor.allPhotosAdded': 'Tutte le foto sono già state aggiunte', + 'journey.editor.writeStory': 'Scrivi la tua storia...', + 'journey.editor.prosCons': 'Pro e contro', + 'journey.editor.pros': 'Pro', + 'journey.editor.cons': 'Contro', + 'journey.editor.proPlaceholder': 'Qualcosa di fantastico...', + 'journey.editor.conPlaceholder': 'Non così fantastico...', + 'journey.editor.addAnother': 'Aggiungi un altro', + 'journey.editor.date': 'Data', + 'journey.editor.location': 'Luogo', + 'journey.editor.searchLocation': 'Cerca luogo...', + 'journey.editor.mood': 'Umore', + 'journey.editor.weather': 'Meteo', + 'journey.editor.photoFirst': '1°', + 'journey.editor.makeFirst': 'Metti 1°', + 'journey.editor.searching': 'Ricerca...', + 'journey.mood.amazing': 'Fantastico', + 'journey.mood.good': 'Buono', + 'journey.mood.neutral': 'Neutro', + 'journey.mood.rough': 'Difficile', + 'journey.weather.sunny': 'Soleggiato', + 'journey.weather.partly': 'Parzialmente nuvoloso', + 'journey.weather.cloudy': 'Nuvoloso', + 'journey.weather.rainy': 'Piovoso', + 'journey.weather.stormy': 'Temporalesco', + 'journey.weather.cold': 'Nevoso', + 'journey.trips.linkTrip': 'Collega viaggio', + 'journey.trips.searchTrip': 'Cerca viaggio', + 'journey.trips.searchPlaceholder': 'Nome del viaggio o destinazione...', + 'journey.trips.noTripsAvailable': 'Nessun viaggio disponibile', + 'journey.trips.link': 'Collega', + 'journey.trips.tripLinked': 'Viaggio collegato', + 'journey.trips.linkFailed': 'Collegamento del viaggio fallito', + 'journey.trips.addTrip': 'Aggiungi viaggio', + 'journey.trips.unlinkTrip': 'Scollega viaggio', + 'journey.trips.unlinkMessage': + 'Scollegare "{title}"? Tutte le voci e le foto sincronizzate da questo viaggio saranno eliminate definitivamente. Questa azione non può essere annullata.', + 'journey.trips.unlink': 'Scollega', + 'journey.trips.tripUnlinked': 'Viaggio scollegato', + 'journey.trips.unlinkFailed': 'Scollegamento del viaggio fallito', + 'journey.trips.noTripsLinkedSettings': 'Nessun viaggio collegato', + 'journey.contributors.invite': 'Invita contributore', + 'journey.contributors.searchUser': 'Cerca utente', + 'journey.contributors.searchPlaceholder': 'Nome utente o e-mail...', + 'journey.contributors.noUsers': 'Nessun utente trovato', + 'journey.contributors.role': 'Ruolo', + 'journey.contributors.added': 'Contributore aggiunto', + 'journey.contributors.addFailed': 'Impossibile aggiungere il contributore', + 'journey.share.publicShare': 'Condivisione pubblica', + 'journey.share.createLink': 'Crea link di condivisione', + 'journey.share.linkCreated': 'Link di condivisione creato', + 'journey.share.createFailed': 'Creazione del link fallita', + 'journey.share.copy': 'Copia', + 'journey.share.copied': 'Copiato!', + 'journey.share.timeline': 'Cronologia', + 'journey.share.gallery': 'Galleria', + 'journey.share.map': 'Mappa', + 'journey.share.removeLink': 'Rimuovi link di condivisione', + 'journey.share.linkDeleted': 'Link di condivisione eliminato', + 'journey.share.deleteFailed': 'Eliminazione fallita', + 'journey.share.updateFailed': 'Aggiornamento fallito', + 'journey.invite.role': 'Ruolo', + 'journey.invite.viewer': 'Visualizzatore', + 'journey.invite.editor': 'Editore', + 'journey.invite.invite': 'Invita', + 'journey.invite.inviting': 'Invito in corso...', + 'journey.settings.title': 'Impostazioni del diario', + 'journey.settings.coverImage': 'Immagine di copertina', + 'journey.settings.changeCover': 'Cambia copertina', + 'journey.settings.addCover': 'Aggiungi immagine di copertina', + 'journey.settings.name': 'Nome', + 'journey.settings.subtitle': 'Sottotitolo', + 'journey.settings.subtitlePlaceholder': 'es. Thailandia, Vietnam e Cambogia', + 'journey.settings.endJourney': 'Archivia il viaggio', + 'journey.settings.reopenJourney': 'Ripristina il viaggio', + 'journey.settings.archived': 'Viaggio archiviato', + 'journey.settings.reopened': 'Viaggio riaperto', + 'journey.settings.endDescription': + 'Nasconde il badge In diretta. Puoi riaprire in qualsiasi momento.', + 'journey.settings.delete': 'Elimina', + 'journey.settings.deleteJourney': 'Elimina diario', + 'journey.settings.deleteMessage': + 'Eliminare "{title}"? Tutte le voci e le foto andranno perse.', + 'journey.settings.saved': 'Impostazioni salvate', + 'journey.settings.saveFailed': 'Salvataggio fallito', + 'journey.settings.coverUpdated': 'Copertina aggiornata', + 'journey.settings.coverFailed': 'Caricamento fallito', + 'journey.settings.failedToDelete': 'Eliminazione non riuscita', + 'journey.entries.deleteTitle': 'Elimina voce', + 'journey.photosUploaded': '{count} foto caricate', + 'journey.photosUploadFailed': 'Alcune foto non sono state caricate', + 'journey.photosAdded': '{count} foto aggiunte', + 'journey.public.notFound': 'Non trovato', + 'journey.public.notFoundMessage': + 'Questo diario non esiste o il link è scaduto.', + 'journey.public.readOnly': 'Sola lettura · Diario pubblico', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Condiviso tramite', + 'journey.public.madeWith': 'Creato con', + 'journey.pdf.journeyBook': 'Diario di viaggio', + 'journey.pdf.madeWith': 'Creato con TREK', + 'journey.pdf.day': 'Giorno', + 'journey.pdf.theEnd': 'Fine', + 'journey.pdf.saveAsPdf': 'Salva come PDF', + 'journey.pdf.pages': 'pagine', + 'journey.picker.tripPeriod': 'Periodo del viaggio', + 'journey.picker.dateRange': 'Intervallo di date', + 'journey.picker.allPhotos': 'Tutte le foto', + 'journey.picker.albums': 'Album', + 'journey.picker.selected': 'selezionati', + 'journey.picker.addTo': 'Aggiungi a', + 'journey.picker.newGallery': 'Nuova galleria', + 'journey.picker.selectAll': 'Seleziona tutto', + 'journey.picker.deselectAll': 'Deseleziona tutto', + 'journey.picker.noAlbums': 'Nessun album trovato', + 'journey.picker.selectDate': 'Seleziona data', + 'journey.picker.search': 'Cerca', +}; +export default journey; diff --git a/shared/src/i18n/it/login.ts b/shared/src/i18n/it/login.ts new file mode 100644 index 00000000..47b8a35b --- /dev/null +++ b/shared/src/i18n/it/login.ts @@ -0,0 +1,96 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Accesso fallito. Controlla le tue credenziali.', + 'login.tagline': 'I tuoi viaggi.\nIl tuo programma.', + 'login.description': + 'Programma viaggi in collaborazione con mappe interattive, budget e sincronizzazione in tempo reale.', + 'login.features.maps': 'Mappe Interattive', + 'login.features.mapsDesc': 'Google Places, percorsi e clustering', + 'login.features.realtime': 'Sincronizzazione in tempo reale', + 'login.features.realtimeDesc': 'Programmate insieme tramite WebSocket', + 'login.features.budget': 'Tracciamento Budget', + 'login.features.budgetDesc': 'Categorie, grafici e costi per persona', + 'login.features.collab': 'Collaborazione', + 'login.features.collabDesc': 'Multi-utente con viaggi condivisi', + 'login.features.packing': 'Lista Valigia', + 'login.features.packingDesc': 'Categorie, progressi e suggerimenti', + 'login.features.bookings': 'Prenotazioni', + 'login.features.bookingsDesc': 'Voli, alloggi, ristoranti e altro', + 'login.features.files': 'Documenti', + 'login.features.filesDesc': 'Carica e gestisci i documenti', + 'login.features.routes': 'Percorsi Intelligenti', + 'login.features.routesDesc': + 'Ottimizzazione automatica ed esportazione su Google Maps', + 'login.selfHosted': 'Self-hosted · Open Source · Your data stays yours', + 'login.title': 'Accedi', + 'login.subtitle': 'Bentornato', + 'login.signingIn': 'Accesso in corso…', + 'login.signIn': 'Accedi', + 'login.createAdmin': 'Crea Account Amministratore', + 'login.createAdminHint': 'Imposta il primo account amministratore per TREK.', + 'login.setNewPassword': 'Imposta nuova password', + 'login.setNewPasswordHint': 'Devi cambiare la password prima di continuare.', + 'login.createAccount': 'Crea Account', + 'login.createAccountHint': 'Registra un nuovo account.', + 'login.creating': 'Creazione in corso…', + 'login.noAccount': 'Non hai un account?', + 'login.hasAccount': 'Hai già un account?', + 'login.register': 'Registrati', + 'login.emailPlaceholder': 'tua@email.com', + 'login.username': 'Nome utente', + 'login.oidc.registrationDisabled': + 'La registrazione è disabilitata. Contatta il tuo amministratore.', + 'login.oidc.noEmail': 'Nessuna email ricevuta dal provider.', + 'login.oidc.tokenFailed': 'Autenticazione fallita.', + 'login.oidc.invalidState': 'Sessione non valida. Riprova.', + 'login.demoFailed': 'Accesso demo fallito', + 'login.oidcSignIn': 'Accedi con {name}', + 'login.oidcOnly': + "L'autenticazione tramite password è disabilitata. Accedi utilizzando il tuo provider SSO.", + 'login.oidcLoggedOut': + 'Sei stato disconnesso. Accedi nuovamente tramite il tuo provider SSO.', + 'login.demoHint': 'Prova la demo — nessuna registrazione necessaria', + 'login.mfaTitle': 'Autenticazione a due fattori', + 'login.mfaSubtitle': + 'Inserisci il codice a 6 cifre dalla tua app authenticator.', + 'login.mfaCodeLabel': 'Codice di verifica', + 'login.mfaCodeRequired': 'Inserisci il codice dalla tua app authenticator.', + 'login.mfaHint': "Apri Google Authenticator, Authy o un'altra app TOTP.", + 'login.mfaBack': "← Torna all'accesso", + 'login.mfaVerify': 'Verifica', + 'login.invalidInviteLink': 'Link di invito non valido o scaduto', + 'login.oidcFailed': 'Accesso OIDC non riuscito', + 'login.usernameRequired': 'Il nome utente è obbligatorio', + 'login.passwordMinLength': 'La password deve contenere almeno 8 caratteri', + 'login.forgotPassword': 'Password dimenticata?', + 'login.forgotPasswordTitle': 'Reimposta la password', + 'login.forgotPasswordBody': + 'Inserisci l’indirizzo email del tuo account. Se esiste un account, invieremo un link per reimpostarla.', + 'login.forgotPasswordSubmit': 'Invia link', + 'login.forgotPasswordSentTitle': 'Controlla la tua email', + 'login.forgotPasswordSentBody': + 'Se esiste un account con questa email, il link è in arrivo. Scade tra 60 minuti.', + 'login.forgotPasswordSmtpHintOff': + 'Nota: il tuo amministratore non ha configurato SMTP, quindi il link di reset verrà scritto nella console del server invece di essere inviato via email.', + 'login.backToLogin': 'Torna all’accesso', + 'login.newPassword': 'Nuova password', + 'login.confirmPassword': 'Conferma nuova password', + 'login.passwordsDontMatch': 'Le password non corrispondono', + 'login.mfaCode': 'Codice 2FA', + 'login.resetPasswordTitle': 'Imposta una nuova password', + 'login.resetPasswordBody': + 'Scegli una password robusta che non hai già usato qui. Minimo 8 caratteri.', + 'login.resetPasswordMfaBody': + 'Inserisci il codice 2FA o un codice di backup per completare il reset.', + 'login.resetPasswordSubmit': 'Reimposta password', + 'login.resetPasswordVerify': 'Verifica e reimposta', + 'login.resetPasswordSuccessTitle': 'Password aggiornata', + 'login.resetPasswordSuccessBody': 'Ora puoi accedere con la nuova password.', + 'login.resetPasswordInvalidLink': 'Link di reset non valido', + 'login.resetPasswordInvalidLinkBody': + 'Il link è mancante o danneggiato. Richiedine uno nuovo per continuare.', + 'login.resetPasswordFailed': + 'Reset non riuscito. Il link potrebbe essere scaduto.', +}; +export default login; diff --git a/shared/src/i18n/it/map.ts b/shared/src/i18n/it/map.ts new file mode 100644 index 00000000..85efe16e --- /dev/null +++ b/shared/src/i18n/it/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Connessioni', + 'map.showConnections': 'Mostra percorsi prenotati', + 'map.hideConnections': 'Nascondi percorsi prenotati', +}; +export default map; diff --git a/shared/src/i18n/it/members.ts b/shared/src/i18n/it/members.ts new file mode 100644 index 00000000..1a05a734 --- /dev/null +++ b/shared/src/i18n/it/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Condividi viaggio', + 'members.inviteUser': 'Invita utente', + 'members.selectUser': 'Seleziona utente...', + 'members.invite': 'Invita', + 'members.allHaveAccess': 'Tutti gli utenti hanno già accesso.', + 'members.access': 'Accesso', + 'members.person': 'persona', + 'members.persons': 'persone', + 'members.you': 'tu', + 'members.owner': 'Proprietario', + 'members.leaveTrip': 'Abbandona viaggio', + 'members.removeAccess': 'Rimuovi accesso', + 'members.confirmLeave': "Abbandonare il viaggio? Perderai l'accesso.", + 'members.confirmRemove': "Rimuovere l'accesso per questo utente?", + 'members.loadError': 'Impossibile caricare i membri', + 'members.added': 'aggiunto', + 'members.addError': 'Impossibile aggiungere', + 'members.removed': 'Membro rimosso', + 'members.removeError': 'Impossibile rimuovere', +}; +export default members; diff --git a/shared/src/i18n/it/memories.ts b/shared/src/i18n/it/memories.ts new file mode 100644 index 00000000..ea92bd30 --- /dev/null +++ b/shared/src/i18n/it/memories.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Foto', + 'memories.notConnected': 'Immich non connesso', + 'memories.notConnectedHint': + 'Connetti la tua istanza Immich nelle Impostazioni per vedere qui le foto del tuo viaggio.', + 'memories.notConnectedMultipleHint': + 'Collega uno di questi provider di foto: {provider_names} nelle Impostazioni per poter aggiungere foto a questo viaggio.', + 'memories.noDates': 'Aggiungi le date al tuo viaggio per caricare le foto.', + 'memories.noPhotos': 'Nessuna foto trovata', + 'memories.noPhotosHint': + "Nessuna foto trovata in Immich per l'intervallo di date di questo viaggio.", + 'memories.photosFound': 'foto', + 'memories.fromOthers': 'da altri', + 'memories.sharePhotos': 'Condividi foto', + 'memories.sharing': 'Condivisione', + 'memories.reviewTitle': 'Rivedi le tue foto', + 'memories.reviewHint': 'Clicca sulle foto per escluderle dalla condivisione.', + 'memories.shareCount': 'Condividi {count} foto', + 'memories.providerUrl': 'URL del server', + 'memories.providerApiKey': 'Chiave API', + 'memories.providerUsername': 'Nome utente', + 'memories.providerPassword': 'Password', + 'memories.providerOTP': 'Codice MFA (se abilitato)', + 'memories.skipSSLVerification': 'Ignora la verifica del certificato SSL', + 'memories.immichAutoUpload': + 'Rispecchia le foto del journey su Immich al caricamento', + 'memories.providerUrlHintSynology': + "Includi il percorso dell'app Foto nell'URL, es. https://nas:5001/photo", + 'memories.testConnection': 'Test connessione', + 'memories.testShort': 'Prova', + 'memories.testFirst': 'Testa prima la connessione', + 'memories.connected': 'Connesso', + 'memories.disconnected': 'Non connesso', + 'memories.connectionSuccess': 'Connesso a Immich', + 'memories.connectionError': 'Impossibile connettersi a Immich', + 'memories.saved': 'Impostazioni {provider_name} salvate', + 'memories.providerDisconnectedBanner': + 'La connessione a {provider_name} è persa. Riconnetti nelle Impostazioni per visualizzare le foto.', + 'memories.saveError': + 'Impossibile salvare le impostazioni di {provider_name}', + 'memories.saveRouteNotConfigured': + 'La route di salvataggio non è configurata per questo provider', + 'memories.testRouteNotConfigured': + 'La route di test non è configurata per questo provider', + 'memories.fillRequiredFields': 'Per favore compila tutti i campi obbligatori', + 'memories.addPhotos': 'Aggiungi foto', + 'memories.linkAlbum': 'Collega album', + 'memories.selectAlbum': 'Seleziona album Immich', + 'memories.selectAlbumMultiple': 'Seleziona album', + 'memories.noAlbums': 'Nessun album trovato', + 'memories.syncAlbum': 'Sincronizza album', + 'memories.unlinkAlbum': 'Scollega', + 'memories.photos': 'foto', + 'memories.selectPhotos': 'Seleziona foto da Immich', + 'memories.selectPhotosMultiple': 'Seleziona foto', + 'memories.selectHint': 'Tocca le foto per selezionarle.', + 'memories.selected': 'selezionate', + 'memories.addSelected': 'Aggiungi {count} foto', + 'memories.alreadyAdded': 'Aggiunta', + 'memories.private': 'Privato', + 'memories.stopSharing': 'Interrompi condivisione', + 'memories.oldest': 'Prima le più vecchie', + 'memories.newest': 'Prima le più recenti', + 'memories.allLocations': 'Tutte le posizioni', + 'memories.tripDates': 'Date del viaggio', + 'memories.allPhotos': 'Tutte le foto', + 'memories.confirmShareTitle': 'Condividere con i membri del viaggio?', + 'memories.confirmShareHint': + '{count} foto saranno visibili a tutti i membri di questo viaggio. Potrai rendere private le singole foto in seguito.', + 'memories.confirmShareButton': 'Condividi foto', + 'memories.error.loadAlbums': 'Caricamento album non riuscito', + 'memories.error.linkAlbum': 'Collegamento album non riuscito', + 'memories.error.unlinkAlbum': 'Scollegamento album non riuscito', + 'memories.error.syncAlbum': 'Sincronizzazione album non riuscita', + 'memories.error.loadPhotos': 'Caricamento foto non riuscito', + 'memories.error.addPhotos': 'Aggiunta foto non riuscita', + 'memories.error.removePhoto': 'Rimozione foto non riuscita', + 'memories.error.toggleSharing': 'Aggiornamento condivisione non riuscito', +}; +export default memories; diff --git a/shared/src/i18n/it/nav.ts b/shared/src/i18n/it/nav.ts new file mode 100644 index 00000000..0d4b09c0 --- /dev/null +++ b/shared/src/i18n/it/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Viaggio', + 'nav.share': 'Condividi', + 'nav.settings': 'Impostazioni', + 'nav.admin': 'Amministrazione', + 'nav.logout': 'Esci', + 'nav.lightMode': 'Modalità chiara', + 'nav.darkMode': 'Modalità scura', + 'nav.autoMode': 'Modalità automatica', + 'nav.administrator': 'Amministratore', + 'nav.myTrips': 'I miei viaggi', + 'nav.profile': 'Profilo', + 'nav.bottomSettings': 'Impostazioni', + 'nav.bottomAdmin': 'Amministrazione', + 'nav.bottomLogout': 'Disconnetti', + 'nav.bottomAdminBadge': 'Admin', +}; +export default nav; diff --git a/shared/src/i18n/it/notif.ts b/shared/src/i18n/it/notif.ts new file mode 100644 index 00000000..5f4cdeb5 --- /dev/null +++ b/shared/src/i18n/it/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notifica', + 'notif.test.simple.text': 'Questa è una semplice notifica di test.', + 'notif.test.boolean.text': 'Accetti questa notifica di test?', + 'notif.test.navigate.text': 'Clicca qui sotto per accedere alla dashboard.', + 'notif.trip_invite.title': 'Invito al viaggio', + 'notif.trip_invite.text': '{actor} ti ha invitato a {trip}', + 'notif.booking_change.title': 'Prenotazione aggiornata', + 'notif.booking_change.text': + '{actor} ha aggiornato una prenotazione in {trip}', + 'notif.trip_reminder.title': 'Promemoria viaggio', + 'notif.trip_reminder.text': 'Il tuo viaggio {trip} si avvicina!', + 'notif.todo_due.title': 'Attività in scadenza', + 'notif.todo_due.text': '{todo} in {trip} scade il {due}', + 'notif.vacay_invite.title': 'Invito Vacay Fusion', + 'notif.vacay_invite.text': '{actor} ti ha invitato a fondere i piani vacanza', + 'notif.photos_shared.title': 'Foto condivise', + 'notif.photos_shared.text': '{actor} ha condiviso {count} foto in {trip}', + 'notif.collab_message.title': 'Nuovo messaggio', + 'notif.collab_message.text': '{actor} ha inviato un messaggio in {trip}', + 'notif.packing_tagged.title': 'Assegnazione bagagli', + 'notif.packing_tagged.text': '{actor} ti ha assegnato a {category} in {trip}', + 'notif.version_available.title': 'Nuova versione disponibile', + 'notif.version_available.text': 'TREK {version} è ora disponibile', + 'notif.action.view_trip': 'Vedi viaggio', + 'notif.action.view_collab': 'Vedi messaggi', + 'notif.action.view_packing': 'Vedi bagagli', + 'notif.action.view_photos': 'Vedi foto', + 'notif.action.view_vacay': 'Vedi Vacay', + 'notif.action.view_admin': "Vai all'admin", + 'notif.action.view': 'Vedi', + 'notif.action.accept': 'Accetta', + 'notif.action.decline': 'Rifiuta', + 'notif.generic.title': 'Notifica', + 'notif.generic.text': 'Hai una nuova notifica', + 'notif.dev.unknown_event.title': '[DEV] Evento sconosciuto', + 'notif.dev.unknown_event.text': + 'Il tipo di evento "{event}" non è registrato in EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/it/notifications.ts b/shared/src/i18n/it/notifications.ts new file mode 100644 index 00000000..d5ebace8 --- /dev/null +++ b/shared/src/i18n/it/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notifiche', + 'notifications.markAllRead': 'Segna tutto come letto', + 'notifications.deleteAll': 'Elimina tutto', + 'notifications.showAll': 'Vedi tutte le notifiche', + 'notifications.empty': 'Nessuna notifica', + 'notifications.emptyDescription': 'Sei aggiornato!', + 'notifications.all': 'Tutte', + 'notifications.unreadOnly': 'Non lette', + 'notifications.markRead': 'Segna come letto', + 'notifications.markUnread': 'Segna come non letto', + 'notifications.delete': 'Elimina', + 'notifications.system': 'Sistema', + 'notifications.synologySessionCleared.title': 'Synology Photos disconnesso', + 'notifications.synologySessionCleared.text': + "Il server o l'account è cambiato — vai alle Impostazioni per testare nuovamente la connessione.", + 'notifications.test.title': 'Notifica di test da {actor}', + 'notifications.test.text': 'Questa è una semplice notifica di test.', + 'notifications.test.booleanTitle': '{actor} richiede la tua approvazione', + 'notifications.test.booleanText': 'Notifica di test con risposta.', + 'notifications.test.accept': 'Approva', + 'notifications.test.decline': 'Rifiuta', + 'notifications.test.navigateTitle': "Dai un'occhiata", + 'notifications.test.navigateText': 'Notifica di test con navigazione.', + 'notifications.test.goThere': 'Vai', + 'notifications.test.adminTitle': 'Comunicazione admin', + 'notifications.test.adminText': + '{actor} ha inviato una notifica di test a tutti gli amministratori.', + 'notifications.test.tripTitle': '{actor} ha pubblicato nel tuo viaggio', + 'notifications.test.tripText': 'Notifica di test per il viaggio "{trip}".', + 'notifications.versionAvailable.title': 'Aggiornamento disponibile', + 'notifications.versionAvailable.text': 'TREK {version} è ora disponibile.', + 'notifications.versionAvailable.button': 'Visualizza dettagli', +}; +export default notifications; diff --git a/shared/src/i18n/it/oauth.ts b/shared/src/i18n/it/oauth.ts new file mode 100644 index 00000000..c3910b40 --- /dev/null +++ b/shared/src/i18n/it/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Viaggi', + 'oauth.scope.group.places': 'Luoghi', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Bagagli', + 'oauth.scope.group.todos': 'Attività', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Prenotazioni', + 'oauth.scope.group.collab': 'Collaborazione', + 'oauth.scope.group.notifications': 'Notifiche', + 'oauth.scope.group.vacay': 'Ferie', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Meteo', + 'oauth.scope.group.journey': 'Diario di viaggio', + 'oauth.scope.trips:read.label': 'Visualizza viaggi e itinerari', + 'oauth.scope.trips:read.description': + 'Leggi viaggi, giorni, note giornaliere e membri', + 'oauth.scope.trips:write.label': 'Modifica viaggi e itinerari', + 'oauth.scope.trips:write.description': + 'Crea e aggiorna viaggi, giorni, note e gestisci membri', + 'oauth.scope.trips:delete.label': 'Elimina viaggi', + 'oauth.scope.trips:delete.description': + 'Elimina definitivamente interi viaggi — questa azione è irreversibile', + 'oauth.scope.trips:share.label': 'Gestisci link di condivisione', + 'oauth.scope.trips:share.description': + 'Crea, aggiorna e revoca link di condivisione pubblici per i viaggi', + 'oauth.scope.places:read.label': 'Visualizza luoghi e dati mappa', + 'oauth.scope.places:read.description': + 'Leggi luoghi, assegnazioni giornaliere, tag e categorie', + 'oauth.scope.places:write.label': 'Gestisci luoghi', + 'oauth.scope.places:write.description': + 'Crea, aggiorna ed elimina luoghi, assegnazioni e tag', + 'oauth.scope.atlas:read.label': 'Visualizza Atlas', + 'oauth.scope.atlas:read.description': + 'Leggi paesi visitati, regioni e lista dei desideri', + 'oauth.scope.atlas:write.label': 'Gestisci Atlas', + 'oauth.scope.atlas:write.description': + 'Segna paesi e regioni come visitati, gestisci la lista dei desideri', + 'oauth.scope.packing:read.label': 'Visualizza liste bagagli', + 'oauth.scope.packing:read.description': + 'Leggi articoli, borse e assegnatari di categoria', + 'oauth.scope.packing:write.label': 'Gestisci liste bagagli', + 'oauth.scope.packing:write.description': + 'Aggiungi, aggiorna, elimina, spunta e riordina articoli e borse', + 'oauth.scope.todos:read.label': 'Visualizza liste attività', + 'oauth.scope.todos:read.description': + 'Leggi attività del viaggio e assegnatari di categoria', + 'oauth.scope.todos:write.label': 'Gestisci liste attività', + 'oauth.scope.todos:write.description': + 'Crea, aggiorna, spunta, elimina e riordina attività', + 'oauth.scope.budget:read.label': 'Visualizza budget', + 'oauth.scope.budget:read.description': + 'Leggi voci di budget e ripartizione delle spese', + 'oauth.scope.budget:write.label': 'Gestisci budget', + 'oauth.scope.budget:write.description': + 'Crea, aggiorna ed elimina voci di budget', + 'oauth.scope.reservations:read.label': 'Visualizza prenotazioni', + 'oauth.scope.reservations:read.description': + 'Leggi prenotazioni e dettagli alloggio', + 'oauth.scope.reservations:write.label': 'Gestisci prenotazioni', + 'oauth.scope.reservations:write.description': + 'Crea, aggiorna, elimina e riordina prenotazioni', + 'oauth.scope.collab:read.label': 'Visualizza collaborazione', + 'oauth.scope.collab:read.description': + 'Leggi note collaborative, sondaggi e messaggi', + 'oauth.scope.collab:write.label': 'Gestisci collaborazione', + 'oauth.scope.collab:write.description': + 'Crea, aggiorna ed elimina note collaborative, sondaggi e messaggi', + 'oauth.scope.notifications:read.label': 'Visualizza notifiche', + 'oauth.scope.notifications:read.description': + 'Leggi notifiche in-app e conteggi non letti', + 'oauth.scope.notifications:write.label': 'Gestisci notifiche', + 'oauth.scope.notifications:write.description': + 'Segna notifiche come lette e rispondi', + 'oauth.scope.vacay:read.label': 'Visualizza piani ferie', + 'oauth.scope.vacay:read.description': + 'Leggi dati di pianificazione ferie, voci e statistiche', + 'oauth.scope.vacay:write.label': 'Gestisci piani ferie', + 'oauth.scope.vacay:write.description': + 'Crea e gestisci voci ferie, festività e piani del team', + 'oauth.scope.geo:read.label': 'Mappe e geocodifica', + 'oauth.scope.geo:read.description': + 'Cerca luoghi, risolvi URL mappa e geocodifica inversa coordinate', + 'oauth.scope.weather:read.label': 'Previsioni meteo', + 'oauth.scope.weather:read.description': + 'Ottieni previsioni meteo per luoghi e date del viaggio', + 'oauth.scope.journey:read.label': 'Visualizza diari di viaggio', + 'oauth.scope.journey:read.description': + 'Leggi diari di viaggio, voci e lista dei collaboratori', + 'oauth.scope.journey:write.label': 'Gestisci diari di viaggio', + 'oauth.scope.journey:write.description': + 'Crea, aggiorna ed elimina diari di viaggio e le loro voci', + 'oauth.scope.journey:share.label': 'Gestisci link diari di viaggio', + 'oauth.scope.journey:share.description': + 'Crea, aggiorna e revoca link di condivisione pubblici per i diari di viaggio', +}; +export default oauth; diff --git a/shared/src/i18n/it/packing.ts b/shared/src/i18n/it/packing.ts new file mode 100644 index 00000000..b1111efd --- /dev/null +++ b/shared/src/i18n/it/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Lista valigia', + 'packing.empty': 'La lista valigia è vuota', + 'packing.import': 'Importa', + 'packing.importTitle': 'Importa lista valigia', + 'packing.importHint': + 'Un elemento per riga. Formato: Categoria, Nome, Peso in g (opzionale), Borsa (opzionale), checked/unchecked (opzionale)', + 'packing.importPlaceholder': + 'Igiene, Spazzolino\nAbbigliamento, Magliette, 200\nDocumenti, Passaporto, , Bagaglio a mano\nElettronica, Caricabatterie, 50, Valigia, checked', + 'packing.importCsv': 'Carica CSV/TXT', + 'packing.importAction': 'Importa {count}', + 'packing.importSuccess': '{count} elementi importati', + 'packing.importError': 'Importazione non riuscita', + 'packing.importEmpty': 'Nessun elemento da importare', + 'packing.progress': '{packed} di {total} in valigia ({percent}%)', + 'packing.clearChecked': 'Rimuovi {count} spuntati', + 'packing.clearCheckedShort': 'Rimuovi {count}', + 'packing.suggestions': 'Suggerimenti', + 'packing.suggestionsTitle': 'Aggiungi suggerimenti', + 'packing.allSuggested': 'Tutti i suggerimenti aggiunti', + 'packing.allPacked': 'Tutto in valigia!', + 'packing.addPlaceholder': 'Aggiungi nuovo elemento...', + 'packing.categoryPlaceholder': 'Categoria...', + 'packing.filterAll': 'Tutti', + 'packing.filterOpen': 'Da fare', + 'packing.filterDone': 'Fatto', + 'packing.emptyTitle': 'La lista valigia è vuota', + 'packing.emptyHint': 'Aggiungi elementi o usa i suggerimenti', + 'packing.emptyFiltered': 'Nessun elemento corrisponde a questo filtro', + 'packing.menuRename': 'Rinomina', + 'packing.menuCheckAll': 'Seleziona tutti', + 'packing.menuUncheckAll': 'Deseleziona tutti', + 'packing.menuDeleteCat': 'Elimina categoria', + 'packing.noMembers': 'Nessun membro del viaggio', + 'packing.addItem': 'Aggiungi elemento', + 'packing.addItemPlaceholder': 'Nome elemento...', + 'packing.addCategory': 'Aggiungi categoria', + 'packing.newCategoryPlaceholder': 'Nome categoria (es. Abbigliamento)', + 'packing.applyTemplate': 'Applica modello', + 'packing.template': 'Modello', + 'packing.templateApplied': '{count} elementi aggiunti dal modello', + 'packing.templateError': 'Impossibile applicare il modello', + 'packing.saveAsTemplate': 'Salva come modello', + 'packing.templateName': 'Nome modello', + 'packing.templateSaved': 'Lista bagagli salvata come modello', + 'packing.bags': 'Valigie', + 'packing.noBag': 'Non assegnato', + 'packing.totalWeight': 'Peso totale', + 'packing.bagName': 'Nome valigia...', + 'packing.addBag': 'Aggiungi valigia', + 'packing.changeCategory': 'Cambia categoria', + 'packing.confirm.clearChecked': + 'Sei sicuro di voler rimuovere {count} elementi spuntati?', + 'packing.confirm.deleteCat': + 'Sei sicuro di voler eliminare la categoria "{name}" con {count} elementi?', + 'packing.defaultCategory': 'Altro', + 'packing.toast.saveError': 'Impossibile salvare', + 'packing.toast.deleteError': 'Impossibile eliminare', + 'packing.toast.renameError': 'Impossibile rinominare', + 'packing.toast.addError': 'Impossibile aggiungere', + 'packing.suggestions.items': [ + { + name: 'Passaporto', + category: 'Documenti', + }, + { + name: "Carta d'identità", + category: 'Documenti', + }, + { + name: 'Assicurazione di viaggio', + category: 'Documenti', + }, + { + name: 'Biglietti aerei', + category: 'Documenti', + }, + { + name: 'Carta di credito', + category: 'Finanze', + }, + { + name: 'Contanti', + category: 'Finanze', + }, + { + name: 'Visto', + category: 'Documenti', + }, + { + name: 'Magliette', + category: 'Abbigliamento', + }, + { + name: 'Pantaloni', + category: 'Abbigliamento', + }, + { + name: 'Intimo', + category: 'Abbigliamento', + }, + { + name: 'Calzini', + category: 'Abbigliamento', + }, + { + name: 'Giacca', + category: 'Abbigliamento', + }, + { + name: 'Pigiama', + category: 'Abbigliamento', + }, + { + name: 'Costume da bagno', + category: 'Abbigliamento', + }, + { + name: 'Giacca a vento', + category: 'Abbigliamento', + }, + { + name: 'Scarpe comode', + category: 'Abbigliamento', + }, + { + name: 'Spazzolino da denti', + category: 'Igiene personale', + }, + { + name: 'Dentifricio', + category: 'Igiene personale', + }, + { + name: 'Shampoo', + category: 'Igiene personale', + }, + { + name: 'Deodorante', + category: 'Igiene personale', + }, + { + name: 'Crema solare', + category: 'Igiene personale', + }, + { + name: 'Rasoio', + category: 'Igiene personale', + }, + { + name: 'Caricabatterie', + category: 'Elettronica', + }, + { + name: 'Power bank', + category: 'Elettronica', + }, + { + name: 'Cuffie', + category: 'Elettronica', + }, + { + name: 'Adattatore da viaggio', + category: 'Elettronica', + }, + { + name: 'Macchina fotografica', + category: 'Elettronica', + }, + { + name: 'Antidolorifici', + category: 'Salute', + }, + { + name: 'Cerotti', + category: 'Salute', + }, + { + name: 'Disinfettante', + category: 'Salute', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/it/pdf.ts b/shared/src/i18n/it/pdf.ts new file mode 100644 index 00000000..57c07fd7 --- /dev/null +++ b/shared/src/i18n/it/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Programma di viaggio', + 'pdf.planned': 'Programmato', + 'pdf.costLabel': 'Costo EUR', + 'pdf.preview': 'Anteprima PDF', + 'pdf.saveAsPdf': 'Salva come PDF', +}; +export default pdf; diff --git a/shared/src/i18n/it/perm.ts b/shared/src/i18n/it/perm.ts new file mode 100644 index 00000000..e7232dbb --- /dev/null +++ b/shared/src/i18n/it/perm.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Impostazioni dei permessi', + 'perm.subtitle': "Controlla chi può eseguire azioni nell'applicazione", + 'perm.saved': 'Impostazioni dei permessi salvate', + 'perm.resetDefaults': 'Ripristina predefiniti', + 'perm.customized': 'personalizzato', + 'perm.level.admin': 'Solo amministratore', + 'perm.level.tripOwner': 'Proprietario del viaggio', + 'perm.level.tripMember': 'Membri del viaggio', + 'perm.level.everybody': 'Tutti', + 'perm.cat.trip': 'Gestione viaggi', + 'perm.cat.members': 'Gestione membri', + 'perm.cat.files': 'File', + 'perm.cat.content': 'Contenuti e programma', + 'perm.cat.extras': 'Budget, bagagli e collaborazione', + 'perm.action.trip_create': 'Creare viaggi', + 'perm.action.trip_edit': 'Modificare dettagli del viaggio', + 'perm.action.trip_delete': 'Eliminare viaggi', + 'perm.action.trip_archive': 'Archiviare / dearchiviare viaggi', + 'perm.action.trip_cover_upload': 'Caricare immagine di copertina', + 'perm.action.member_manage': 'Aggiungere / rimuovere membri', + 'perm.action.file_upload': 'Caricare file', + 'perm.action.file_edit': 'Modificare metadati dei file', + 'perm.action.file_delete': 'Eliminare file', + 'perm.action.place_edit': 'Aggiungere / modificare / eliminare luoghi', + 'perm.action.day_edit': 'Modificare giorni, note e assegnazioni', + 'perm.action.reservation_edit': 'Gestire prenotazioni', + 'perm.action.budget_edit': 'Gestire budget', + 'perm.action.packing_edit': 'Gestire liste bagagli', + 'perm.action.collab_edit': 'Collaborazione (note, sondaggi, chat)', + 'perm.action.share_manage': 'Gestire link di condivisione', + 'perm.actionHint.trip_create': 'Chi può creare nuovi viaggi', + 'perm.actionHint.trip_edit': + 'Chi può modificare nome, date, descrizione e valuta del viaggio', + 'perm.actionHint.trip_delete': 'Chi può eliminare definitivamente un viaggio', + 'perm.actionHint.trip_archive': + 'Chi può archiviare o dearchiviare un viaggio', + 'perm.actionHint.trip_cover_upload': + "Chi può caricare o modificare l'immagine di copertina", + 'perm.actionHint.member_manage': + 'Chi può invitare o rimuovere membri del viaggio', + 'perm.actionHint.file_upload': 'Chi può caricare file in un viaggio', + 'perm.actionHint.file_edit': 'Chi può modificare descrizioni e link dei file', + 'perm.actionHint.file_delete': + 'Chi può spostare file nel cestino o eliminarli definitivamente', + 'perm.actionHint.place_edit': + 'Chi può aggiungere, modificare o eliminare luoghi', + 'perm.actionHint.day_edit': + 'Chi può modificare giorni, note dei giorni e assegnazioni dei luoghi', + 'perm.actionHint.reservation_edit': + 'Chi può creare, modificare o eliminare prenotazioni', + 'perm.actionHint.budget_edit': + 'Chi può creare, modificare o eliminare voci di budget', + 'perm.actionHint.packing_edit': + 'Chi può gestire articoli da bagaglio e borse', + 'perm.actionHint.collab_edit': + 'Chi può creare note, sondaggi e inviare messaggi', + 'perm.actionHint.share_manage': + 'Chi può creare o eliminare link di condivisione pubblici', +}; +export default perm; diff --git a/shared/src/i18n/it/photos.ts b/shared/src/i18n/it/photos.ts new file mode 100644 index 00000000..55ed2ac2 --- /dev/null +++ b/shared/src/i18n/it/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Foto', + 'photos.subtitle': '{count} foto per {trip}', + 'photos.dropHere': 'Trascina le foto qui...', + 'photos.dropHereActive': 'Trascina le foto qui', + 'photos.captionForAll': 'Didascalia (per tutti)', + 'photos.captionPlaceholder': 'Didascalia opzionale...', + 'photos.addCaption': 'Aggiungi didascalia...', + 'photos.allDays': 'Tutti i giorni', + 'photos.noPhotos': 'Ancora nessuna foto', + 'photos.uploadHint': 'Carica le foto del tuo viaggio', + 'photos.clickToSelect': 'o clicca per selezionare', + 'photos.linkPlace': 'Collega luogo', + 'photos.noPlace': 'Nessun luogo', + 'photos.uploadN': 'Caricamento di {n} foto', + 'photos.linkDay': 'Collega giorno', + 'photos.noDay': 'Nessun giorno', + 'photos.dayLabel': 'Giorno {number}', + 'photos.photoSelected': 'Foto selezionata', + 'photos.photosSelected': 'Foto selezionate', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · fino a 30 foto', +}; +export default photos; diff --git a/shared/src/i18n/it/places.ts b/shared/src/i18n/it/places.ts new file mode 100644 index 00000000..cf9728a9 --- /dev/null +++ b/shared/src/i18n/it/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Aggiungi Luogo/Attività', + 'places.importFile': 'Importa file', + 'places.sidebarDrop': 'Rilascia per importare', + 'places.importFileHint': + 'Importa file .gpx, .kml o .kmz da strumenti come Google My Maps, Google Earth o un tracker GPS.', + 'places.importFileDropHere': + 'Clicca per selezionare un file o trascina e rilascia qui', + 'places.importFileDropActive': 'Rilascia il file per selezionarlo', + 'places.importFileUnsupported': + 'Tipo di file non supportato. Usa .gpx, .kml o .kmz.', + 'places.importFileTooLarge': + 'Il file è troppo grande. La dimensione massima di caricamento è {maxMb} MB.', + 'places.importFileError': 'Importazione non riuscita', + 'places.importAllSkipped': 'Tutti i luoghi erano già nel viaggio.', + 'places.gpxImported': '{count} luoghi importati da GPX', + 'places.gpxImportTypes': 'Cosa vuoi importare?', + 'places.gpxImportWaypoints': 'Waypoint', + 'places.gpxImportRoutes': 'Percorsi', + 'places.gpxImportTracks': 'Tracce (con geometria percorso)', + 'places.gpxImportNoneSelected': 'Seleziona almeno un tipo da importare.', + 'places.kmlImportTypes': 'Cosa vuoi importare?', + 'places.kmlImportPoints': 'Punti (Placemarks)', + 'places.kmlImportPaths': 'Percorsi (LineStrings)', + 'places.kmlImportNoneSelected': 'Seleziona almeno un tipo.', + 'places.selectionCount': '{count} selezionato/i', + 'places.deleteSelected': 'Elimina selezionati', + 'places.kmlKmzImported': '{count} luoghi importati da KMZ/KML', + 'places.urlResolved': "Luogo importato dall'URL", + 'places.importList': 'Importa lista', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Importati: {created} • Saltati: {skipped}', + 'places.importGoogleList': 'Lista Google', + 'places.importNaverList': 'Lista Naver', + 'places.googleListHint': + 'Incolla un link condiviso di una lista Google Maps per importare tutti i luoghi.', + 'places.googleListImported': '{count} luoghi importati da "{list}"', + 'places.googleListError': 'Importazione lista Google Maps non riuscita', + 'places.naverListHint': + 'Incolla un link condiviso di una lista Naver Maps per importare tutti i luoghi.', + 'places.naverListImported': '{count} luoghi importati da "{list}"', + 'places.naverListError': 'Importazione lista Naver Maps non riuscita', + 'places.viewDetails': 'Visualizza dettagli', + 'places.assignToDay': 'A quale giorno aggiungere?', + 'places.all': 'Tutti', + 'places.unplanned': 'Non pianificati', + 'places.filterTracks': 'Tracce', + 'places.search': 'Cerca luoghi...', + 'places.allCategories': 'Tutte le categorie', + 'places.categoriesSelected': 'categorie', + 'places.clearFilter': 'Cancella filtro', + 'places.count': '{count} luoghi', + 'places.countSingular': '1 luogo', + 'places.allPlanned': 'Tutti i luoghi sono programmati', + 'places.noneFound': 'Nessun luogo trovato', + 'places.editPlace': 'Modifica luogo', + 'places.formName': 'Nome', + 'places.formNamePlaceholder': 'es. Torre Eiffel', + 'places.formDescription': 'Descrizione', + 'places.formDescriptionPlaceholder': 'Breve descrizione...', + 'places.formAddress': 'Indirizzo', + 'places.formAddressPlaceholder': 'Via, Città, Paese', + 'places.formLat': 'Latitudine (es. 48.8566)', + 'places.formLng': 'Longitudine (es. 2.3522)', + 'places.formCategory': 'Categoria', + 'places.noCategory': 'Nessuna categoria', + 'places.categoryNamePlaceholder': 'Nome categoria', + 'places.formTime': 'Ora', + 'places.startTime': 'Inizio', + 'places.endTime': 'Fine', + 'places.endTimeBeforeStart': "L'ora di fine è precedente all'ora di inizio", + 'places.timeCollision': 'Sovrapposizione di orario con:', + 'places.formWebsite': 'Sito web', + 'places.formNotes': 'Note', + 'places.formNotesPlaceholder': 'Note personali...', + 'places.formReservation': 'Prenotazione', + 'places.reservationNotesPlaceholder': + 'Note della prenotazione, numero di conferma...', + 'places.mapsSearchPlaceholder': 'Cerca luoghi...', + 'places.mapsSearchError': 'Impossibile cercare i luoghi.', + 'places.loadingDetails': 'Caricamento dettagli del luogo…', + 'places.osmHint': + 'Uso della ricerca OpenStreetMap (senza foto, orari di apertura o valutazioni). Aggiungi una chiave API Google nelle impostazioni per i dettagli completi.', + 'places.osmActive': + 'Ricerca tramite OpenStreetMap (senza foto, valutazioni o orari di apertura). Aggiungi una chiave API Google nelle Impostazioni per dati avanzati.', + 'places.categoryCreateError': 'Impossibile creare la categoria', + 'places.nameRequired': 'Inserisci un nome', + 'places.saveError': 'Impossibile salvare', +}; +export default places; diff --git a/shared/src/i18n/it/planner.ts b/shared/src/i18n/it/planner.ts new file mode 100644 index 00000000..a588205b --- /dev/null +++ b/shared/src/i18n/it/planner.ts @@ -0,0 +1,69 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Luoghi', + 'planner.bookings': 'Prenotazioni', + 'planner.packingList': 'Lista valigia', + 'planner.documents': 'Documenti', + 'planner.dayPlan': 'Programma giornaliero', + 'planner.reservations': 'Prenotazioni', + 'planner.minTwoPlaces': 'Servono almeno 2 luoghi con coordinate', + 'planner.noGeoPlaces': 'Nessun luogo con coordinate disponibile', + 'planner.routeCalculated': 'Percorso calcolato', + 'planner.routeCalcFailed': 'Il percorso non è stato calcolato', + 'planner.routeError': 'Errore nel calcolo del percorso', + 'planner.icsExportFailed': 'Esportazione ICS non riuscita', + 'planner.routeOptimized': 'Percorso ottimizzato', + 'planner.reservationUpdated': 'Prenotazione aggiornata', + 'planner.reservationAdded': 'Prenotazione aggiunta', + 'planner.confirmDeleteReservation': 'Eliminare la prenotazione?', + 'planner.reservationDeleted': 'Prenotazione eliminata', + 'planner.days': 'Giorni', + 'planner.allPlaces': 'Tutti i luoghi', + 'planner.totalPlaces': '{n} luoghi in totale', + 'planner.noDaysPlanned': 'Nessun giorno ancora programmato', + 'planner.editTrip': 'Modifica viaggio →', + 'planner.placeOne': '1 luogo', + 'planner.placeN': '{n} luoghi', + 'planner.addNote': 'Aggiungi nota', + 'planner.noEntries': 'Nessuna voce per questo giorno', + 'planner.addPlace': 'Aggiungi luogo/attività', + 'planner.addPlaceShort': '+ Aggiungi luogo/attività', + 'planner.resPending': 'Prenotazione in attesa · ', + 'planner.resConfirmed': 'Prenotazione confermata · ', + 'planner.notePlaceholder': 'Nota…', + 'planner.noteTimePlaceholder': 'Ora (opzionale)', + 'planner.noteExamplePlaceholder': + 'es. S3 alle 14:30 dalla stazione centrale, traghetto dal molo 7, pausa pranzo…', + 'planner.totalCost': 'Costo totale', + 'planner.searchPlaces': 'Cerca luoghi…', + 'planner.allCategories': 'Tutte le categorie', + 'planner.noPlacesFound': 'Nessun luogo trovato', + 'planner.addFirstPlace': 'Aggiungi primo luogo', + 'planner.noReservations': 'Nessuna prenotazione', + 'planner.addFirstReservation': 'Aggiungi prima prenotazione', + 'planner.new': 'Nuovo', + 'planner.addToDay': '+ Giorno', + 'planner.calculating': 'Calcolo in corso…', + 'planner.route': 'Percorso', + 'planner.optimize': 'Ottimizza', + 'planner.openGoogleMaps': 'Apri in Google Maps', + 'planner.selectDayHint': + "Seleziona un giorno dall'elenco a sinistra per vedere il programma", + 'planner.noPlacesForDay': 'Ancora nessun luogo per questo giorno', + 'planner.addPlacesLink': 'Aggiungi luoghi →', + 'planner.minTotal': 'min. totali', + 'planner.noReservation': 'Nessuna prenotazione', + 'planner.removeFromDay': 'Rimuovi dal giorno', + 'planner.addToThisDay': 'Aggiungi al giorno', + 'planner.overview': 'Panoramica', + 'planner.noDays': 'Ancora nessun giorno', + 'planner.editTripToAddDays': 'Modifica viaggio per aggiungere giorni', + 'planner.dayCount': '{n} Giorni', + 'planner.clickToUnlock': 'Clicca per sbloccare', + 'planner.keepPosition': + "Mantieni la posizione durante l'ottimizzazione del percorso", + 'planner.dayDetails': 'Dettagli del giorno', + 'planner.dayN': 'Giorno {n}', +}; +export default planner; diff --git a/shared/src/i18n/it/register.ts b/shared/src/i18n/it/register.ts new file mode 100644 index 00000000..9b3a66c9 --- /dev/null +++ b/shared/src/i18n/it/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Le password non corrispondono', + 'register.passwordTooShort': 'La password deve contenere almeno 8 caratteri', + 'register.failed': 'Registrazione fallita', + 'register.getStarted': 'Inizia', + 'register.subtitle': + 'Crea un account e inizia a programmare i viaggi dei tuoi sogni.', + 'register.feature1': 'Piani di viaggio illimitati', + 'register.feature2': 'Vista mappa interattiva', + 'register.feature3': 'Gestisci luoghi e categorie', + 'register.feature4': 'Traccia le prenotazioni', + 'register.feature5': 'Crea liste per la valigia', + 'register.feature6': 'Archivia foto e file', + 'register.createAccount': 'Crea Account', + 'register.startPlanning': 'Inizia a programmare il tuo viaggio', + 'register.minChars': 'Min. 6 caratteri', + 'register.confirmPassword': 'Conferma Password', + 'register.repeatPassword': 'Ripeti password', + 'register.registering': 'Registrazione in corso...', + 'register.register': 'Registrati', + 'register.hasAccount': 'Hai già un account?', + 'register.signIn': 'Accedi', +}; +export default register; diff --git a/shared/src/i18n/it/reservations.ts b/shared/src/i18n/it/reservations.ts new file mode 100644 index 00000000..876ef4f5 --- /dev/null +++ b/shared/src/i18n/it/reservations.ts @@ -0,0 +1,120 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Prenotazioni', + 'reservations.empty': 'Ancora nessuna prenotazione', + 'reservations.emptyHint': 'Aggiungi prenotazioni per voli, alloggi e altro', + 'reservations.add': 'Aggiungi prenotazione', + 'reservations.addManual': 'Prenotazione manuale', + 'reservations.placeHint': + 'Suggerimento: è meglio creare le prenotazioni direttamente da un luogo per collegarle al tuo programma del giorno.', + 'reservations.confirmed': 'Confermata', + 'reservations.pending': 'In attesa', + 'reservations.summary': '{confirmed} confermate, {pending} in attesa', + 'reservations.fromPlan': 'Dal programma', + 'reservations.showFiles': 'Mostra file', + 'reservations.editTitle': 'Modifica prenotazione', + 'reservations.status': 'Stato', + 'reservations.datetime': 'Data e ora', + 'reservations.startTime': 'Ora di inizio', + 'reservations.endTime': 'Ora di fine', + 'reservations.date': 'Data', + 'reservations.time': 'Ora', + 'reservations.timeAlt': 'Ora (alternativa, es. 19:30)', + 'reservations.notes': 'Note', + 'reservations.notesPlaceholder': 'Note aggiuntive...', + 'reservations.meta.airline': 'Compagnia aerea', + 'reservations.meta.flightNumber': 'N. volo', + 'reservations.meta.from': 'Da', + 'reservations.meta.to': 'A', + 'reservations.needsReview': 'Verifica', + 'reservations.needsReviewHint': + "L'aeroporto non è stato riconosciuto automaticamente — conferma la posizione.", + 'reservations.searchLocation': 'Cerca stazione, porto, indirizzo...', + 'reservations.meta.trainNumber': 'N. treno', + 'reservations.meta.platform': 'Binario', + 'reservations.meta.seat': 'Posto', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in fino a', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Alloggio', + 'reservations.meta.pickAccommodation': 'Collega a un alloggio', + 'reservations.meta.noAccommodation': 'Nessuno', + 'reservations.meta.hotelPlace': 'Alloggio', + 'reservations.meta.pickHotel': 'Seleziona alloggio', + 'reservations.meta.fromDay': 'Da', + 'reservations.meta.toDay': 'A', + 'reservations.meta.selectDay': 'Seleziona giorno', + 'reservations.type.flight': 'Volo', + 'reservations.type.hotel': 'Alloggio', + 'reservations.type.restaurant': 'Ristorante', + 'reservations.type.train': 'Treno', + 'reservations.type.car': 'Auto', + 'reservations.type.cruise': 'Crociera', + 'reservations.type.event': 'Evento', + 'reservations.type.tour': 'Tour', + 'reservations.type.other': 'Altro', + 'reservations.confirm.delete': + 'Sei sicuro di voler eliminare la prenotazione "{name}"?', + 'reservations.confirm.deleteTitle': 'Eliminare la prenotazione?', + 'reservations.confirm.deleteBody': + '"{name}" verrà eliminato in modo permanente.', + 'reservations.toast.updated': 'Prenotazione aggiornata', + 'reservations.toast.removed': 'Prenotazione eliminata', + 'reservations.toast.fileUploaded': 'File caricato', + 'reservations.toast.uploadError': 'Impossibile caricare', + 'reservations.newTitle': 'Nuova prenotazione', + 'reservations.bookingType': 'Tipo di prenotazione', + 'reservations.titleLabel': 'Titolo', + 'reservations.titlePlaceholder': 'es. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Posizione / Indirizzo', + 'reservations.locationPlaceholder': 'Indirizzo, aeroporto, hotel...', + 'reservations.confirmationCode': 'Codice prenotazione', + 'reservations.confirmationPlaceholder': 'es. ABC12345', + 'reservations.day': 'Giorno', + 'reservations.noDay': 'Nessun giorno', + 'reservations.place': 'Luogo', + 'reservations.noPlace': 'Nessun luogo', + 'reservations.pendingSave': 'verrà salvato…', + 'reservations.uploading': 'Caricamento...', + 'reservations.attachFile': 'Allega file', + 'reservations.linkExisting': 'Collega file esistente', + 'reservations.toast.saveError': 'Impossibile salvare', + 'reservations.toast.updateError': 'Impossibile aggiornare', + 'reservations.toast.deleteError': 'Impossibile eliminare', + 'reservations.confirm.remove': 'Rimuovere la prenotazione per "{name}"?', + 'reservations.linkAssignment': "Collega all'assegnazione del giorno", + 'reservations.pickAssignment': + "Seleziona un'assegnazione dal tuo programma...", + 'reservations.noAssignment': 'Nessun collegamento (autonomo)', + 'reservations.price': 'Prezzo', + 'reservations.budgetCategory': 'Categoria budget', + 'reservations.budgetCategoryPlaceholder': 'es. Trasporto, Alloggio', + 'reservations.budgetCategoryAuto': 'Auto (dal tipo di prenotazione)', + 'reservations.budgetHint': + 'Una voce di budget verrà creata automaticamente al salvataggio.', + 'reservations.departureDate': 'Partenza', + 'reservations.arrivalDate': 'Arrivo', + 'reservations.departureTime': 'Ora part.', + 'reservations.arrivalTime': 'Ora arr.', + 'reservations.pickupDate': 'Ritiro', + 'reservations.returnDate': 'Riconsegna', + 'reservations.pickupTime': 'Ora ritiro', + 'reservations.returnTime': 'Ora riconsegna', + 'reservations.endDate': 'Data fine', + 'reservations.meta.departureTimezone': 'TZ part.', + 'reservations.meta.arrivalTimezone': 'TZ arr.', + 'reservations.span.departure': 'Partenza', + 'reservations.span.arrival': 'Arrivo', + 'reservations.span.inTransit': 'In transito', + 'reservations.span.pickup': 'Ritiro', + 'reservations.span.return': 'Riconsegna', + 'reservations.span.active': 'Attivo', + 'reservations.span.start': 'Inizio', + 'reservations.span.end': 'Fine', + 'reservations.span.ongoing': 'In corso', + 'reservations.validation.endBeforeStart': + 'La data/ora di fine deve essere successiva alla data/ora di inizio', + 'reservations.addBooking': 'Aggiungi prenotazione', +}; +export default reservations; diff --git a/shared/src/i18n/it/settings.ts b/shared/src/i18n/it/settings.ts new file mode 100644 index 00000000..c166cd9b --- /dev/null +++ b/shared/src/i18n/it/settings.ts @@ -0,0 +1,298 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Impostazioni', + 'settings.subtitle': 'Configura le tue impostazioni personali', + 'settings.tabs.display': 'Visualizzazione', + 'settings.tabs.map': 'Mappa', + 'settings.tabs.notifications': 'Notifiche', + 'settings.tabs.integrations': 'Integrazioni', + 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Informazioni', + 'settings.map': 'Mappa', + 'settings.mapTemplate': 'Modello Mappa', + 'settings.mapTemplatePlaceholder.select': 'Seleziona modello...', + 'settings.mapDefaultHint': 'Lascia vuoto per OpenStreetMap (predefinito)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'Modello URL per i tile della mappa', + 'settings.mapProvider': 'Provider mappa', + 'settings.mapProviderHint': + 'Influisce sulle mappe Trip Planner e Journey. Atlas usa sempre Leaflet.', + 'settings.mapLeafletSubtitle': 'Classica 2D, qualsiasi tile raster', + 'settings.mapMapboxSubtitle': 'Tile vettoriali, edifici 3D e terreno', + 'settings.mapExperimental': 'Sperimentale', + 'settings.mapMapboxToken': 'Token di accesso Mapbox', + 'settings.mapMapboxTokenHint': 'Token pubblico (pk.*) da', + 'settings.mapMapboxTokenLink': 'mapbox.com → Token di accesso', + 'settings.mapStyle': 'Stile mappa', + 'settings.mapStylePlaceholder': 'Seleziona uno stile Mapbox', + 'settings.mapStyleHint': 'Preset o il tuo URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': 'Edifici 3D e terreno', + 'settings.map3dHint': + 'Inclinazione + estrusioni 3D reali degli edifici — funziona con ogni stile, incluso satellite.', + 'settings.mapHighQuality': 'Modalità alta qualità', + 'settings.mapHighQualityHint': + 'Antialiasing + proiezione globo per bordi più nitidi e una vista realistica del mondo.', + 'settings.mapHighQualityWarning': + 'Può influire sulle prestazioni su dispositivi meno potenti.', + 'settings.mapTipLabel': 'Suggerimento:', + 'settings.mapTip': + 'Click destro e trascina per ruotare/inclinare la mappa. Click centrale per aggiungere un luogo (il click destro è riservato alla rotazione).', + 'settings.latitude': 'Latitudine', + 'settings.longitude': 'Longitudine', + 'settings.saveMap': 'Salva Mappa', + 'settings.apiKeys': 'Chiavi API', + 'settings.mapsKey': 'Chiave API Google Maps', + 'settings.mapsKeyHint': + 'Per la ricerca dei luoghi. Richiede Places API (New). Ottienila su console.cloud.google.com', + 'settings.weatherKey': 'Chiave API OpenWeatherMap', + 'settings.weatherKeyHint': + 'Per i dati meteo. Gratuita su openweathermap.org/api', + 'settings.keyPlaceholder': 'Inserisci la chiave...', + 'settings.configured': 'Configurata', + 'settings.saveKeys': 'Salva Chiavi', + 'settings.display': 'Visualizzazione', + 'settings.colorMode': 'Modalità Colore', + 'settings.light': 'Chiara', + 'settings.dark': 'Scura', + 'settings.auto': 'Automatica', + 'settings.language': 'Lingua', + 'settings.temperature': 'Unità di Temperatura', + 'settings.timeFormat': 'Formato Ora', + 'settings.blurBookingCodes': 'Nascondi codici di prenotazione', + 'settings.notifications': 'Notifiche', + 'settings.notifyTripInvite': 'Inviti di viaggio', + 'settings.notifyBookingChange': 'Modifiche alle prenotazioni', + 'settings.notifyTripReminder': 'Promemoria di viaggio', + 'settings.notifyTodoDue': 'Attività in scadenza', + 'settings.notifyVacayInvite': 'Inviti fusione Vacay', + 'settings.notifyPhotosShared': 'Foto condivise (Immich)', + 'settings.notifyCollabMessage': 'Messaggi chat (Collab)', + 'settings.notifyPackingTagged': 'Lista valigia: assegnazioni', + 'settings.notifyWebhook': 'Notifiche webhook', + 'settings.notificationsDisabled': + 'Le notifiche non sono configurate. Chiedi a un amministratore di abilitare le notifiche e-mail o webhook.', + 'settings.notificationsActive': 'Canale attivo', + 'settings.notificationsManagedByAdmin': + "Gli eventi di notifica sono configurati dall'amministratore.", + 'settings.on': 'On', + 'settings.off': 'Off', + 'settings.mcp.title': 'Configurazione MCP', + 'settings.mcp.endpoint': 'Endpoint MCP', + 'settings.mcp.clientConfig': 'Configurazione client', + 'settings.mcp.clientConfigHint': + 'Sostituisci con un token API dalla lista sottostante. Il percorso di npx potrebbe dover essere adattato per il tuo sistema (es. C:\\PROGRA~1\\nodejs\\npx.cmd su Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).', + 'settings.mcp.copy': 'Copia', + 'settings.mcp.copied': 'Copiato!', + 'settings.mcp.apiTokens': 'Token API', + 'settings.mcp.createToken': 'Crea nuovo token', + 'settings.mcp.noTokens': + 'Nessun token ancora. Creane uno per connettere i client MCP.', + 'settings.mcp.tokenCreatedAt': 'Creato', + 'settings.mcp.tokenUsedAt': 'Utilizzato', + 'settings.mcp.deleteTokenTitle': 'Elimina token', + 'settings.mcp.deleteTokenMessage': + "Questo token smetterà di funzionare immediatamente. Qualsiasi client MCP che lo utilizza perderà l'accesso.", + 'settings.mcp.modal.createTitle': 'Crea token API', + 'settings.mcp.modal.tokenName': 'Nome del token', + 'settings.mcp.modal.tokenNamePlaceholder': + 'es. Claude Desktop, Laptop di lavoro', + 'settings.mcp.modal.creating': 'Creazione…', + 'settings.mcp.modal.create': 'Crea token', + 'settings.mcp.modal.createdTitle': 'Token creato', + 'settings.mcp.modal.createdWarning': + 'Questo token verrà mostrato solo una volta. Copialo e salvalo ora — non può essere recuperato.', + 'settings.mcp.modal.done': 'Fatto', + 'settings.mcp.toast.created': 'Token creato', + 'settings.mcp.toast.createError': 'Impossibile creare il token', + 'settings.mcp.toast.deleted': 'Token eliminato', + 'settings.mcp.toast.deleteError': 'Impossibile eliminare il token', + 'settings.mcp.apiTokensDeprecated': + 'I token API sono deprecati e verranno rimossi in una versione futura. Utilizza invece i client OAuth 2.1.', + 'settings.oauth.clients': 'Client OAuth 2.1', + 'settings.oauth.clientsHint': + 'Registra client OAuth 2.1 per consentire alle applicazioni MCP di terze parti (Claude Web, Cursor, ecc.) di connettersi senza token statici.', + 'settings.oauth.createClient': 'Nuovo client', + 'settings.oauth.noClients': 'Nessun client OAuth registrato.', + 'settings.oauth.clientId': 'ID client', + 'settings.oauth.clientSecret': 'Segreto client', + 'settings.oauth.deleteClient': 'Elimina client', + 'settings.oauth.deleteClientMessage': + "Questo client e tutte le sessioni attive verranno eliminati definitivamente. Qualsiasi applicazione che lo utilizza perderà immediatamente l'accesso.", + 'settings.oauth.rotateSecret': 'Rinnova segreto', + 'settings.oauth.rotateSecretMessage': + 'Verrà generato un nuovo segreto client e tutte le sessioni esistenti verranno invalidate immediatamente. Aggiorna la tua applicazione prima di chiudere questa finestra.', + 'settings.oauth.rotateSecretConfirm': 'Rinnova', + 'settings.oauth.rotateSecretConfirming': 'Rinnovo in corso…', + 'settings.oauth.rotateSecretDoneTitle': 'Nuovo segreto generato', + 'settings.oauth.rotateSecretDoneWarning': + 'Questo segreto viene mostrato una sola volta. Copialo ora e aggiorna la tua applicazione — tutte le sessioni precedenti sono state invalidate.', + 'settings.oauth.activeSessions': 'Sessioni OAuth attive', + 'settings.oauth.sessionScopes': 'Ambiti', + 'settings.oauth.sessionExpires': 'Scade', + 'settings.oauth.revoke': 'Revoca', + 'settings.oauth.revokeSession': 'Revoca sessione', + 'settings.oauth.revokeSessionMessage': + "Questo revocherà immediatamente l'accesso per questa sessione OAuth.", + 'settings.oauth.modal.createTitle': 'Registra client OAuth', + 'settings.oauth.modal.presets': 'Preimpostazioni rapide', + 'settings.oauth.modal.clientName': 'Nome applicazione', + 'settings.oauth.modal.clientNamePlaceholder': + 'es. Claude Web, La mia app MCP', + 'settings.oauth.modal.redirectUris': 'URI di reindirizzamento', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Un URI per riga. HTTPS richiesto (localhost esente). Corrispondenza esatta richiesta.', + 'settings.oauth.modal.scopes': 'Ambiti consentiti', + 'settings.oauth.modal.scopesHint': + "list_trips e get_trip_summary sono sempre disponibili — nessun ambito richiesto. Permettono all'IA di scoprire gli ID viaggio necessari.", + 'settings.oauth.modal.selectAll': 'Seleziona tutto', + 'settings.oauth.modal.deselectAll': 'Deseleziona tutto', + 'settings.oauth.modal.creating': 'Registrazione…', + 'settings.oauth.modal.create': 'Registra client', + 'settings.oauth.modal.createdTitle': 'Client registrato', + 'settings.oauth.modal.createdWarning': + 'Il segreto client viene mostrato una sola volta. Copialo ora — non può essere recuperato.', + 'settings.oauth.toast.createError': 'Impossibile registrare il client OAuth', + 'settings.oauth.toast.deleted': 'Client OAuth eliminato', + 'settings.oauth.toast.deleteError': 'Impossibile eliminare il client OAuth', + 'settings.oauth.toast.revoked': 'Sessione revocata', + 'settings.oauth.toast.revokeError': 'Impossibile revocare la sessione', + 'settings.oauth.toast.rotateError': 'Impossibile rinnovare il segreto client', + 'settings.oauth.modal.machineClient': + 'Client macchina (senza login nel browser)', + 'settings.oauth.modal.machineClientHint': + 'Usa il grant client_credentials — nessun URI di reindirizzamento necessario. Il token viene emesso direttamente tramite client_id + client_secret e agisce come te negli ambiti selezionati.', + 'settings.oauth.modal.machineClientUsage': + 'Ottieni token: POST /oauth/token con grant_type=client_credentials, client_id e client_secret. Senza browser, senza token di aggiornamento.', + 'settings.oauth.badge.machine': 'macchina', + 'settings.account': 'Account', + 'settings.about': 'Informazioni', + 'settings.about.reportBug': 'Segnala un bug', + 'settings.about.reportBugHint': 'Hai trovato un problema? Faccelo sapere', + 'settings.about.featureRequest': 'Richiedi funzionalità', + 'settings.about.featureRequestHint': 'Suggerisci una nuova funzionalità', + 'settings.about.wikiHint': 'Documentazione e guide', + 'settings.about.supporters.badge': 'Sostenitori Mensili', + 'settings.about.supporters.title': 'Compagni di viaggio per TREK', + 'settings.about.supporters.subtitle': + 'Mentre pianifichi il tuo prossimo itinerario, queste persone aiutano a pianificare il futuro di TREK. Il loro contributo mensile va direttamente allo sviluppo e alle ore realmente investite — per mantenere TREK Open Source.', + 'settings.about.supporters.since': 'sostenitore da {date}', + 'settings.about.supporters.tierEmpty': 'Sii il primo', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + "TREK è un pianificatore di viaggi self-hosted che ti aiuta a organizzare i tuoi viaggi dalla prima idea all'ultimo ricordo. Pianificazione giornaliera, budget, liste bagagli, foto e molto altro — tutto in un unico posto, sul tuo server.", + 'settings.about.madeWith': 'Fatto con', + 'settings.about.madeBy': 'da Maurice e una crescente comunità open-source.', + 'settings.username': 'Nome utente', + 'settings.email': 'Email', + 'settings.role': 'Ruolo', + 'settings.roleAdmin': 'Amministratore', + 'settings.oidcLinked': 'Collegato con', + 'settings.changePassword': 'Cambia Password', + 'settings.currentPassword': 'Password attuale', + 'settings.currentPasswordRequired': 'La password attuale è obbligatoria', + 'settings.newPassword': 'Nuova password', + 'settings.confirmPassword': 'Conferma nuova password', + 'settings.updatePassword': 'Aggiorna password', + 'settings.passwordRequired': 'Inserisci la password attuale e quella nuova', + 'settings.passwordTooShort': 'La password deve contenere almeno 8 caratteri', + 'settings.passwordMismatch': 'Le password non corrispondono', + 'settings.passwordWeak': + 'La password deve contenere lettere maiuscole, minuscole, un numero e un carattere speciale', + 'settings.passwordChanged': 'Password cambiata con successo', + 'settings.deleteAccount': 'Elimina account', + 'settings.deleteAccountTitle': 'Eliminare il tuo account?', + 'settings.deleteAccountWarning': + 'Il tuo account e tutti i tuoi viaggi, luoghi e file verranno eliminati in modo permanente. Questa azione non può essere annullata.', + 'settings.deleteAccountConfirm': 'Elimina in modo permanente', + 'settings.deleteBlockedTitle': 'Eliminazione impossibile', + 'settings.deleteBlockedMessage': + "Sei l'unico amministratore. Promuovi un altro utente ad amministratore prima di eliminare il tuo account.", + 'settings.roleUser': 'Utente', + 'settings.saveProfile': 'Salva Profillo', + 'settings.toast.mapSaved': 'Impostazioni mappa salvate', + 'settings.toast.keysSaved': 'Chiavi API salvate', + 'settings.toast.displaySaved': 'Impostazioni di visualizzazione salvate', + 'settings.toast.profileSaved': 'Profilo salvato', + 'settings.uploadAvatar': 'Carica Immagine del Profilo', + 'settings.removeAvatar': 'Rimuovi Immagine del Profilo', + 'settings.avatarUploaded': 'Immagine del profilo aggiornata', + 'settings.avatarRemoved': 'Immagine del profilo rimossa', + 'settings.avatarError': 'Impossibile caricare', + 'settings.mfa.title': 'Autenticazione a due fattori (2FA)', + 'settings.mfa.description': + "Aggiunge un secondo passaggio quando accedi con email e password. Usa un'app authenticator (Google Authenticator, Authy, ecc.).", + 'settings.mfa.requiredByPolicy': + "L'amministratore richiede l'autenticazione a due fattori. Configura un'app authenticator qui sotto prima di continuare.", + 'settings.mfa.backupTitle': 'Codici di backup', + 'settings.mfa.backupDescription': + "Usa questi codici monouso se perdi l'accesso alla tua app authenticator.", + 'settings.mfa.backupWarning': + 'Salvali adesso. Ogni codice può essere usato una sola volta.', + 'settings.mfa.backupCopy': 'Copia codici', + 'settings.mfa.backupDownload': 'Scarica TXT', + 'settings.mfa.backupPrint': 'Stampa / PDF', + 'settings.mfa.backupCopied': 'Codici di backup copiati', + 'settings.mfa.enabled': 'La 2FA è abilitata sul tuo account.', + 'settings.mfa.disabled': 'La 2FA non è abilitata.', + 'settings.mfa.setup': 'Configura authenticator', + 'settings.mfa.scanQr': + 'Scansiona questo codice QR con la tua app, o inserisci il segreto manualmente.', + 'settings.mfa.secretLabel': 'Chiave segreta (inserimento manuale)', + 'settings.mfa.codePlaceholder': 'Codice a 6 cifre', + 'settings.mfa.enable': 'Abilita 2FA', + 'settings.mfa.cancelSetup': 'Annulla', + 'settings.mfa.disableTitle': 'Disabilita 2FA', + 'settings.mfa.disableHint': + 'Inserisci la password del tuo account e un codice attuale dal tuo authenticator.', + 'settings.mfa.disable': 'Disabilita 2FA', + 'settings.mfa.toastEnabled': 'Autenticazione a due fattori abilitata', + 'settings.mfa.toastDisabled': 'Autenticazione a due fattori disabilitata', + 'settings.mfa.demoBlocked': 'Non disponibile in modalità demo', + 'settings.mustChangePassword': + 'Devi cambiare la password prima di continuare. Imposta una nuova password qui sotto.', + 'settings.bookingLabels': 'Etichette percorsi prenotati', + 'settings.bookingLabelsHint': + "Mostra i nomi di stazioni / aeroporti sulla mappa. Se disattivato, viene mostrata solo l'icona.", + 'settings.notifyVersionAvailable': 'Nuova versione disponibile', + 'settings.notificationPreferences.noChannels': + 'Nessun canale di notifica configurato. Chiedi a un amministratore di configurare notifiche via e-mail o webhook.', + 'settings.webhookUrl.label': 'URL webhook', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Inserisci il tuo URL webhook Discord, Slack o personalizzato per ricevere notifiche.', + 'settings.webhookUrl.saved': 'URL webhook salvato', + 'settings.webhookUrl.test': 'Test', + 'settings.webhookUrl.testSuccess': 'Webhook di test inviato con successo', + 'settings.webhookUrl.testFailed': 'Invio webhook di test fallito', + 'settings.ntfyUrl.topicLabel': 'Argomento Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL server Ntfy (opzionale)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + "Inserisci il tuo argomento Ntfy per ricevere notifiche push. Lascia il server vuoto per usare il valore predefinito configurato dall'amministratore.", + 'settings.ntfyUrl.tokenLabel': 'Token di accesso (opzionale)', + 'settings.ntfyUrl.tokenHint': + 'Richiesto per gli argomenti protetti da password.', + 'settings.ntfyUrl.saved': 'Impostazioni Ntfy salvate', + 'settings.ntfyUrl.test': 'Testa', + 'settings.ntfyUrl.testSuccess': 'Notifica di test Ntfy inviata con successo', + 'settings.ntfyUrl.testFailed': 'Notifica di test Ntfy fallita', + 'settings.ntfyUrl.tokenCleared': 'Token di accesso rimosso', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/it/share.ts b/shared/src/i18n/it/share.ts new file mode 100644 index 00000000..7889ae7c --- /dev/null +++ b/shared/src/i18n/it/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Link pubblico', + 'share.linkHint': + 'Crea un link che chiunque può usare per visualizzare questo viaggio senza accedere. Solo lettura — nessuna modifica possibile.', + 'share.createLink': 'Crea link', + 'share.deleteLink': 'Elimina link', + 'share.createError': 'Impossibile creare il link', + 'share.permMap': 'Mappa e programma', + 'share.permBookings': 'Prenotazioni', + 'share.permPacking': 'Valigia', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/it/shared.ts b/shared/src/i18n/it/shared.ts new file mode 100644 index 00000000..a052c63d --- /dev/null +++ b/shared/src/i18n/it/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link scaduto o non valido', + 'shared.expiredHint': 'Questo link di viaggio condiviso non è più attivo.', + 'shared.readOnly': 'Vista in sola lettura', + 'shared.tabPlan': 'Programma', + 'shared.tabBookings': 'Prenotazioni', + 'shared.tabPacking': 'Valigia', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'giorni', + 'shared.places': 'luoghi', + 'shared.other': 'Altro', + 'shared.totalBudget': 'Budget totale', + 'shared.messages': 'messaggi', + 'shared.sharedVia': 'Condiviso tramite', + 'shared.confirmed': 'Confermato', + 'shared.pending': 'In attesa', +}; +export default shared; diff --git a/shared/src/i18n/it/stats.ts b/shared/src/i18n/it/stats.ts new file mode 100644 index 00000000..35b45a98 --- /dev/null +++ b/shared/src/i18n/it/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Paesi', + 'stats.cities': 'Città', + 'stats.trips': 'Viaggi', + 'stats.places': 'Luoghi', + 'stats.worldProgress': 'Progresso nel mondo', + 'stats.visited': 'visitati', + 'stats.remaining': 'rimanenti', + 'stats.visitedCountries': 'Paesi visitati', +}; +export default stats; diff --git a/shared/src/i18n/it/system_notice.ts b/shared/src/i18n/it/system_notice.ts new file mode 100644 index 00000000..f8c3b322 --- /dev/null +++ b/shared/src/i18n/it/system_notice.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Benvenuto su TREK', + 'system_notice.welcome_v1.body': + 'Il tuo pianificatore di viaggi tutto in uno. Crea itinerari, condividi viaggi con gli amici e rimani organizzato — online e offline.', + 'system_notice.welcome_v1.cta_label': 'Pianifica un viaggio', + 'system_notice.welcome_v1.hero_alt': + "Destinazione di viaggio panoramica con l'interfaccia TREK", + 'system_notice.welcome_v1.highlight_plan': 'Itinerari giorno per giorno', + 'system_notice.welcome_v1.highlight_share': + 'Collabora con i tuoi compagni di viaggio', + 'system_notice.welcome_v1.highlight_offline': 'Funziona offline su mobile', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Avviso precedente', + 'system_notice.pager.next': 'Avviso successivo', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': "Vai all'avviso {n}", + 'system_notice.pager.position': 'Avviso {current} di {total}', + 'system_notice.v3_photos.title': 'Le foto sono spostate nella 3.0', + 'system_notice.v3_photos.body': + '**Foto** nel Pianificatore di Viaggio sono state rimosse. Le tue foto sono al sicuro — TREK non ha mai modificato la tua libreria Immich o Synology.\n\nLe foto ora si trovano nel componente aggiuntivo **Journey**. Journey è opzionale — se non è ancora disponibile, chiedi al tuo admin di abilitarlo in Admin → Addon.', + 'system_notice.v3_journey.title': 'Scopri Journey — diario di viaggio', + 'system_notice.v3_journey.body': + 'Documenta i tuoi viaggi come storie ricche con cronologie, gallerie fotografiche e mappe interattive.', + 'system_notice.v3_journey.cta_label': 'Apri Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Cronologia e galleria giornaliera', + 'system_notice.v3_journey.highlight_photos': 'Importa da Immich o Synology', + 'system_notice.v3_journey.highlight_share': + 'Condividi pubblicamente — senza accesso', + 'system_notice.v3_journey.highlight_export': + 'Esporta come libro fotografico PDF', + 'system_notice.v3_features.title': 'Altri punti salienti nel 3.0', + 'system_notice.v3_features.body': + 'Altre novità da conoscere in questa versione.', + 'system_notice.v3_features.highlight_dashboard': + 'Dashboard ridisegnata mobile-first', + 'system_notice.v3_features.highlight_offline': + 'Modalità offline completa come PWA', + 'system_notice.v3_features.highlight_search': + 'Completamento automatico luoghi in tempo reale', + 'system_notice.v3_features.highlight_import': + 'Importa luoghi da file KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: aggiornamento OAuth 2.1', + 'system_notice.v3_mcp.body': + "L'integrazione MCP è stata completamente rinnovata. OAuth 2.1 è ora il metodo di autenticazione consigliato. I token statici (trek_…) sono deprecati e verranno rimossi in una versione futura.", + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 consigliato (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': + '24 scope di autorizzazione granulari', + 'system_notice.v3_mcp.highlight_deprecated': 'Token statici trek_ deprecati', + 'system_notice.v3_mcp.highlight_tools': 'Strumenti e prompt estesi', + 'system_notice.v3_thankyou.title': 'Una nota personale da parte mia', + 'system_notice.v3_thankyou.body': + "Prima di andare avanti — voglio prendermi un momento.\n\nTREK è nato come un progetto secondario che ho costruito per i miei viaggi. Non avrei mai immaginato che sarebbe cresciuto fino a diventare qualcosa di cui 4.000 di voi si fidano per pianificare le proprie avventure. Ogni stella, ogni issue, ogni richiesta di funzionalità — le leggo tutte, e sono loro a tenermi in piedi nelle notti tarde tra un lavoro a tempo pieno e l'università.\n\nVoglio che sappiate: TREK sarà sempre open source, sempre self-hosted, sempre vostro. Nessun tracciamento, nessun abbonamento, nessuna fregatura. Solo uno strumento creato da qualcuno che ama viaggiare tanto quanto voi.\n\nUn ringraziamento speciale a [jubnl](https://github.com/jubnl) — sei diventato un collaboratore incredibile. Molto di ciò che rende la 3.0 fantastica porta la tua impronta. Grazie per aver creduto in questo progetto quando era ancora acerbo.\n\nE a ognuno di voi che ha segnalato un bug, tradotto una stringa, condiviso TREK con un amico o semplicemente lo ha usato per pianificare un viaggio — **grazie**. Voi siete il motivo per cui tutto questo esiste.\n\nA molte altre avventure insieme.\n\n— Maurice\n\n---\n\n[Unisciti alla community su Discord](https://discord.gg/7Q6M6jDwzf)\n\nSe TREK rende i tuoi viaggi migliori, un [piccolo caffè](https://ko-fi.com/mauriceboe) aiuta sempre a tenere le luci accese.", + 'system_notice.v3014_whitespace_collision.title': + 'Azione richiesta: conflitto di account utente', + 'system_notice.v3014_whitespace_collision.body': + "L'aggiornamento 3.0.14 ha rilevato uno o più conflitti di nome utente o e-mail causati da spazi iniziali o finali nei valori memorizzati. Gli account interessati sono stati rinominati automaticamente. Controlla i log del server per le righe che iniziano con **[migration] WHITESPACE COLLISION** per identificare quali account richiedono revisione.", +}; +export default system_notice; diff --git a/shared/src/i18n/it/todo.ts b/shared/src/i18n/it/todo.ts new file mode 100644 index 00000000..87149400 --- /dev/null +++ b/shared/src/i18n/it/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Lista di imballaggio', + 'todo.subtab.todo': 'Da fare', + 'todo.completed': 'completato/i', + 'todo.filter.all': 'Tutti', + 'todo.filter.open': 'Aperto', + 'todo.filter.done': 'Fatto', + 'todo.uncategorized': 'Senza categoria', + 'todo.namePlaceholder': 'Nome attività', + 'todo.descriptionPlaceholder': 'Descrizione (facoltativa)', + 'todo.unassigned': 'Non assegnato', + 'todo.noCategory': 'Nessuna categoria', + 'todo.hasDescription': 'Ha descrizione', + 'todo.addItem': 'Nuova attività', + 'todo.sidebar.sortBy': 'Ordina per', + 'todo.priority': 'Priorità', + 'todo.newCategoryLabel': 'nuova', + 'todo.newCategory': 'Nome categoria', + 'todo.addCategory': 'Aggiungi categoria', + 'todo.newItem': 'Nuova attività', + 'todo.empty': "Nessuna attività ancora. Aggiungi un'attività per iniziare!", + 'todo.filter.my': 'Le mie attività', + 'todo.filter.overdue': 'Scaduta', + 'todo.sidebar.tasks': 'Attività', + 'todo.sidebar.categories': 'Categorie', + 'todo.detail.title': 'Attività', + 'todo.detail.description': 'Descrizione', + 'todo.detail.category': 'Categoria', + 'todo.detail.dueDate': 'Scadenza', + 'todo.detail.assignedTo': 'Assegnato a', + 'todo.detail.delete': 'Elimina', + 'todo.detail.save': 'Salva modifiche', + 'todo.detail.create': 'Crea attività', + 'todo.detail.priority': 'Priorità', + 'todo.detail.noPriority': 'Nessuna', + 'todo.sortByPrio': 'Priorità', +}; +export default todo; diff --git a/shared/src/i18n/it/transport.ts b/shared/src/i18n/it/transport.ts new file mode 100644 index 00000000..8fbb5644 --- /dev/null +++ b/shared/src/i18n/it/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Aggiungi trasporto', + 'transport.modalTitle.create': 'Aggiungi trasporto', + 'transport.modalTitle.edit': 'Modifica trasporto', + 'transport.title': 'Trasporti', + 'transport.addManual': 'Trasporto manuale', +}; +export default transport; diff --git a/shared/src/i18n/it/trip.ts b/shared/src/i18n/it/trip.ts new file mode 100644 index 00000000..42820e82 --- /dev/null +++ b/shared/src/i18n/it/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Programma', + 'trip.tabs.transports': 'Trasporti', + 'trip.tabs.reservations': 'Prenotazioni', + 'trip.tabs.reservationsShort': 'Pren.', + 'trip.tabs.packing': 'Lista valigia', + 'trip.tabs.packingShort': 'Valigia', + 'trip.tabs.lists': 'Liste', + 'trip.tabs.listsShort': 'Liste', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'File', + 'trip.loading': 'Caricamento viaggio...', + 'trip.mobilePlan': 'Programma', + 'trip.mobilePlaces': 'Luoghi', + 'trip.toast.placeUpdated': 'Luogo aggiornato', + 'trip.toast.placeAdded': 'Luogo aggiunto', + 'trip.toast.placeDeleted': 'Luogo eliminato', + 'trip.toast.selectDay': 'Seleziona prima un giorno', + 'trip.toast.assignedToDay': 'Luogo assegnato al giorno', + 'trip.toast.reorderError': 'Impossibile riordinare', + 'trip.toast.reservationUpdated': 'Prenotazione aggiornata', + 'trip.toast.reservationAdded': 'Prenotazione aggiunta', + 'trip.toast.deleted': 'Eliminato', + 'trip.confirm.deletePlace': 'Sei sicuro di voler eliminare questo luogo?', + 'trip.confirm.deletePlaces': 'Eliminare {count} luoghi?', + 'trip.toast.placesDeleted': '{count} luoghi eliminati', + 'trip.loadingPhotos': 'Caricamento foto dei luoghi...', +}; +export default trip; diff --git a/shared/src/i18n/it/trips.ts b/shared/src/i18n/it/trips.ts new file mode 100644 index 00000000..4b099465 --- /dev/null +++ b/shared/src/i18n/it/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} rimosso', + 'trips.memberRemoveError': 'Rimozione non riuscita', + 'trips.memberAdded': '{username} aggiunto', + 'trips.memberAddError': 'Aggiunta non riuscita', + 'trips.reminder': 'Promemoria', + 'trips.reminderNone': 'Nessuno', + 'trips.reminderDay': 'giorno', + 'trips.reminderDays': 'giorni', + 'trips.reminderCustom': 'Personalizzato', + 'trips.reminderDaysBefore': 'giorni prima della partenza', + 'trips.reminderDisabledHint': + 'I promemoria dei viaggi sono disabilitati. Abilitali in Admin > Impostazioni > Notifiche.', +}; +export default trips; diff --git a/shared/src/i18n/it/undo.ts b/shared/src/i18n/it/undo.ts new file mode 100644 index 00000000..efa2546f --- /dev/null +++ b/shared/src/i18n/it/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Annulla', + 'undo.tooltip': 'Annulla: {action}', + 'undo.assignPlace': 'Luogo assegnato al giorno', + 'undo.removeAssignment': 'Luogo rimosso dal giorno', + 'undo.reorder': 'Luoghi riordinati', + 'undo.optimize': 'Percorso ottimizzato', + 'undo.deletePlace': 'Luogo eliminato', + 'undo.deletePlaces': 'Luoghi eliminati', + 'undo.moveDay': 'Luogo spostato in altro giorno', + 'undo.lock': 'Blocco luogo modificato', + 'undo.importGpx': 'Importazione GPX', + 'undo.importKeyholeMarkup': 'Importazione KMZ/KML', + 'undo.importGoogleList': 'Importazione Google Maps', + 'undo.importNaverList': 'Importazione Naver Maps', + 'undo.addPlace': 'Luogo aggiunto', + 'undo.done': 'Annullato: {action}', +}; +export default undo; diff --git a/shared/src/i18n/it/vacay.ts b/shared/src/i18n/it/vacay.ts new file mode 100644 index 00000000..acca33b0 --- /dev/null +++ b/shared/src/i18n/it/vacay.ts @@ -0,0 +1,105 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Pianifica e gestisci i giorni di ferie', + 'vacay.settings': 'Impostazioni', + 'vacay.year': 'Anno', + 'vacay.addYear': 'Aggiungi anno successivo', + 'vacay.addPrevYear': 'Aggiungi anno precedente', + 'vacay.removeYear': 'Rimuovi anno', + 'vacay.removeYearConfirm': 'Rimuovere {year}?', + 'vacay.removeYearHint': + 'Tutte le voci delle ferie e le ferie aziendali di questo anno verranno eliminate in modo permanente.', + 'vacay.remove': 'Rimuovi', + 'vacay.persons': 'Persone', + 'vacay.noPersons': 'Nessuna persona aggiunta', + 'vacay.addPerson': 'Aggiungi persona', + 'vacay.editPerson': 'Modifica persona', + 'vacay.removePerson': 'Rimuovi persona', + 'vacay.removePersonConfirm': 'Rimuovere {name}?', + 'vacay.removePersonHint': + 'Tutte le voci delle ferie per questa persona verranno eliminate in modo permanente.', + 'vacay.personName': 'Nome', + 'vacay.personNamePlaceholder': 'Inserisci nome', + 'vacay.color': 'Colore', + 'vacay.add': 'Aggiungi', + 'vacay.legend': 'Legenda', + 'vacay.publicHoliday': 'Festività pubblica', + 'vacay.companyHoliday': 'Ferie aziendali', + 'vacay.weekend': 'Weekend', + 'vacay.modeVacation': 'Ferie', + 'vacay.modeCompany': 'Ferie aziendali', + 'vacay.entitlement': 'Disponibilità', + 'vacay.entitlementDays': 'Giorni', + 'vacay.used': 'Usati', + 'vacay.remaining': 'Rimanenti', + 'vacay.carriedOver': 'dal {year}', + 'vacay.blockWeekends': 'Blocca weekend', + 'vacay.blockWeekendsHint': 'Impedisci le voci ferie nei giorni del weekend', + 'vacay.weekendDays': 'Giorni del weekend', + 'vacay.mon': 'Lun', + 'vacay.tue': 'Mar', + 'vacay.wed': 'Mer', + 'vacay.thu': 'Gio', + 'vacay.fri': 'Ven', + 'vacay.sat': 'Sab', + 'vacay.sun': 'Dom', + 'vacay.publicHolidays': 'Festività pubbliche', + 'vacay.publicHolidaysHint': 'Segna le festività pubbliche nel calendario', + 'vacay.selectCountry': 'Seleziona paese', + 'vacay.selectRegion': 'Seleziona regione (opzionale)', + 'vacay.addCalendar': 'Aggiungi calendario', + 'vacay.calendarLabel': 'Etichetta (opzionale)', + 'vacay.calendarColor': 'Colore', + 'vacay.noCalendars': 'Ancora nessun calendario delle festività aggiunto', + 'vacay.companyHolidays': 'Ferie aziendali', + 'vacay.companyHolidaysHint': 'Consenti di segnare giorni di ferie aziendali', + 'vacay.companyHolidaysNoDeduct': + 'Le ferie aziendali non vengono conteggiate nei giorni di ferie.', + 'vacay.weekStart': 'La settimana inizia il', + 'vacay.weekStartHint': + 'Scegli se la settimana inizia il lunedì o la domenica', + 'vacay.carryOver': 'Riporto', + 'vacay.carryOverHint': + "Riporta automaticamente i giorni di ferie rimanenti all'anno successivo", + 'vacay.sharing': 'Condivisione', + 'vacay.sharingHint': 'Condividi il tuo piano ferie con altri utenti TREK', + 'vacay.owner': 'Proprietario', + 'vacay.shareEmailPlaceholder': "Email dell'utente TREK", + 'vacay.shareSuccess': 'Piano condiviso con successo', + 'vacay.shareError': 'Impossibile condividere il piano', + 'vacay.dissolve': 'Sciogli unione', + 'vacay.dissolveHint': + 'Separa di nuovo i calendari. Le tue voci verranno mantenute.', + 'vacay.dissolveAction': 'Sciogli', + 'vacay.dissolved': 'Calendario separato', + 'vacay.fusedWith': 'Unito con', + 'vacay.you': 'tu', + 'vacay.noData': 'Nessun dato', + 'vacay.changeColor': 'Cambia colore', + 'vacay.inviteUser': 'Invita utente', + 'vacay.inviteHint': + 'Invita un altro utente TREK a condividere un calendario ferie combinato.', + 'vacay.selectUser': 'Seleziona utente', + 'vacay.sendInvite': 'Invia invito', + 'vacay.inviteSent': 'Invito inviato', + 'vacay.inviteError': "Impossibile inviare l'invito", + 'vacay.pending': 'in attesa', + 'vacay.noUsersAvailable': 'Nessun utente disponibile', + 'vacay.accept': 'Accetta', + 'vacay.decline': 'Rifiuta', + 'vacay.acceptFusion': 'Accetta e unisci', + 'vacay.inviteTitle': 'Richiesta di unione', + 'vacay.inviteWantsToFuse': 'vuole condividere con te un calendario ferie.', + 'vacay.fuseInfo1': + 'Entrambi vedrete tutte le voci ferie in un unico calendario condiviso.', + 'vacay.fuseInfo2': + 'Entrambe le parti possono creare e modificare le voci reciproche.', + 'vacay.fuseInfo3': + 'Entrambe le parti possono eliminare le voci e modificare le disponibilità ferie.', + 'vacay.fuseInfo4': + 'Le impostazioni come festività pubbliche e ferie aziendali sono condivise.', + 'vacay.fuseInfo5': + "L'unione può essere sciolta in qualsiasi momento da una delle due parti. Le tue voci verranno preservate.", +}; +export default vacay; diff --git a/shared/src/i18n/ja/admin.ts b/shared/src/i18n/ja/admin.ts new file mode 100644 index 00000000..a2986b62 --- /dev/null +++ b/shared/src/i18n/ja/admin.ts @@ -0,0 +1,340 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': '通知', + 'admin.notifications.hint': + '通知チャネルを1つ選択してください。同時に有効にできるのは1つだけです。', + 'admin.notifications.none': '無効', + 'admin.notifications.email': 'メール(SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'ユーザーが独自のntfyトピックを設定できるようにします。下で既定サーバーを設定してください。', + 'admin.notifications.save': '通知設定を保存', + 'admin.notifications.saved': '通知設定を保存しました', + 'admin.notifications.testWebhook': 'Webhookテスト送信', + 'admin.notifications.testWebhookSuccess': 'テストWebhookを送信しました', + 'admin.notifications.testWebhookFailed': 'テストWebhookに失敗しました', + 'admin.notifications.testNtfy': 'ntfyテスト送信', + 'admin.notifications.testNtfySuccess': 'テストntfyを送信しました', + 'admin.notifications.testNtfyFailed': 'テストntfyに失敗しました', + 'admin.notifications.emailPanel.title': 'メール(SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'アプリ内', + 'admin.notifications.inappPanel.hint': + 'アプリ内通知は常に有効で、全体では無効にできません。', + 'admin.notifications.adminWebhookPanel.title': '管理者Webhook', + 'admin.notifications.adminWebhookPanel.hint': + '管理者通知専用のWebhookです(例:バージョン通知)。常に送信されます。', + 'admin.notifications.adminWebhookPanel.saved': + '管理者Webhook URLを保存しました', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'テストWebhookを送信しました', + 'admin.notifications.adminWebhookPanel.testFailed': + 'テストWebhookに失敗しました', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'URLが設定されていると常に送信されます', + 'admin.notifications.adminNtfyPanel.title': '管理者Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + '管理者通知専用のntfyトピックです。', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy サーバーURL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'ユーザー通知の既定サーバーとしても使用されます。', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': '管理者トピック', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'アクセストークン(任意)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + '管理者アクセストークンを削除しました', + 'admin.notifications.adminNtfyPanel.saved': '管理者ntfy設定を保存しました', + 'admin.notifications.adminNtfyPanel.test': 'ntfyテスト送信', + 'admin.notifications.adminNtfyPanel.testSuccess': 'テストntfyを送信しました', + 'admin.notifications.adminNtfyPanel.testFailed': 'テストntfyに失敗しました', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'トピック設定時は常に送信されます', + 'admin.notifications.adminNotificationsHint': + '管理者専用通知の配信先を設定します。', + 'admin.notifications.tripReminders.title': '旅行リマインダー', + 'admin.notifications.tripReminders.hint': + '旅行開始前に通知を送信します(旅行側の設定が必要)。', + 'admin.notifications.tripReminders.enabled': '旅行リマインダー有効', + 'admin.notifications.tripReminders.disabled': '旅行リマインダー無効', + 'admin.smtp.title': 'メール・通知', + 'admin.smtp.hint': 'メール通知送信用のSMTP設定。', + 'admin.smtp.testButton': 'テストメール送信', + 'admin.webhook.hint': 'ユーザーが独自のWebhook URLを設定できるようにします。', + 'admin.smtp.testSuccess': 'テストメールを送信しました', + 'admin.smtp.testFailed': 'テストメールに失敗しました', + 'admin.title': '管理', + 'admin.subtitle': 'ユーザー管理とシステム設定', + 'admin.tabs.users': 'ユーザー', + 'admin.tabs.categories': 'カテゴリ', + 'admin.tabs.backup': 'バックアップ', + 'admin.tabs.notifications': '通知', + 'admin.tabs.audit': '監査', + 'admin.stats.users': 'ユーザー', + 'admin.stats.trips': '旅行', + 'admin.stats.places': '場所', + 'admin.stats.photos': '写真', + 'admin.stats.files': 'ファイル', + 'admin.table.user': 'ユーザー', + 'admin.table.email': 'メール', + 'admin.table.role': '権限', + 'admin.table.created': '作成日', + 'admin.table.lastLogin': '最終ログイン', + 'admin.table.actions': '操作', + 'admin.you': '(あなた)', + 'admin.editUser': 'ユーザーを編集', + 'admin.newPassword': '新しいパスワード', + 'admin.newPasswordHint': '空欄の場合は現在のパスワードを維持', + 'admin.deleteUser': + 'ユーザー「{name}」を削除しますか?すべての旅行が完全に削除されます。', + 'admin.deleteUserTitle': 'ユーザー削除', + 'admin.newPasswordPlaceholder': '新しいパスワードを入力…', + 'admin.toast.loadError': '管理データの読み込みに失敗しました', + 'admin.toast.userUpdated': 'ユーザーを更新しました', + 'admin.toast.updateError': '更新に失敗しました', + 'admin.toast.userDeleted': 'ユーザーを削除しました', + 'admin.toast.deleteError': '削除に失敗しました', + 'admin.toast.cannotDeleteSelf': '自分のアカウントは削除できません', + 'admin.toast.userCreated': 'ユーザーを作成しました', + 'admin.toast.createError': 'ユーザー作成に失敗しました', + 'admin.toast.fieldsRequired': 'ユーザー名、メール、パスワードは必須です', + 'admin.createUser': 'ユーザーを作成', + 'admin.invite.title': '招待リンク', + 'admin.invite.subtitle': '使い切り登録リンクを作成', + 'admin.invite.create': 'リンク作成', + 'admin.invite.createAndCopy': '作成してコピー', + 'admin.invite.empty': '招待リンクがまだありません', + 'admin.invite.maxUses': '最大使用回数', + 'admin.invite.expiry': '有効期限', + 'admin.invite.uses': '使用済み', + 'admin.invite.expiresAt': '期限', + 'admin.invite.createdBy': '作成者', + 'admin.invite.active': '有効', + 'admin.invite.expired': '期限切れ', + 'admin.invite.usedUp': '使用済み', + 'admin.invite.copied': '招待リンクをコピーしました', + 'admin.invite.copyLink': 'リンクをコピー', + 'admin.invite.deleted': '招待リンクを削除しました', + 'admin.invite.createError': '招待リンクの作成に失敗しました', + 'admin.invite.deleteError': '招待リンクの削除に失敗しました', + 'admin.tabs.settings': '設定', + 'admin.allowRegistration': '登録を許可', + 'admin.allowRegistrationHint': '新規ユーザーの自己登録を許可します', + 'admin.authMethods': '認証方法', + 'admin.passwordLogin': 'パスワードログイン', + 'admin.passwordLoginHint': 'メールとパスワードでのログインを許可', + 'admin.passwordRegistration': 'パスワード登録', + 'admin.passwordRegistrationHint': 'メールとパスワードでの新規登録を許可', + 'admin.oidcLogin': 'SSOログイン', + 'admin.oidcLoginHint': 'SSOでのログインを許可', + 'admin.oidcRegistration': 'SSO自動登録', + 'admin.oidcRegistrationHint': '新しいSSOユーザーを自動作成', + 'admin.envOverrideHint': + 'パスワードログイン設定は OIDC_ONLY 環境変数で制御されています。', + 'admin.lockoutWarning': '少なくとも1つのログイン方法を有効にしてください', + 'admin.requireMfa': '二要素認証(2FA)を必須にする', + 'admin.requireMfaHint': '2FA未設定のユーザーは、利用前に設定が必要です。', + 'admin.apiKeys': 'APIキー', + 'admin.apiKeysHint': '任意。写真や天気などの拡張データを有効化します。', + 'admin.mapsKey': 'Google Maps APIキー', + 'admin.mapsKeyHint': '場所検索に必要。console.cloud.google.com で取得', + 'admin.mapsKeyHintLong': + 'APIキーなしではOpenStreetMapを使用します。Google APIキーがあれば写真、評価、営業時間も表示できます。', + 'admin.recommended': '推奨', + 'admin.weatherKey': 'OpenWeatherMap APIキー', + 'admin.weatherKeyHint': '天気データ用。openweathermap.org で無料', + 'admin.validateKey': 'テスト', + 'admin.keyValid': '接続済み', + 'admin.keyInvalid': '無効', + 'admin.keySaved': 'APIキーを保存しました', + 'admin.oidcTitle': 'シングルサインオン(OIDC)', + 'admin.oidcSubtitle': + 'Google、Apple、Authentik、Keycloak などでログインを許可します。', + 'admin.oidcDisplayName': '表示名', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'OpenID ConnectのIssuer URL(例:https://accounts.google.com)', + 'admin.oidcSaved': 'OIDC設定を保存しました', + 'admin.oidcOnlyMode': 'パスワード認証を無効化', + 'admin.oidcOnlyModeHint': + '有効にするとSSOのみ使用可能になり、パスワードログインと登録は無効になります。', + 'admin.fileTypes': '許可するファイル形式', + 'admin.fileTypesHint': + 'ユーザーがアップロードできるファイル形式を設定します。', + 'admin.fileTypesFormat': + '拡張子をカンマ区切り(例:jpg,png,pdf,doc)。すべて許可する場合は *。', + 'admin.fileTypesSaved': 'ファイル形式の設定を保存しました', + 'admin.placesPhotos.title': '場所の写真', + 'admin.placesPhotos.subtitle': + 'Google Places APIから写真を取得します。APIクォータ節約のため無効にできます。Wikimediaの写真には影響しません。', + 'admin.placesAutocomplete.title': '場所のオートコンプリート', + 'admin.placesAutocomplete.subtitle': + '検索候補にGoogle Places APIを使用します。APIクォータ節約のため無効にできます。', + 'admin.placesDetails.title': '場所の詳細', + 'admin.placesDetails.subtitle': + 'Google Places APIから営業時間、評価、ウェブサイトなどの詳細情報を取得します。APIクォータ節約のため無効にできます。', + 'admin.bagTracking.title': 'バッグ管理', + 'admin.bagTracking.subtitle': '持ち物の重量とバッグ割り当てを有効化', + 'admin.collab.chat.title': 'チャット', + 'admin.collab.chat.subtitle': '旅行の共同作業用リアルタイムメッセージ', + 'admin.collab.notes.title': 'ノート', + 'admin.collab.notes.subtitle': '共有ノートとドキュメント', + 'admin.collab.polls.title': '投票', + 'admin.collab.polls.subtitle': 'グループ投票・アンケート', + 'admin.collab.whatsnext.title': '次にすること', + 'admin.collab.whatsnext.subtitle': 'アクティビティ提案と次のステップ', + 'admin.tabs.config': 'カスタマイズ', + 'admin.tabs.defaults': 'ユーザーのデフォルト', + 'admin.defaultSettings.title': '既定のユーザー設定', + 'admin.defaultSettings.description': + 'インスタンス全体の既定値を設定します。設定を変更していないユーザーにはこれらの値が適用されます。各ユーザーの設定変更は常に優先されます。', + 'admin.defaultSettings.saved': '既定値を保存しました', + 'admin.defaultSettings.reset': '組み込みの既定値に戻す', + 'admin.defaultSettings.resetToBuiltIn': 'リセット', + 'admin.tabs.templates': '持ち物テンプレート', + 'admin.packingTemplates.title': '持ち物テンプレート', + 'admin.packingTemplates.subtitle': '旅行で使い回せる持ち物リストを作成', + 'admin.packingTemplates.create': '新規テンプレート', + 'admin.packingTemplates.namePlaceholder': 'テンプレート名(例:ビーチ旅行)', + 'admin.packingTemplates.empty': 'テンプレートはまだありません', + 'admin.packingTemplates.items': 'アイテム', + 'admin.packingTemplates.categories': 'カテゴリ', + 'admin.packingTemplates.itemName': 'アイテム名', + 'admin.packingTemplates.itemCategory': 'カテゴリ', + 'admin.packingTemplates.categoryName': 'カテゴリ名(例:衣類)', + 'admin.packingTemplates.addCategory': 'カテゴリを追加', + 'admin.packingTemplates.created': 'テンプレートを作成しました', + 'admin.packingTemplates.deleted': 'テンプレートを削除しました', + 'admin.packingTemplates.loadError': 'テンプレートの読み込みに失敗しました', + 'admin.packingTemplates.createError': 'テンプレートの作成に失敗しました', + 'admin.packingTemplates.deleteError': 'テンプレートの削除に失敗しました', + 'admin.packingTemplates.saveError': '保存に失敗しました', + 'admin.tabs.addons': 'アドオン', + 'admin.addons.title': 'アドオン', + 'admin.addons.subtitle': '機能を有効/無効にしてTREKをカスタマイズします。', + 'admin.addons.catalog.packing.name': 'リスト', + 'admin.addons.catalog.packing.description': '旅行用の持ち物リストとToDo', + 'admin.addons.catalog.budget.name': '予算', + 'admin.addons.catalog.budget.description': '支出の管理と予算計画', + 'admin.addons.catalog.documents.name': 'ドキュメント', + 'admin.addons.catalog.documents.description': '旅行書類の保存・管理', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'カレンダー表示の個人休暇プランナー', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': '訪問国と旅行統計の世界地図', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'リアルタイムのメモ、投票、チャット', + 'admin.addons.catalog.memories.name': '写真(Immich)', + 'admin.addons.catalog.memories.description': 'Immichで旅行写真を共有', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'AI連携のためのModel Context Protocol', + 'admin.addons.subtitleBefore': '機能を有効/無効にして ', + 'admin.addons.subtitleAfter': ' をカスタマイズします。', + 'admin.addons.enabled': '有効', + 'admin.addons.disabled': '無効', + 'admin.addons.type.trip': '旅行', + 'admin.addons.type.global': '全体', + 'admin.addons.type.integration': '連携', + 'admin.addons.tripHint': '各旅行内のタブとして利用可能', + 'admin.addons.globalHint': 'メインナビの独立セクションとして利用可能', + 'admin.addons.integrationHint': '専用ページのないバックエンド/API連携', + 'admin.addons.toast.updated': 'アドオンを更新しました', + 'admin.addons.toast.error': 'アドオンの更新に失敗しました', + 'admin.addons.noAddons': '利用可能なアドオンはありません', + 'admin.weather.title': '天気データ', + 'admin.weather.badge': '2026年3月24日以降', + 'admin.weather.description': + 'TREKは天気データにOpen‑Meteoを使用しています。無料でオープンソース、APIキーは不要です。', + 'admin.weather.forecast': '16日間予報', + 'admin.weather.forecastDesc': '以前は5日(OpenWeatherMap)', + 'admin.weather.climate': '過去の気候データ', + 'admin.weather.climateDesc': '16日以降は過去85年の平均値', + 'admin.weather.requests': '1日10,000リクエスト', + 'admin.weather.requestsDesc': '無料、APIキー不要', + 'admin.weather.locationHint': + '各日の座標付き最初の場所を基準にします。場所が未割り当ての場合は、場所一覧の任意の場所を参照します。', + 'admin.tabs.mcpTokens': 'MCPトークン', + 'admin.mcpTokens.title': 'MCPトークン', + 'admin.mcpTokens.subtitle': 'すべてのユーザーのAPIトークンを管理', + 'admin.mcpTokens.sectionTitle': 'API トークン', + 'admin.mcpTokens.owner': '所有者', + 'admin.mcpTokens.tokenName': 'トークン名', + 'admin.mcpTokens.created': '作成日', + 'admin.mcpTokens.lastUsed': '最終使用', + 'admin.mcpTokens.never': '未使用', + 'admin.mcpTokens.empty': 'MCPトークンはまだ作成されていません', + 'admin.mcpTokens.deleteTitle': 'トークンを削除', + 'admin.mcpTokens.deleteMessage': + 'このトークンは即座に失効します。ユーザーはこのトークン経由のMCPアクセスを失います。', + 'admin.mcpTokens.deleteSuccess': 'トークンを削除しました', + 'admin.mcpTokens.deleteError': 'トークンの削除に失敗しました', + 'admin.mcpTokens.loadError': 'トークンの読み込みに失敗しました', + 'admin.oauthSessions.sectionTitle': 'OAuthセッション', + 'admin.oauthSessions.clientName': 'クライアント', + 'admin.oauthSessions.owner': '所有者', + 'admin.oauthSessions.scopes': 'スコープ', + 'admin.oauthSessions.created': '作成日時', + 'admin.oauthSessions.empty': '有効なOAuthセッションはありません', + 'admin.oauthSessions.revokeTitle': 'セッションを無効化', + 'admin.oauthSessions.revokeMessage': + 'このOAuthセッションは即時無効化され、クライアントはMCPへのアクセスを失います。', + 'admin.oauthSessions.revokeSuccess': 'セッションを無効化しました', + 'admin.oauthSessions.revokeError': 'セッションの無効化に失敗しました', + 'admin.oauthSessions.loadError': 'OAuthセッションの読み込みに失敗しました', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'セキュリティおよび管理イベント(バックアップ、ユーザー、MFA、設定)。', + 'admin.audit.empty': '監査ログはまだありません。', + 'admin.audit.refresh': '更新', + 'admin.audit.loadMore': 'さらに読み込む', + 'admin.audit.showing': '{count}件表示 · 全{total}件', + 'admin.audit.col.time': '時刻', + 'admin.audit.col.user': 'ユーザー', + 'admin.audit.col.action': '操作', + 'admin.audit.col.resource': '対象', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': '詳細', + 'admin.github.title': 'リリース履歴', + 'admin.github.subtitle': '{repo} の最新アップデート', + 'admin.github.latest': '最新', + 'admin.github.prerelease': 'プレリリース', + 'admin.github.showDetails': '詳細を表示', + 'admin.github.hideDetails': '詳細を非表示', + 'admin.github.loadMore': 'さらに読み込む', + 'admin.github.loading': '読み込み中...', + 'admin.github.error': 'リリースの読み込みに失敗しました', + 'admin.github.by': '作成者', + 'admin.github.support': 'TREKの開発を支援', + 'admin.update.available': '更新があります', + 'admin.update.text': + 'TREK {version} が利用可能です。現在は {current} を使用しています。', + 'admin.update.button': 'GitHubで見る', + 'admin.update.install': '更新をインストール', + 'admin.update.confirmTitle': '更新をインストールしますか?', + 'admin.update.confirmText': + 'TREKを {current} から {version} に更新します。更新後、サーバーは自動的に再起動します。', + 'admin.update.dataInfo': + 'すべてのデータ(旅行、ユーザー、APIキー、アップロード、Vacay、Atlas、予算)は保持されます。', + 'admin.update.warning': '再起動中、アプリは短時間利用できません。', + 'admin.update.confirm': '今すぐ更新', + 'admin.update.installing': '更新中…', + 'admin.update.success': '更新が完了しました!サーバーを再起動しています…', + 'admin.update.failed': '更新に失敗しました', + 'admin.update.backupHint': + '更新前にバックアップを作成することをおすすめします。', + 'admin.update.backupLink': 'バックアップへ', + 'admin.update.howTo': '更新方法', + 'admin.update.dockerText': + 'TREKはDockerで実行されています。{version} に更新するには、サーバーで次のコマンドを実行してください:', + 'admin.update.reloadHint': '数秒後にページを再読み込みしてください。', + 'admin.tabs.permissions': '権限', + 'admin.addons.catalog.journey.name': '日記', + 'admin.addons.catalog.journey.description': + 'チェックイン、写真、日ごとのストーリーで旅を記録', +}; +export default admin; diff --git a/shared/src/i18n/ja/airport.ts b/shared/src/i18n/ja/airport.ts new file mode 100644 index 00000000..ac37c3e8 --- /dev/null +++ b/shared/src/i18n/ja/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': '空港コードまたは都市名(例:FRA)', +}; +export default airport; diff --git a/shared/src/i18n/ja/atlas.ts b/shared/src/i18n/ja/atlas.ts new file mode 100644 index 00000000..4599557c --- /dev/null +++ b/shared/src/i18n/ja/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': '世界に広がるあなたの旅の足跡', + 'atlas.countries': '国', + 'atlas.trips': '旅行', + 'atlas.places': '場所', + 'atlas.unmark': '削除', + 'atlas.confirmMark': 'この国を訪問済みにしますか?', + 'atlas.confirmUnmark': 'この国を訪問済みリストから削除しますか?', + 'atlas.confirmUnmarkRegion': 'この地域を訪問済みリストから削除しますか?', + 'atlas.markVisited': '訪問済みにする', + 'atlas.markVisitedHint': 'この国を訪問済みリストに追加', + 'atlas.markRegionVisitedHint': 'この地域を訪問済みリストに追加', + 'atlas.addToBucket': '行きたいリストに追加', + 'atlas.addPoi': '場所を追加', + 'atlas.searchCountry': '国を検索...', + 'atlas.bucketNamePlaceholder': '名前(国・都市・場所など)', + 'atlas.month': '月', + 'atlas.year': '年', + 'atlas.addToBucketHint': '行きたい場所として保存', + 'atlas.bucketWhen': '訪問予定はいつですか?', + 'atlas.statsTab': '統計', + 'atlas.bucketTab': '行きたいリスト', + 'atlas.addBucket': '行きたいリストに追加', + 'atlas.bucketNotesPlaceholder': 'メモ(任意)', + 'atlas.bucketEmpty': '行きたいリストは空です', + 'atlas.bucketEmptyHint': '行ってみたい場所を追加しましょう', + 'atlas.days': '日', + 'atlas.visitedCountries': '訪問国', + 'atlas.cities': '都市', + 'atlas.noData': '旅行データがありません', + 'atlas.noDataHint': '旅行を作成して場所を追加すると、マップに表示されます', + 'atlas.lastTrip': '前回の旅行', + 'atlas.nextTrip': '次の旅行', + 'atlas.daysLeft': '残り日数', + 'atlas.streak': '連続', + 'atlas.years': '年', + 'atlas.yearInRow': '年連続', + 'atlas.yearsInRow': '年連続', + 'atlas.tripIn': '旅行', + 'atlas.tripsIn': '旅行', + 'atlas.since': '開始', + 'atlas.europe': 'ヨーロッパ', + 'atlas.asia': 'アジア', + 'atlas.northAmerica': '北アメリカ', + 'atlas.southAmerica': '南アメリカ', + 'atlas.africa': 'アフリカ', + 'atlas.oceania': 'オセアニア', + 'atlas.other': 'その他', + 'atlas.firstVisit': '最初の旅行', + 'atlas.lastVisitLabel': '最後の旅行', + 'atlas.tripSingular': '旅行', + 'atlas.tripPlural': '旅行', + 'atlas.placeVisited': '訪問した場所', + 'atlas.placesVisited': '訪問した場所', +}; +export default atlas; diff --git a/shared/src/i18n/ja/backup.ts b/shared/src/i18n/ja/backup.ts new file mode 100644 index 00000000..148436bd --- /dev/null +++ b/shared/src/i18n/ja/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'データバックアップ', + 'backup.subtitle': 'データベースとアップロードされたすべてのファイル', + 'backup.refresh': '更新', + 'backup.upload': 'バックアップをアップロード', + 'backup.uploading': 'アップロード中…', + 'backup.create': 'バックアップを作成', + 'backup.creating': '作成中…', + 'backup.empty': 'バックアップはまだありません', + 'backup.createFirst': '最初のバックアップを作成', + 'backup.download': 'ダウンロード', + 'backup.restore': '復元', + 'backup.confirm.restore': + 'バックアップ「{name}」を復元しますか?\n\n現在のすべてのデータはバックアップで置き換えられます。', + 'backup.confirm.uploadRestore': + 'バックアップファイル「{name}」をアップロードして復元しますか?\n\n現在のすべてのデータは上書きされます。', + 'backup.confirm.delete': 'バックアップ「{name}」を削除しますか?', + 'backup.toast.loadError': 'バックアップの読み込みに失敗しました', + 'backup.toast.created': 'バックアップを作成しました', + 'backup.toast.createError': 'バックアップの作成に失敗しました', + 'backup.toast.restored': + 'バックアップを復元しました。ページを再読み込みします…', + 'backup.toast.restoreError': '復元に失敗しました', + 'backup.toast.uploadError': 'アップロードに失敗しました', + 'backup.toast.deleted': 'バックアップを削除しました', + 'backup.toast.deleteError': '削除に失敗しました', + 'backup.toast.downloadError': 'ダウンロードに失敗しました', + 'backup.toast.settingsSaved': '自動バックアップ設定を保存しました', + 'backup.toast.settingsError': '設定の保存に失敗しました', + 'backup.auto.title': '自動バックアップ', + 'backup.auto.subtitle': 'スケジュールに基づいて自動実行', + 'backup.auto.enable': '自動バックアップを有効化', + 'backup.auto.enableHint': '選択したスケジュールで自動的に作成されます', + 'backup.auto.interval': '間隔', + 'backup.auto.hour': '実行時刻', + 'backup.auto.hourHint': 'サーバーのローカル時刻({format}形式)', + 'backup.auto.dayOfWeek': '曜日', + 'backup.auto.dayOfMonth': '月の日', + 'backup.auto.dayOfMonthHint': + 'すべての月に対応するため1~28に制限されています', + 'backup.auto.scheduleSummary': 'スケジュール', + 'backup.auto.summaryDaily': '毎日 {hour}:00', + 'backup.auto.summaryWeekly': '毎週{day} {hour}:00', + 'backup.auto.summaryMonthly': '毎月{day}日 {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + '自動バックアップはDockerの環境変数で設定されています。変更するにはdocker-compose.ymlを更新し、コンテナを再起動してください。', + 'backup.auto.copyEnv': 'Docker環境変数をコピー', + 'backup.auto.envCopied': 'Docker環境変数をコピーしました', + 'backup.auto.keepLabel': '古いバックアップを削除', + 'backup.dow.sunday': '日', + 'backup.dow.monday': '月', + 'backup.dow.tuesday': '火', + 'backup.dow.wednesday': '水', + 'backup.dow.thursday': '木', + 'backup.dow.friday': '金', + 'backup.dow.saturday': '土', + 'backup.interval.hourly': '毎時間', + 'backup.interval.daily': '毎日', + 'backup.interval.weekly': '毎週', + 'backup.interval.monthly': '毎月', + 'backup.keep.1day': '1日', + 'backup.keep.3days': '3日', + 'backup.keep.7days': '7日', + 'backup.keep.14days': '14日', + 'backup.keep.30days': '30日', + 'backup.keep.forever': '無期限', + 'backup.restoreConfirmTitle': 'バックアップを復元しますか?', + 'backup.restoreWarning': + '現在のすべてのデータ(旅行、場所、ユーザー、アップロード)はバックアップで完全に置き換えられます。この操作は元に戻せません。', + 'backup.restoreTip': + 'ヒント:復元前に現在の状態をバックアップすることをおすすめします。', + 'backup.restoreConfirm': 'はい、復元します', +}; +export default backup; diff --git a/shared/src/i18n/ja/budget.ts b/shared/src/i18n/ja/budget.ts new file mode 100644 index 00000000..33466e55 --- /dev/null +++ b/shared/src/i18n/ja/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': '予算', + 'budget.exportCsv': 'CSVを書き出し', + 'budget.emptyTitle': '予算はまだありません', + 'budget.emptyText': 'カテゴリと項目を作成して旅行予算を計画しましょう', + 'budget.emptyPlaceholder': 'カテゴリ名を入力...', + 'budget.createCategory': 'カテゴリを作成', + 'budget.category': 'カテゴリ', + 'budget.categoryName': 'カテゴリ名', + 'budget.table.name': '名前', + 'budget.table.total': '合計', + 'budget.table.persons': '人数', + 'budget.table.days': '日数', + 'budget.table.perPerson': '1人あたり', + 'budget.table.perDay': '1日あたり', + 'budget.table.perPersonDay': '人/日', + 'budget.table.note': 'メモ', + 'budget.table.date': '日付', + 'budget.newEntry': '新しい項目', + 'budget.defaultEntry': '新しい項目', + 'budget.defaultCategory': '新しいカテゴリ', + 'budget.total': '合計', + 'budget.totalBudget': '総予算', + 'budget.byCategory': 'カテゴリ別', + 'budget.editTooltip': 'クリックして編集', + 'budget.linkedToReservation': '予約に連携中 — 名前はそちらで編集してください', + 'budget.confirm.deleteCategory': + '{count}件の項目があるカテゴリ「{name}」を削除しますか?', + 'budget.deleteCategory': 'カテゴリを削除', + 'budget.perPerson': '1人あたり', + 'budget.paid': '支払済み', + 'budget.open': '未精算', + 'budget.noMembers': 'メンバー未割り当て', + 'budget.settlement': '精算', + 'budget.settlementInfo': + '予算項目のメンバーアイコンをクリックして緑にすると、支払い済みを示します。精算では、誰が誰にいくら支払うべきかを表示します。', + 'budget.netBalances': '差引残高', + 'budget.categoriesLabel': 'カテゴリ', +}; +export default budget; diff --git a/shared/src/i18n/ja/categories.ts b/shared/src/i18n/ja/categories.ts new file mode 100644 index 00000000..5d644e98 --- /dev/null +++ b/shared/src/i18n/ja/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'カテゴリ', + 'categories.subtitle': '場所のカテゴリを管理', + 'categories.new': '新しいカテゴリ', + 'categories.empty': 'カテゴリはまだありません', + 'categories.namePlaceholder': 'カテゴリ名', + 'categories.icon': 'アイコン', + 'categories.color': '色', + 'categories.customColor': 'カスタムカラーを選択', + 'categories.preview': 'プレビュー', + 'categories.defaultName': 'カテゴリ', + 'categories.update': '更新', + 'categories.create': '作成', + 'categories.confirm.delete': + 'カテゴリを削除しますか?このカテゴリの場所は削除されません。', + 'categories.toast.loadError': 'カテゴリの読み込みに失敗しました', + 'categories.toast.nameRequired': '名前を入力してください', + 'categories.toast.updated': 'カテゴリを更新しました', + 'categories.toast.created': 'カテゴリを作成しました', + 'categories.toast.saveError': '保存に失敗しました', + 'categories.toast.deleted': 'カテゴリを削除しました', + 'categories.toast.deleteError': '削除に失敗しました', +}; +export default categories; diff --git a/shared/src/i18n/ja/collab.ts b/shared/src/i18n/ja/collab.ts new file mode 100644 index 00000000..31abd110 --- /dev/null +++ b/shared/src/i18n/ja/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'チャット', + 'collab.tabs.notes': 'ノート', + 'collab.tabs.polls': '投票', + 'collab.whatsNext.title': '次にすること', + 'collab.whatsNext.today': '今日', + 'collab.whatsNext.tomorrow': '明日', + 'collab.whatsNext.empty': '予定されたアクティビティはありません', + 'collab.whatsNext.until': '〜', + 'collab.whatsNext.emptyHint': + '時間が設定されたアクティビティがここに表示されます', + 'collab.chat.send': '送信', + 'collab.chat.placeholder': 'メッセージを入力…', + 'collab.chat.empty': '会話を始めましょう', + 'collab.chat.emptyHint': 'メッセージは旅行メンバー全員と共有されます', + 'collab.chat.emptyDesc': 'アイデアや計画、最新情報を共有しましょう', + 'collab.chat.today': '今日', + 'collab.chat.yesterday': '昨日', + 'collab.chat.deletedMessage': 'メッセージを削除しました', + 'collab.chat.reply': '返信', + 'collab.chat.loadMore': '以前のメッセージを読み込む', + 'collab.chat.justNow': 'たった今', + 'collab.chat.minutesAgo': '{n}分前', + 'collab.chat.hoursAgo': '{n}時間前', + 'collab.notes.title': 'ノート', + 'collab.notes.new': '新規ノート', + 'collab.notes.empty': 'まだノートがありません', + 'collab.notes.emptyHint': 'アイデアや計画を書き留めましょう', + 'collab.notes.all': 'すべて', + 'collab.notes.titlePlaceholder': 'ノートのタイトル', + 'collab.notes.contentPlaceholder': '内容を入力…', + 'collab.notes.categoryPlaceholder': 'カテゴリ', + 'collab.notes.newCategory': '新しいカテゴリ…', + 'collab.notes.category': 'カテゴリ', + 'collab.notes.noCategory': 'カテゴリなし', + 'collab.notes.color': '色', + 'collab.notes.save': '保存', + 'collab.notes.cancel': 'キャンセル', + 'collab.notes.edit': '編集', + 'collab.notes.delete': '削除', + 'collab.notes.pin': '固定', + 'collab.notes.unpin': '固定を解除', + 'collab.notes.daysAgo': '{n}日前', + 'collab.notes.categorySettings': 'カテゴリ管理', + 'collab.notes.create': '作成', + 'collab.notes.website': 'ウェブサイト', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'ファイルを添付', + 'collab.notes.noCategoriesYet': 'カテゴリがまだありません', + 'collab.notes.emptyDesc': 'ノートを作成して始めましょう', + 'collab.polls.title': '投票', + 'collab.polls.new': '新しい投票', + 'collab.polls.empty': 'まだ投票がありません', + 'collab.polls.emptyHint': '質問してみんなで投票しましょう', + 'collab.polls.question': '質問', + 'collab.polls.questionPlaceholder': '何をしますか?', + 'collab.polls.addOption': '+ 選択肢を追加', + 'collab.polls.optionPlaceholder': '選択肢 {n}', + 'collab.polls.create': '投票を作成', + 'collab.polls.close': '閉じる', + 'collab.polls.closed': '終了', + 'collab.polls.votes': '{n}票', + 'collab.polls.vote': '{n}票', + 'collab.polls.multipleChoice': '複数選択', + 'collab.polls.multiChoice': '複数選択', + 'collab.polls.deadline': '締切', + 'collab.polls.option': '選択肢', + 'collab.polls.options': '選択肢', + 'collab.polls.delete': '削除', + 'collab.polls.closedSection': '終了', +}; +export default collab; diff --git a/shared/src/i18n/ja/common.ts b/shared/src/i18n/ja/common.ts new file mode 100644 index 00000000..d6ef7f51 --- /dev/null +++ b/shared/src/i18n/ja/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': '保存', + 'common.showMore': 'もっと見る', + 'common.showLess': '閉じる', + 'common.cancel': 'キャンセル', + 'common.clear': 'クリア', + 'common.delete': '削除', + 'common.edit': '編集', + 'common.add': '追加', + 'common.loading': '読み込み中…', + 'common.import': 'インポート', + 'common.select': '選択', + 'common.selectAll': 'すべて選択', + 'common.deselectAll': 'すべて解除', + 'common.error': 'エラー', + 'common.unknownError': '不明なエラー', + 'common.tooManyAttempts': + '試行回数が多すぎます。時間をおいて再度お試しください。', + 'common.back': '戻る', + 'common.all': 'すべて', + 'common.close': '閉じる', + 'common.open': '開く', + 'common.upload': 'アップロード', + 'common.search': '検索', + 'common.confirm': '確認', + 'common.ok': 'OK', + 'common.yes': 'はい', + 'common.no': 'いいえ', + 'common.or': 'または', + 'common.none': 'なし', + 'common.date': '日付', + 'common.rename': '名前を変更', + 'common.discardChanges': '変更をキャンセル', + 'common.discard': 'キャンセル', + 'common.name': '名前', + 'common.email': 'メールアドレス', + 'common.password': 'パスワード', + 'common.saving': '保存中…', + 'common.justNow': 'たった今', + 'common.hoursAgo': '{count}時間前', + 'common.daysAgo': '{count}日前', + 'common.saved': '保存しました', + 'common.update': '更新', + 'common.change': '変更', + 'common.uploading': 'アップロード中…', + 'common.backToPlanning': 'プランに戻る', + 'common.reset': 'リセット', + 'common.expand': '展開', + 'common.collapse': '折りたたむ', + 'common.copy': 'コピー', + 'common.copied': 'コピーしました', +}; +export default common; diff --git a/shared/src/i18n/ja/dashboard.ts b/shared/src/i18n/ja/dashboard.ts new file mode 100644 index 00000000..64493928 --- /dev/null +++ b/shared/src/i18n/ja/dashboard.ts @@ -0,0 +1,120 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'マイ旅行', + 'dashboard.subtitle.loading': '旅行を読み込み中...', + 'dashboard.subtitle.trips': '{count}件の旅行({archived}件アーカイブ)', + 'dashboard.subtitle.empty': '最初の旅行を始めましょう', + 'dashboard.subtitle.activeOne': '進行中の旅行 {count}件', + 'dashboard.subtitle.activeMany': '進行中の旅行 {count}件', + 'dashboard.subtitle.archivedSuffix': ' · アーカイブ {count}件', + 'dashboard.newTrip': '新しい旅行', + 'dashboard.gridView': 'グリッド表示', + 'dashboard.listView': 'リスト表示', + 'dashboard.currency': '通貨', + 'dashboard.timezone': 'タイムゾーン', + 'dashboard.localTime': '現地', + 'dashboard.timezoneCustomTitle': 'カスタムタイムゾーン', + 'dashboard.timezoneCustomLabelPlaceholder': 'ラベル(任意)', + 'dashboard.timezoneCustomTzPlaceholder': '例:America/New_York', + 'dashboard.timezoneCustomAdd': '追加', + 'dashboard.timezoneCustomErrorEmpty': 'タイムゾーンIDを入力してください', + 'dashboard.timezoneCustomErrorInvalid': + '無効なタイムゾーンです(例:Europe/Berlin)', + 'dashboard.timezoneCustomErrorDuplicate': 'すでに追加されています', + 'dashboard.emptyTitle': '旅行はまだありません', + 'dashboard.emptyText': '最初の旅行を作成して計画を始めましょう!', + 'dashboard.emptyButton': '最初の旅行を作成', + 'dashboard.nextTrip': '次の旅行', + 'dashboard.shared': '共有済み', + 'dashboard.sharedBy': '{name}さんが共有', + 'dashboard.days': '日数', + 'dashboard.places': '場所', + 'dashboard.members': '同行者', + 'dashboard.archive': 'アーカイブ', + 'dashboard.copyTrip': 'コピー', + 'dashboard.copySuffix': 'コピー', + 'dashboard.restore': '復元', + 'dashboard.archived': 'アーカイブ済み', + 'dashboard.status.ongoing': '進行中', + 'dashboard.status.today': '今日', + 'dashboard.status.tomorrow': '明日', + 'dashboard.status.past': '過去', + 'dashboard.status.daysLeft': '残り{count}日', + 'dashboard.toast.loadError': '旅行の読み込みに失敗しました', + 'dashboard.toast.created': '旅行を作成しました!', + 'dashboard.toast.createError': '旅行の作成に失敗しました', + 'dashboard.toast.updated': '旅行を更新しました!', + 'dashboard.toast.updateError': '旅行の更新に失敗しました', + 'dashboard.toast.deleted': '旅行を削除しました', + 'dashboard.toast.deleteError': '旅行の削除に失敗しました', + 'dashboard.toast.archived': '旅行をアーカイブしました', + 'dashboard.toast.archiveError': '旅行のアーカイブに失敗しました', + 'dashboard.toast.restored': '旅行を復元しました', + 'dashboard.toast.restoreError': '旅行の復元に失敗しました', + 'dashboard.toast.copied': '旅行をコピーしました!', + 'dashboard.toast.copyError': '旅行のコピーに失敗しました', + 'dashboard.confirm.delete': + '旅行「{title}」を削除しますか?すべての場所と計画は完全に削除されます。', + 'dashboard.confirm.copy.title': 'この旅行をコピーしますか?', + 'dashboard.confirm.copy.willCopy': 'コピーされる内容', + 'dashboard.confirm.copy.will1': '日数、訪問先 & 日ごとの割り当て', + 'dashboard.confirm.copy.will2': '宿泊施設 & 予約', + 'dashboard.confirm.copy.will3': '予算項目 & カテゴリの順序', + 'dashboard.confirm.copy.will4': '持ち物リスト(未チェックのみ)', + 'dashboard.confirm.copy.will5': 'TODO(未割り当て & 未チェック)', + 'dashboard.confirm.copy.will6': '日ごとのメモ', + 'dashboard.confirm.copy.wontCopy': 'コピーされない内容', + 'dashboard.confirm.copy.wont1': '共同編集者 & メンバー割り当て', + 'dashboard.confirm.copy.wont2': '共同ノート、投票 & メッセージ', + 'dashboard.confirm.copy.wont3': 'ファイル & 写真', + 'dashboard.confirm.copy.wont4': '共有トークン', + 'dashboard.confirm.copy.confirm': '旅行をコピー', + 'dashboard.editTrip': '旅行を編集', + 'dashboard.createTrip': '新しい旅行を作成', + 'dashboard.tripTitle': 'タイトル', + 'dashboard.tripTitlePlaceholder': '例:日本の夏', + 'dashboard.tripDescription': '説明', + 'dashboard.tripDescriptionPlaceholder': 'この旅行について', + 'dashboard.startDate': '開始日', + 'dashboard.endDate': '終了日', + 'dashboard.dayCount': '日数', + 'dashboard.dayCountHint': '日付が未設定の場合に作成する日数です。', + 'dashboard.noDateHint': + '日付未設定 — 既定で7日分が作成されます。後で変更できます。', + 'dashboard.coverImage': 'カバー画像', + 'dashboard.addCoverImage': 'カバー画像を追加(またはドラッグ&ドロップ)', + 'dashboard.addMembers': '同行者', + 'dashboard.addMember': 'メンバーを追加', + 'dashboard.coverSaved': 'カバー画像を保存しました', + 'dashboard.coverUploadError': 'アップロードに失敗しました', + 'dashboard.coverRemoveError': '削除に失敗しました', + 'dashboard.titleRequired': 'タイトルは必須です', + 'dashboard.endDateError': '終了日は開始日より後にしてください', + 'dashboard.greeting.morning': 'おはようございます、', + 'dashboard.greeting.afternoon': 'こんにちは、', + 'dashboard.greeting.evening': 'こんばんは、', + 'dashboard.mobile.liveNow': 'ライブ中', + 'dashboard.mobile.tripProgress': '旅行の進行状況', + 'dashboard.mobile.daysLeft': '残り{count}日', + 'dashboard.mobile.places': '場所', + 'dashboard.mobile.buddies': '仲間', + 'dashboard.mobile.newTrip': '新しい旅行', + 'dashboard.mobile.currency': '通貨', + 'dashboard.mobile.timezone': 'タイムゾーン', + 'dashboard.mobile.upcomingTrips': '今後の旅行', + 'dashboard.mobile.yourTrips': 'あなたの旅行', + 'dashboard.mobile.trips': '旅行', + 'dashboard.mobile.starts': '開始', + 'dashboard.mobile.duration': '期間', + 'dashboard.mobile.day': '日', + 'dashboard.mobile.days': '日', + 'dashboard.mobile.ongoing': '進行中', + 'dashboard.mobile.startsToday': '今日開始', + 'dashboard.mobile.tomorrow': '明日', + 'dashboard.mobile.inDays': '{count}日後', + 'dashboard.mobile.inMonths': '{count}か月後', + 'dashboard.mobile.completed': '完了', + 'dashboard.mobile.currencyConverter': '通貨換算', +}; +export default dashboard; diff --git a/shared/src/i18n/ja/day.ts b/shared/src/i18n/ja/day.ts new file mode 100644 index 00000000..da111db3 --- /dev/null +++ b/shared/src/i18n/ja/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': '降水確率', + 'day.precipitation': '降水量', + 'day.wind': '風', + 'day.sunrise': '日の出', + 'day.sunset': '日の入り', + 'day.hourlyForecast': '時間別予報', + 'day.climateHint': + '過去の平均値 — 実際の予報はこの日付の16日前から表示されます。', + 'day.noWeather': '天気データがありません。座標付きの場所を追加してください。', + 'day.overview': '1日の概要', + 'day.accommodation': '宿泊先', + 'day.addAccommodation': '宿泊先を追加', + 'day.hotelDayRange': '適用日', + 'day.noPlacesForHotel': '先に旅行に場所を追加してください', + 'day.allDays': 'すべて', + 'day.checkIn': 'チェックイン', + 'day.checkInUntil': 'チェックイン期限', + 'day.checkOut': 'チェックアウト', + 'day.confirmation': '確認', + 'day.editAccommodation': '宿泊先を編集', + 'day.reservations': '予約', +}; +export default day; diff --git a/shared/src/i18n/ja/dayplan.ts b/shared/src/i18n/ja/dayplan.ts new file mode 100644 index 00000000..eca6d544 --- /dev/null +++ b/shared/src/i18n/ja/dayplan.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'カレンダーを書き出し(ICS)', + 'dayplan.emptyDay': 'この日の予定はありません', + 'dayplan.cannotReorderTransport': '時刻が固定された予約は並び替えできません', + 'dayplan.confirmRemoveTimeTitle': '時刻を削除しますか?', + 'dayplan.confirmRemoveTimeBody': + 'この場所には固定時刻({time})があります。移動すると時刻が削除され、自由に並び替えできます。', + 'dayplan.confirmRemoveTimeAction': '時刻を削除して移動', + 'dayplan.cannotDropOnTimed': '時刻指定の項目の間には配置できません', + 'dayplan.cannotBreakChronology': '時刻指定の項目や予約の時系列が崩れます', + 'dayplan.addNote': 'メモを追加', + 'dayplan.expandAll': 'すべての日を展開', + 'dayplan.collapseAll': 'すべての日を折りたたむ', + 'dayplan.editNote': 'メモを編集', + 'dayplan.noteAdd': 'メモを追加', + 'dayplan.noteEdit': 'メモを編集', + 'dayplan.noteTitle': 'メモ', + 'dayplan.noteSubtitle': '日別メモ', + 'dayplan.totalCost': '合計費用', + 'dayplan.days': '日', + 'dayplan.dayN': '{n}日目', + 'dayplan.calculating': '計算中...', + 'dayplan.route': 'ルート', + 'dayplan.optimize': '最適化', + 'dayplan.optimized': 'ルートを最適化しました', + 'dayplan.routeError': 'ルートの計算に失敗しました', + 'dayplan.toast.needTwoPlaces': 'ルート最適化には2つ以上の場所が必要です', + 'dayplan.toast.routeOptimized': 'ルートを最適化しました', + 'dayplan.toast.noGeoPlaces': '座標付きの場所がありません', + 'dayplan.confirmed': '確定', + 'dayplan.pendingRes': '保留', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': '日別計画をPDFで書き出し', + 'dayplan.pdfError': 'PDFの書き出しに失敗しました', + 'dayplan.mobile.addPlace': '場所を追加', + 'dayplan.mobile.searchPlaces': '場所を検索…', + 'dayplan.mobile.allAssigned': 'すべて割り当て済み', + 'dayplan.mobile.noMatch': '一致なし', + 'dayplan.mobile.createNew': '新しい場所を作成', +}; +export default dayplan; diff --git a/shared/src/i18n/ja/externalNotifications.ts b/shared/src/i18n/ja/externalNotifications.ts new file mode 100644 index 00000000..3ae1bd02 --- /dev/null +++ b/shared/src/i18n/ja/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const ja: NotificationLocale = { + email: { + footer: 'TREKで通知を有効にしているため、このメールが届きました。', + manage: '設定で通知設定を管理', + madeWith: 'Made with', + openTrek: 'TREKを開く', + }, + events: { + trip_invite: (p) => ({ + title: `「${p.trip}」への旅行招待`, + body: `${p.actor}が${p.invitee || 'メンバー'}を「${p.trip}」の旅行に招待しました。`, + }), + booking_change: (p) => ({ + title: `新しい予約:${p.booking}`, + body: `${p.actor}が「${p.trip}」に「${p.booking}」(${p.type})を追加しました。`, + }), + trip_reminder: (p) => ({ + title: `旅行リマインダー:${p.trip}`, + body: `「${p.trip}」の旅行が近づいています!`, + }), + todo_due: (p) => ({ + title: `期限のタスク:${p.todo}`, + body: `「${p.trip}」の「${p.todo}」は${p.due}が期限です。`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion招待', + body: `${p.actor}が休暇プランの統合に招待しています。TREKを開いて承認または拒否してください。`, + }), + photos_shared: (p) => ({ + title: `${p.count}枚の写真が共有されました`, + body: `${p.actor}が「${p.trip}」で${p.count}枚の写真を共有しました。`, + }), + collab_message: (p) => ({ + title: `「${p.trip}」の新しいメッセージ`, + body: `${p.actor}:${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `パッキング:${p.category}`, + body: `${p.actor}が「${p.trip}」の「${p.category}」カテゴリにあなたを割り当てました。`, + }), + version_available: (p) => ({ + title: '新しいTREKバージョンが利用可能', + body: `TREK ${p.version}が利用可能になりました。管理パネルからアップデートしてください。`, + }), + synology_session_cleared: () => ({ + title: 'Synologyセッションがクリアされました', + body: 'SynologyアカウントまたはURLが変更されました。Synology Photosからログアウトされました。', + }), + }, + passwordReset: { + subject: 'パスワードをリセット', + greeting: 'こんにちは', + body: 'TREKアカウントのパスワードリセットリクエストを受け付けました。以下のボタンをクリックして新しいパスワードを設定してください。', + ctaIntro: 'パスワードをリセット', + expiry: 'このリンクは60分後に期限切れになります。', + ignore: + 'このリクエストをご自身でしていない場合は、このメールを無視してください — パスワードは変更されません。', + }, +}; + +export default ja; diff --git a/shared/src/i18n/ja/files.ts b/shared/src/i18n/ja/files.ts new file mode 100644 index 00000000..0c359ad1 --- /dev/null +++ b/shared/src/i18n/ja/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'ファイル', + 'files.pageTitle': 'ファイル・ドキュメント', + 'files.subtitle': '{trip} のファイル {count} 件', + 'files.download': 'ダウンロード', + 'files.openError': 'ファイルを開けませんでした', + 'files.downloadPdf': 'PDFをダウンロード', + 'files.count': '{count}件のファイル', + 'files.countSingular': '1件のファイル', + 'files.uploaded': '{count}件アップロード', + 'files.uploadError': 'アップロードに失敗しました', + 'files.dropzone': 'ここにファイルをドロップ', + 'files.dropzoneHint': 'またはクリックして参照', + 'files.allowedTypes': '画像、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大50MB', + 'files.uploading': 'アップロード中...', + 'files.filterAll': 'すべて', + 'files.filterPdf': 'PDF', + 'files.filterImages': '画像', + 'files.filterDocs': 'ドキュメント', + 'files.filterCollab': 'Collabメモ', + 'files.sourceCollab': 'Collabメモより', + 'files.empty': 'ファイルはまだありません', + 'files.emptyHint': 'ファイルをアップロードして旅行に添付しましょう', + 'files.openTab': '新しいタブで開く', + 'files.confirm.delete': 'このファイルを削除しますか?', + 'files.toast.deleted': 'ファイルを削除しました', + 'files.toast.deleteError': 'ファイルの削除に失敗しました', + 'files.sourcePlan': '日別計画', + 'files.sourceBooking': '予約', + 'files.sourceTransport': '移動', + 'files.attach': '添付', + 'files.pasteHint': + 'クリップボードから画像を貼り付けることもできます(Ctrl+V)', + 'files.trash': 'ゴミ箱', + 'files.trashEmpty': 'ゴミ箱は空です', + 'files.emptyTrash': 'ゴミ箱を空にする', + 'files.restore': '復元', + 'files.star': 'スター', + 'files.unstar': 'スター解除', + 'files.assign': '割り当て', + 'files.assignTitle': 'ファイルを割り当て', + 'files.assignPlace': '場所', + 'files.assignBooking': '予約', + 'files.assignTransport': '移動', + 'files.unassigned': '未割り当て', + 'files.unlink': 'リンクを解除', + 'files.toast.trashed': 'ゴミ箱に移動しました', + 'files.toast.restored': 'ファイルを復元しました', + 'files.toast.trashEmptied': 'ゴミ箱を空にしました', + 'files.toast.assigned': 'ファイルを割り当てました', + 'files.toast.assignError': '割り当てに失敗しました', + 'files.toast.restoreError': '復元に失敗しました', + 'files.confirm.permanentDelete': + 'このファイルを完全に削除しますか?元に戻せません。', + 'files.confirm.emptyTrash': + 'ゴミ箱内のファイルをすべて完全に削除しますか?元に戻せません。', + 'files.noteLabel': 'メモ', + 'files.notePlaceholder': 'メモを追加...', +}; +export default files; diff --git a/shared/src/i18n/ja/index.ts b/shared/src/i18n/ja/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/ja/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/ja/inspector.ts b/shared/src/i18n/ja/inspector.ts new file mode 100644 index 00000000..ed9ad9a7 --- /dev/null +++ b/shared/src/i18n/ja/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': '営業中', + 'inspector.closed': '営業時間外', + 'inspector.openingHours': '営業時間', + 'inspector.showHours': '営業時間を表示', + 'inspector.files': 'ファイル', + 'inspector.remove': '削除', + 'inspector.filesCount': '{count}件のファイル', + 'inspector.removeFromDay': 'この日から削除', + 'inspector.addToDay': '日に追加', + 'inspector.confirmedRes': '確定済み予約', + 'inspector.pendingRes': '保留中の予約', + 'inspector.google': 'Googleマップで開く', + 'inspector.website': 'Webサイトを開く', + 'inspector.addRes': '予約', + 'inspector.editRes': '予約を編集', + 'inspector.participants': '参加者', + 'inspector.trackStats': '統計を記録', +}; +export default inspector; diff --git a/shared/src/i18n/ja/journey.ts b/shared/src/i18n/ja/journey.ts new file mode 100644 index 00000000..62108f91 --- /dev/null +++ b/shared/src/i18n/ja/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': '日記を検索…', + 'journey.search.noResults': '「{query}」に一致する日記はありません', + 'journey.title': '日記', + 'journey.subtitle': '旅の記録をリアルタイムで残そう', + 'journey.new': '新しい日記', + 'journey.create': '作成', + 'journey.titlePlaceholder': 'どこへ行きますか?', + 'journey.empty': '日記はまだありません', + 'journey.emptyHint': '次の旅を記録してみましょう', + 'journey.deleted': '日記を削除しました', + 'journey.createError': '日記を作成できませんでした', + 'journey.deleteError': '日記を削除できませんでした', + 'journey.deleteConfirmTitle': '削除', + 'journey.deleteConfirmMessage': '「{title}」を削除しますか?元に戻せません。', + 'journey.deleteConfirmGeneric': '本当に削除しますか?', + 'journey.notFound': '日記が見つかりません', + 'journey.photos': '写真', + 'journey.timelineEmpty': 'まだ立ち寄りがありません', + 'journey.timelineEmptyHint': 'チェックインするか、日記を書いて始めましょう', + 'journey.status.draft': '下書き', + 'journey.status.active': '進行中', + 'journey.status.completed': '完了', + 'journey.status.upcoming': '予定', + 'journey.status.archived': 'アーカイブ', + 'journey.checkin.add': 'チェックイン', + 'journey.checkin.namePlaceholder': '場所名', + 'journey.checkin.notesPlaceholder': 'メモ(任意)', + 'journey.checkin.save': '保存', + 'journey.checkin.error': 'チェックインを保存できませんでした', + 'journey.entry.add': '日記', + 'journey.entry.edit': '編集', + 'journey.entry.titlePlaceholder': 'タイトル(任意)', + 'journey.entry.bodyPlaceholder': '今日は何がありましたか?', + 'journey.entry.save': '保存', + 'journey.entry.error': '日記を保存できませんでした', + 'journey.photo.add': '写真', + 'journey.photo.uploadError': 'アップロードに失敗しました', + 'journey.share.share': '共有', + 'journey.share.public': '公開', + 'journey.share.linkCopied': '公開リンクをコピーしました', + 'journey.share.disabled': '公開共有は無効です', + 'journey.editor.titlePlaceholder': 'この瞬間に名前をつけて…', + 'journey.editor.bodyPlaceholder': 'この日のストーリーを書いてみよう…', + 'journey.editor.placePlaceholder': '場所(任意)', + 'journey.editor.tagsPlaceholder': 'タグ:穴場、最高の食事、また行きたい…', + 'journey.visibility.private': '非公開', + 'journey.visibility.shared': '共有', + 'journey.visibility.public': '公開', + 'journey.emptyState.title': 'ここから物語が始まります', + 'journey.emptyState.subtitle': + '場所にチェックインするか、最初の日記を書いてみましょう', + 'journey.frontpage.subtitle': '旅を、忘れられない物語に', + 'journey.frontpage.createJourney': '日記を作成', + 'journey.frontpage.activeJourney': '進行中の日記', + 'journey.frontpage.allJourneys': 'すべての日記', + 'journey.frontpage.journeys': '日記', + 'journey.frontpage.createNew': '新しい日記を作成', + 'journey.frontpage.createNewSub': '旅を選んで、物語を書き、共有しよう', + 'journey.frontpage.live': 'ライブ', + 'journey.frontpage.synced': '同期済み', + 'journey.frontpage.continueWriting': '続けて書く', + 'journey.frontpage.updated': '{time}に更新', + 'journey.frontpage.suggestionLabel': '旅行が終了しました', + 'journey.frontpage.suggestionText': '{title}を日記にしよう', + 'journey.frontpage.dismiss': '閉じる', + 'journey.frontpage.journeyName': '日記名', + 'journey.frontpage.namePlaceholder': '例:東南アジア 2026', + 'journey.frontpage.selectTrips': '旅行を選択', + 'journey.frontpage.tripsSelected': '件選択', + 'journey.frontpage.trips': '旅行', + 'journey.frontpage.placesImported': '場所がインポートされます', + 'journey.frontpage.places': '場所', + 'journey.detail.backToJourney': '日記に戻る', + 'journey.detail.syncedWithTrips': '旅行と同期済み', + 'journey.detail.addEntry': 'エントリーを追加', + 'journey.detail.newEntry': '新しいエントリー', + 'journey.detail.editEntry': 'エントリーを編集', + 'journey.detail.noEntries': 'エントリーはまだありません', + 'journey.detail.noEntriesHint': + '旅行を追加して下書きエントリーを作成しましょう', + 'journey.detail.noPhotos': '写真はまだありません', + 'journey.detail.noPhotosHint': + 'エントリーに写真を追加するか、Immich/Synologyライブラリを表示', + 'journey.detail.journeyTab': '日記', + 'journey.detail.journeyStats': '統計', + 'journey.detail.syncedTrips': '同期中の旅行', + 'journey.detail.noTripsLinked': 'リンクされた旅行はありません', + 'journey.detail.contributors': '参加者', + 'journey.detail.readMore': 'もっと見る', + 'journey.detail.prosCons': '良かった点・気になった点', + 'journey.detail.photos': '写真', + 'journey.detail.day': '{number}日目', + 'journey.detail.places': '場所', + 'journey.stats.days': '日数', + 'journey.stats.cities': '都市', + 'journey.stats.entries': 'エントリー', + 'journey.stats.photos': '写真', + 'journey.stats.places': '場所', + 'journey.skeletons.show': '提案を表示', + 'journey.skeletons.hide': '提案を非表示', + 'journey.verdict.lovedIt': '最高だった', + 'journey.verdict.couldBeBetter': '改善の余地あり', + 'journey.synced.places': '場所', + 'journey.synced.synced': '同期済み', + 'journey.editor.discardChangesConfirm': + '未保存の変更があります。破棄しますか?', + 'journey.editor.uploadPhotos': '写真をアップロード', + 'journey.editor.uploading': 'アップロード中…', + 'journey.editor.fromGallery': 'ギャラリーから', + 'journey.editor.allPhotosAdded': 'すべての写真は追加済みです', + 'journey.editor.writeStory': 'ストーリーを書く…', + 'journey.editor.prosCons': '良かった点・気になった点', + 'journey.editor.pros': '良かった点', + 'journey.editor.cons': '気になった点', + 'journey.editor.proPlaceholder': '良かったこと…', + 'journey.editor.conPlaceholder': 'いまいちだったこと…', + 'journey.editor.addAnother': '追加', + 'journey.editor.date': '日付', + 'journey.editor.location': '場所', + 'journey.editor.searchLocation': '場所を検索…', + 'journey.editor.mood': '気分', + 'journey.editor.weather': '天気', + 'journey.editor.photoFirst': '1番目', + 'journey.editor.makeFirst': '1番目にする', + 'journey.editor.searching': '検索中…', + 'journey.mood.amazing': '最高', + 'journey.mood.good': '良い', + 'journey.mood.neutral': '普通', + 'journey.mood.rough': '大変', + 'journey.weather.sunny': '晴れ', + 'journey.weather.partly': '晴れ時々くもり', + 'journey.weather.cloudy': 'くもり', + 'journey.weather.rainy': '雨', + 'journey.weather.stormy': '嵐', + 'journey.weather.cold': '雪', + 'journey.trips.linkTrip': '旅行をリンク', + 'journey.trips.searchTrip': '旅行を検索', + 'journey.trips.searchPlaceholder': '旅行名または目的地…', + 'journey.trips.noTripsAvailable': '利用できる旅行がありません', + 'journey.trips.link': 'リンク', + 'journey.trips.tripLinked': '旅行をリンクしました', + 'journey.trips.linkFailed': 'リンクに失敗しました', + 'journey.trips.addTrip': '旅行を追加', + 'journey.trips.unlinkTrip': 'リンク解除', + 'journey.trips.unlinkMessage': + '「{title}」のリンクを解除しますか?この旅行から同期されたエントリーと写真はすべて完全に削除されます。元に戻せません。', + 'journey.trips.unlink': '解除', + 'journey.trips.tripUnlinked': 'リンクを解除しました', + 'journey.trips.unlinkFailed': '解除に失敗しました', + 'journey.trips.noTripsLinkedSettings': 'リンクされた旅行はありません', + 'journey.contributors.invite': '参加者を招待', + 'journey.contributors.searchUser': 'ユーザーを検索', + 'journey.contributors.searchPlaceholder': 'ユーザー名またはメール…', + 'journey.contributors.noUsers': 'ユーザーが見つかりません', + 'journey.contributors.role': '役割', + 'journey.contributors.added': '参加者を追加しました', + 'journey.contributors.addFailed': '追加に失敗しました', + 'journey.contributors.remove': '参加者を削除', + 'journey.contributors.removeConfirm': + '{username}をこの日記から削除しますか?', + 'journey.contributors.removed': '参加者を削除しました', + 'journey.contributors.removeFailed': '削除に失敗しました', + 'journey.share.publicShare': '公開共有', + 'journey.share.createLink': '共有リンクを作成', + 'journey.share.linkCreated': '共有リンクを作成しました', + 'journey.share.createFailed': 'リンク作成に失敗しました', + 'journey.share.copy': 'コピー', + 'journey.share.copied': 'コピーしました!', + 'journey.share.timeline': 'タイムライン', + 'journey.share.gallery': 'ギャラリー', + 'journey.share.map': 'マップ', + 'journey.share.removeLink': '共有リンクを削除', + 'journey.share.linkDeleted': '共有リンクを削除しました', + 'journey.share.deleteFailed': '削除に失敗しました', + 'journey.share.updateFailed': '更新に失敗しました', + 'journey.invite.role': '役割', + 'journey.invite.viewer': '閲覧者', + 'journey.invite.editor': '編集者', + 'journey.invite.invite': '招待', + 'journey.invite.inviting': '招待中…', + 'journey.settings.title': '日記設定', + 'journey.settings.coverImage': 'カバー画像', + 'journey.settings.changeCover': 'カバーを変更', + 'journey.settings.addCover': 'カバー画像を追加', + 'journey.settings.name': '名前', + 'journey.settings.subtitle': 'サブタイトル', + 'journey.settings.subtitlePlaceholder': '例:タイ・ベトナム・カンボジア', + 'journey.settings.endJourney': '日記をアーカイブ', + 'journey.settings.reopenJourney': '日記を復元', + 'journey.settings.archived': '日記をアーカイブしました', + 'journey.settings.reopened': '日記を復元しました', + 'journey.settings.endDescription': + 'Liveバッジを非表示にします。いつでも再開できます。', + 'journey.settings.delete': '削除', + 'journey.settings.deleteJourney': '日記を削除', + 'journey.settings.deleteMessage': + '「{title}」を削除しますか?すべてのエントリーと写真が失われます。', + 'journey.settings.saved': '設定を保存しました', + 'journey.settings.saveFailed': '保存に失敗しました', + 'journey.settings.coverUpdated': 'カバーを更新しました', + 'journey.settings.coverFailed': 'アップロードに失敗しました', + 'journey.settings.failedToDelete': '削除に失敗しました', + 'journey.entries.deleteTitle': 'エントリーを削除', + 'journey.photosUploaded': '{count}枚の写真をアップロード', + 'journey.photosAdded': '{count}枚の写真を追加', + 'journey.public.notFound': '見つかりません', + 'journey.public.notFoundMessage': + 'この日記は存在しないか、リンクの有効期限が切れています。', + 'journey.public.readOnly': '閲覧のみ · 公開日記', + 'journey.public.tagline': '旅の記録&探索キット', + 'journey.public.sharedVia': '共有元', + 'journey.public.madeWith': '作成:', + 'journey.pdf.journeyBook': '日記ブック', + 'journey.pdf.madeWith': 'Made with TREK', + 'journey.pdf.day': '日目', + 'journey.pdf.theEnd': 'おわり', + 'journey.pdf.saveAsPdf': 'PDFとして保存', + 'journey.pdf.pages': 'ページ', + 'journey.picker.tripPeriod': '旅行期間', + 'journey.picker.dateRange': '日付範囲', + 'journey.picker.allPhotos': 'すべての写真', + 'journey.picker.albums': 'アルバム', + 'journey.picker.selected': '選択中', + 'journey.picker.addTo': '追加先', + 'journey.picker.newGallery': '新しいギャラリー', + 'journey.picker.selectAll': 'すべて選択', + 'journey.picker.deselectAll': '選択解除', + 'journey.picker.noAlbums': 'アルバムがありません', + 'journey.picker.selectDate': '日付を選択', + 'journey.picker.search': '検索', + 'journey.editor.uploadingProgress': 'アップロード中 {done}/{total}…', + 'journey.editor.uploadFailed': '写真のアップロードに失敗しました', + 'journey.editor.uploadPartialFailed': + '{total}枚中{failed}枚の写真がアップロードに失敗しました — もう一度保存して再試行してください', + 'journey.photosUploadFailed': '一部の写真をアップロードできませんでした', +}; +export default journey; diff --git a/shared/src/i18n/ja/login.ts b/shared/src/i18n/ja/login.ts new file mode 100644 index 00000000..f7621567 --- /dev/null +++ b/shared/src/i18n/ja/login.ts @@ -0,0 +1,95 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'ログインに失敗しました。認証情報を確認してください。', + 'login.tagline': 'あなたの旅行。\nあなたの計画。', + 'login.description': + 'インタラクティブなマップ、予算、リアルタイム同期で、みんなで旅行を計画。', + 'login.features.maps': 'インタラクティブマップ', + 'login.features.mapsDesc': 'Google Places、経路、クラスタリング', + 'login.features.realtime': 'リアルタイム同期', + 'login.features.realtimeDesc': 'WebSocketで共同計画', + 'login.features.budget': '予算管理', + 'login.features.budgetDesc': 'カテゴリ、グラフ、人数別費用', + 'login.features.collab': 'コラボレーション', + 'login.features.collabDesc': '複数ユーザーで旅行を共有', + 'login.features.packing': '持ち物リスト', + 'login.features.packingDesc': 'カテゴリ、進捗、提案', + 'login.features.bookings': '予約', + 'login.features.bookingsDesc': '航空券、ホテル、レストランなど', + 'login.features.files': 'ドキュメント', + 'login.features.filesDesc': '書類のアップロードと管理', + 'login.features.routes': 'スマート経路', + 'login.features.routesDesc': '自動最適化&Google Maps書き出し', + 'login.selfHosted': 'セルフホスト · オープンソース · データはあなたのもの', + 'login.title': 'サインイン', + 'login.subtitle': 'おかえりなさい', + 'login.signingIn': 'サインイン中…', + 'login.signIn': 'サインイン', + 'login.createAdmin': '管理者アカウントを作成', + 'login.createAdminHint': 'TREKの最初の管理者アカウントを設定します。', + 'login.setNewPassword': '新しいパスワードを設定', + 'login.setNewPasswordHint': '続行する前にパスワードを変更してください。', + 'login.createAccount': 'アカウントを作成', + 'login.createAccountHint': '新しいアカウントを登録。', + 'login.creating': '作成中…', + 'login.noAccount': 'アカウントをお持ちでないですか?', + 'login.hasAccount': 'すでにアカウントをお持ちですか?', + 'login.register': '登録', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': 'ユーザー名', + 'login.oidc.registrationDisabled': + '登録は無効です。管理者に連絡してください。', + 'login.oidc.noEmail': 'プロバイダーからメールが取得できませんでした。', + 'login.oidc.tokenFailed': '認証に失敗しました。', + 'login.oidc.invalidState': 'セッションが無効です。もう一度お試しください。', + 'login.demoFailed': 'デモログインに失敗しました', + 'login.oidcSignIn': '{name}でサインイン', + 'login.oidcOnly': + 'パスワード認証は無効です。SSOプロバイダーでサインインしてください。', + 'login.oidcLoggedOut': + 'ログアウトしました。SSOプロバイダーで再度サインインしてください。', + 'login.demoHint': 'デモを試す — 登録不要', + 'login.mfaTitle': '二要素認証', + 'login.mfaSubtitle': '認証アプリの6桁コードを入力してください。', + 'login.mfaCodeLabel': '確認コード', + 'login.mfaCodeRequired': '認証アプリのコードを入力してください。', + 'login.mfaHint': + 'Google Authenticator、Authy などのTOTPアプリを開いてください。', + 'login.mfaBack': '← サインインに戻る', + 'login.mfaVerify': '確認', + 'login.invalidInviteLink': '無効または期限切れの招待リンクです', + 'login.oidcFailed': 'OIDCログインに失敗しました', + 'login.usernameRequired': 'ユーザー名を入力してください', + 'login.passwordMinLength': 'パスワードは8文字以上である必要があります', + 'login.forgotPassword': 'パスワードを忘れた場合', + 'login.forgotPasswordTitle': 'パスワードをリセット', + 'login.forgotPasswordBody': + '登録時のメールアドレスを入力してください。アカウントが存在する場合、リセット用リンクを送信します。', + 'login.forgotPasswordSubmit': 'リセットリンクを送信', + 'login.forgotPasswordSentTitle': 'メールを確認してください', + 'login.forgotPasswordSentBody': + '該当するアカウントがある場合、リセットリンクを送信しました。リンクの有効期限は60分です。', + 'login.forgotPasswordSmtpHintOff': + '注意:管理者がSMTPを設定していないため、リセットリンクはメールではなくサーバーコンソールに出力されます。', + 'login.backToLogin': 'ログインに戻る', + 'login.newPassword': '新しいパスワード', + 'login.confirmPassword': '新しいパスワード(確認)', + 'login.passwordsDontMatch': 'パスワードが一致しません', + 'login.mfaCode': '2FAコード', + 'login.resetPasswordTitle': '新しいパスワードを設定', + 'login.resetPasswordBody': + '以前使用していない強力なパスワードを設定してください(8文字以上)。', + 'login.resetPasswordMfaBody': + '2FAコードまたはバックアップコードを入力してリセットを完了してください。', + 'login.resetPasswordSubmit': 'パスワードをリセット', + 'login.resetPasswordVerify': '確認してリセット', + 'login.resetPasswordSuccessTitle': 'パスワードを更新しました', + 'login.resetPasswordSuccessBody': '新しいパスワードでログインできます。', + 'login.resetPasswordInvalidLink': '無効なリセットリンク', + 'login.resetPasswordInvalidLinkBody': + 'リンクが無効または破損しています。新しいリンクをリクエストしてください。', + 'login.resetPasswordFailed': + 'リセットに失敗しました。リンクの有効期限が切れている可能性があります。', +}; +export default login; diff --git a/shared/src/i18n/ja/map.ts b/shared/src/i18n/ja/map.ts new file mode 100644 index 00000000..6a4b8602 --- /dev/null +++ b/shared/src/i18n/ja/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': '接続', + 'map.showConnections': '予約ルートを表示', + 'map.hideConnections': '予約ルートを非表示', +}; +export default map; diff --git a/shared/src/i18n/ja/members.ts b/shared/src/i18n/ja/members.ts new file mode 100644 index 00000000..3d749039 --- /dev/null +++ b/shared/src/i18n/ja/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': '旅行を共有', + 'members.inviteUser': 'ユーザーを招待', + 'members.selectUser': 'ユーザーを選択…', + 'members.invite': '招待', + 'members.allHaveAccess': 'すでに全員がアクセスできます。', + 'members.access': 'アクセス', + 'members.person': '人', + 'members.persons': '人', + 'members.you': 'あなた', + 'members.owner': 'オーナー', + 'members.leaveTrip': '旅行を退出', + 'members.removeAccess': 'アクセスを削除', + 'members.confirmLeave': '旅行を退出しますか?アクセスできなくなります。', + 'members.confirmRemove': 'このユーザーのアクセスを削除しますか?', + 'members.loadError': 'メンバーの読み込みに失敗しました', + 'members.added': '追加しました', + 'members.addError': '追加に失敗しました', + 'members.removed': 'メンバーを削除しました', + 'members.removeError': '削除に失敗しました', +}; +export default members; diff --git a/shared/src/i18n/ja/memories.ts b/shared/src/i18n/ja/memories.ts new file mode 100644 index 00000000..3103125c --- /dev/null +++ b/shared/src/i18n/ja/memories.ts @@ -0,0 +1,79 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': '写真', + 'memories.notConnected': '{provider_name} が接続されていません', + 'memories.notConnectedHint': + '設定で {provider_name} インスタンスを接続すると、この旅行に写真を追加できます。', + 'memories.notConnectedMultipleHint': + '設定で次の写真プロバイダーのいずれかを接続してください:{provider_names}', + 'memories.noDates': '写真を読み込むには旅行の日付を追加してください。', + 'memories.noPhotos': '写真が見つかりません', + 'memories.noPhotosHint': '{provider_name} にこの旅行期間の写真がありません。', + 'memories.photosFound': '枚の写真', + 'memories.fromOthers': '他のユーザーから', + 'memories.sharePhotos': '写真を共有', + 'memories.sharing': '共有', + 'memories.reviewTitle': '写真を確認', + 'memories.reviewHint': 'クリックして共有から除外できます。', + 'memories.shareCount': '{count}枚の写真を共有', + 'memories.providerUrl': 'サーバーURL', + 'memories.providerApiKey': 'APIキー', + 'memories.providerUsername': 'ユーザー名', + 'memories.providerPassword': 'パスワード', + 'memories.providerOTP': 'MFAコード(有効な場合)', + 'memories.skipSSLVerification': 'SSL証明書の検証をスキップ', + 'memories.immichAutoUpload': 'アップロード時に旅程の写真をImmichにミラー', + 'memories.providerUrlHintSynology': + 'URLにPhotosアプリのパスを含めてください(例:https://nas:5001/photo)', + 'memories.testConnection': '接続をテスト', + 'memories.testFirst': '先に接続をテストしてください', + 'memories.testShort': 'テスト', + 'memories.connected': '接続済み', + 'memories.disconnected': '未接続', + 'memories.connectionSuccess': '{provider_name} に接続しました', + 'memories.connectionError': '{provider_name} に接続できませんでした', + 'memories.saved': '{provider_name} の設定を保存しました', + 'memories.providerDisconnectedBanner': + '{provider_name} との接続が切れています。写真を見るには設定で再接続してください。', + 'memories.saveError': '{provider_name} の設定を保存できませんでした', + 'memories.addPhotos': '写真を追加', + 'memories.linkAlbum': 'アルバムをリンク', + 'memories.selectAlbum': '{provider_name} のアルバムを選択', + 'memories.selectAlbumMultiple': 'アルバムを選択', + 'memories.noAlbums': 'アルバムが見つかりません', + 'memories.syncAlbum': 'アルバムを同期', + 'memories.unlinkAlbum': 'アルバムのリンクを解除', + 'memories.photos': '写真', + 'memories.selectPhotos': '{provider_name} から写真を選択', + 'memories.selectPhotosMultiple': '写真を選択', + 'memories.selectHint': '写真をタップして選択してください。', + 'memories.selected': '選択済み', + 'memories.addSelected': '{count} 枚の写真を追加', + 'memories.alreadyAdded': '追加済み', + 'memories.private': '非公開', + 'memories.stopSharing': '共有を停止', + 'memories.oldest': '古い順', + 'memories.newest': '新しい順', + 'memories.allLocations': 'すべての場所', + 'memories.tripDates': '旅行期間', + 'memories.allPhotos': 'すべての写真', + 'memories.confirmShareTitle': '旅行メンバーと共有しますか?', + 'memories.confirmShareHint': + '{count} 枚の写真がこの旅行の全メンバーに表示されます。後から個別に非公開にできます。', + 'memories.confirmShareButton': '写真を共有', + 'memories.error.loadAlbums': 'アルバムの読み込みに失敗しました', + 'memories.error.linkAlbum': 'アルバムのリンクに失敗しました', + 'memories.error.unlinkAlbum': 'アルバムのリンク解除に失敗しました', + 'memories.error.syncAlbum': 'アルバムの同期に失敗しました', + 'memories.error.loadPhotos': '写真の読み込みに失敗しました', + 'memories.error.addPhotos': '写真の追加に失敗しました', + 'memories.error.removePhoto': '写真の削除に失敗しました', + 'memories.error.toggleSharing': '共有設定の更新に失敗しました', + 'memories.saveRouteNotConfigured': + 'このプロバイダーでは保存先が設定されていません', + 'memories.testRouteNotConfigured': + 'このプロバイダーではテスト用の保存先が設定されていません', + 'memories.fillRequiredFields': '必須項目をすべて入力してください', +}; +export default memories; diff --git a/shared/src/i18n/ja/nav.ts b/shared/src/i18n/ja/nav.ts new file mode 100644 index 00000000..67ee82b9 --- /dev/null +++ b/shared/src/i18n/ja/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': '旅行', + 'nav.share': '共有', + 'nav.settings': '設定', + 'nav.admin': '管理', + 'nav.logout': 'ログアウト', + 'nav.lightMode': 'ライトモード', + 'nav.darkMode': 'ダークモード', + 'nav.autoMode': '自動', + 'nav.administrator': '管理者', + 'nav.myTrips': 'マイ旅行', + 'nav.profile': 'プロフィール', + 'nav.bottomSettings': '設定', + 'nav.bottomAdmin': '管理者設定', + 'nav.bottomLogout': 'ログアウト', + 'nav.bottomAdminBadge': '管理者', +}; +export default nav; diff --git a/shared/src/i18n/ja/notif.ts b/shared/src/i18n/ja/notif.ts new file mode 100644 index 00000000..44dc6a93 --- /dev/null +++ b/shared/src/i18n/ja/notif.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[テスト] 通知', + 'notif.test.simple.text': 'これはシンプルなテスト通知です。', + 'notif.test.boolean.text': 'このテスト通知を承認しますか?', + 'notif.test.navigate.text': + '下をクリックしてダッシュボードに移動してください。', + 'notif.trip_invite.title': '旅行への招待', + 'notif.trip_invite.text': '{actor}が「{trip}」に招待しました', + 'notif.booking_change.title': '予約が更新されました', + 'notif.booking_change.text': '{actor}が「{trip}」の予約を更新しました', + 'notif.trip_reminder.title': '旅行リマインド', + 'notif.trip_reminder.text': '旅行「{trip}」がまもなく始まります!', + 'notif.todo_due.title': 'ToDoの期限', + 'notif.todo_due.text': '「{trip}」の{todo}は{due}が期限です', + 'notif.vacay_invite.title': 'Vacay Fusionへの招待', + 'notif.vacay_invite.text': '{actor}から旅行プランの統合に招待されました', + 'notif.photos_shared.title': '写真が共有されました', + 'notif.photos_shared.text': + '{actor}が「{trip}」で{count}枚の写真を共有しました', + 'notif.collab_message.title': '新しいメッセージ', + 'notif.collab_message.text': '{actor}が「{trip}」でメッセージを送りました', + 'notif.packing_tagged.title': '持ち物の割り当て', + 'notif.packing_tagged.text': + '{actor}が「{trip}」の{category}をあなたに割り当てました', + 'notif.version_available.title': '新しいバージョンがあります', + 'notif.version_available.text': 'TREK {version}が利用可能です', + 'notif.action.view_trip': '旅行を見る', + 'notif.action.view_collab': 'メッセージを見る', + 'notif.action.view_packing': '持ち物を見る', + 'notif.action.view_photos': '写真を見る', + 'notif.action.view_vacay': 'Vacayを見る', + 'notif.action.view_admin': '管理画面へ', + 'notif.action.view': '表示', + 'notif.action.accept': '承認', + 'notif.action.decline': '拒否', + 'notif.generic.title': '通知', + 'notif.generic.text': '新しい通知があります', + 'notif.dev.unknown_event.title': '[DEV] 不明なイベント', + 'notif.dev.unknown_event.text': + 'イベントタイプ「{event}」はEVENT_NOTIFICATION_CONFIGに登録されていません', +}; +export default notif; diff --git a/shared/src/i18n/ja/notifications.ts b/shared/src/i18n/ja/notifications.ts new file mode 100644 index 00000000..5157c3c0 --- /dev/null +++ b/shared/src/i18n/ja/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': '通知', + 'notifications.markAllRead': 'すべて既読', + 'notifications.deleteAll': 'すべて削除', + 'notifications.showAll': 'すべて表示', + 'notifications.empty': '通知はありません', + 'notifications.emptyDescription': 'すべて確認済みです!', + 'notifications.all': 'すべて', + 'notifications.unreadOnly': '未読', + 'notifications.markRead': '既読にする', + 'notifications.markUnread': '未読にする', + 'notifications.delete': '削除', + 'notifications.system': 'システム', + 'notifications.synologySessionCleared.title': + 'Synology Photosが切断されました', + 'notifications.synologySessionCleared.text': + 'サーバーまたはアカウントが変更されました。設定で接続を再テストしてください。', + 'notifications.versionAvailable.title': '更新があります', + 'notifications.versionAvailable.text': 'TREK {version} が利用可能です。', + 'notifications.versionAvailable.button': '詳細を見る', + 'notifications.test.title': '{actor} からのテスト通知', + 'notifications.test.text': 'これはテスト通知です。', + 'notifications.test.booleanTitle': '{actor} が承認を求めています', + 'notifications.test.booleanText': 'テスト用の承認通知です。', + 'notifications.test.accept': '承認', + 'notifications.test.decline': '却下', + 'notifications.test.navigateTitle': '確認してください', + 'notifications.test.navigateText': 'テスト用の遷移通知です。', + 'notifications.test.goThere': '移動', + 'notifications.test.adminTitle': '管理者通知', + 'notifications.test.adminText': '{actor} が管理者全員に通知を送りました。', + 'notifications.test.tripTitle': '{actor} が旅行に投稿しました', + 'notifications.test.tripText': '旅行「{trip}」のテスト通知です。', +}; +export default notifications; diff --git a/shared/src/i18n/ja/oauth.ts b/shared/src/i18n/ja/oauth.ts new file mode 100644 index 00000000..ce4bf8e7 --- /dev/null +++ b/shared/src/i18n/ja/oauth.ts @@ -0,0 +1,83 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': '旅行', + 'oauth.scope.group.places': '場所', + 'oauth.scope.group.atlas': '地図', + 'oauth.scope.group.packing': '持ち物', + 'oauth.scope.group.todos': 'ToDo', + 'oauth.scope.group.budget': '予算', + 'oauth.scope.group.reservations': '予約', + 'oauth.scope.group.collab': 'コラボ', + 'oauth.scope.group.notifications': '通知', + 'oauth.scope.group.vacay': '休暇', + 'oauth.scope.group.geo': '地図', + 'oauth.scope.group.weather': '天気', + 'oauth.scope.group.journey': '日記', + 'oauth.scope.trips:read.label': '旅行・旅程を表示', + 'oauth.scope.trips:read.description': '旅行、日程、メモ、メンバーを閲覧', + 'oauth.scope.trips:write.label': '旅行・旅程を編集', + 'oauth.scope.trips:write.description': + '旅行や日程、メモの作成・更新、メンバー管理', + 'oauth.scope.trips:delete.label': '旅行を削除', + 'oauth.scope.trips:delete.description': + '旅行全体を完全に削除(元に戻せません)', + 'oauth.scope.trips:share.label': '共有リンクを管理', + 'oauth.scope.trips:share.description': + '旅行の公開共有リンクを作成・更新・無効化', + 'oauth.scope.places:read.label': '場所・地図データを表示', + 'oauth.scope.places:read.description': + '場所、日への割り当て、タグ、カテゴリを閲覧', + 'oauth.scope.places:write.label': '場所を管理', + 'oauth.scope.places:write.description': + '場所、割り当て、タグの作成・更新・削除', + 'oauth.scope.atlas:read.label': '地図を表示', + 'oauth.scope.atlas:read.description': + '訪問した国・地域、バケットリストを閲覧', + 'oauth.scope.atlas:write.label': '地図を管理', + 'oauth.scope.atlas:write.description': + '訪問済みの国・地域を管理、バケットリスト編集', + 'oauth.scope.packing:read.label': '持ち物リストを表示', + 'oauth.scope.packing:read.description': '持ち物、バッグ、担当者を閲覧', + 'oauth.scope.packing:write.label': '持ち物リストを管理', + 'oauth.scope.packing:write.description': + '持ち物やバッグの追加・編集・削除・並び替え', + 'oauth.scope.todos:read.label': 'ToDoリストを表示', + 'oauth.scope.todos:read.description': '旅行のToDoと担当者を閲覧', + 'oauth.scope.todos:write.label': 'ToDoリストを管理', + 'oauth.scope.todos:write.description': + 'ToDoの作成・編集・完了・削除・並び替え', + 'oauth.scope.budget:read.label': '予算を表示', + 'oauth.scope.budget:read.description': '予算項目や内訳を閲覧', + 'oauth.scope.budget:write.label': '予算を管理', + 'oauth.scope.budget:write.description': '予算項目の作成・編集・削除', + 'oauth.scope.reservations:read.label': '予約を表示', + 'oauth.scope.reservations:read.description': '予約や宿泊情報を閲覧', + 'oauth.scope.reservations:write.label': '予約を管理', + 'oauth.scope.reservations:write.description': + '予約の作成・編集・削除・並び替え', + 'oauth.scope.collab:read.label': 'コラボを表示', + 'oauth.scope.collab:read.description': '共同メモ、投票、メッセージを閲覧', + 'oauth.scope.collab:write.label': 'コラボを管理', + 'oauth.scope.collab:write.description': '共同メモ、投票、メッセージを管理', + 'oauth.scope.notifications:read.label': '通知を表示', + 'oauth.scope.notifications:read.description': 'アプリ内通知と未読数を閲覧', + 'oauth.scope.notifications:write.label': '通知を管理', + 'oauth.scope.notifications:write.description': '通知を既読にする・対応する', + 'oauth.scope.vacay:read.label': '休暇プランを表示', + 'oauth.scope.vacay:read.description': '休暇プランのデータや統計を閲覧', + 'oauth.scope.vacay:write.label': '休暇プランを管理', + 'oauth.scope.vacay:write.description': '休暇エントリーや予定を管理', + 'oauth.scope.geo:read.label': '地図・ジオコーディング', + 'oauth.scope.geo:read.description': + '場所検索、地図URL解析、逆ジオコーディング', + 'oauth.scope.weather:read.label': '天気予報', + 'oauth.scope.weather:read.description': '旅行先・日程の天気予報を取得', + 'oauth.scope.journey:read.label': '日記を表示', + 'oauth.scope.journey:read.description': '日記、エントリー、参加者を閲覧', + 'oauth.scope.journey:write.label': '日記を管理', + 'oauth.scope.journey:write.description': '日記やエントリーの作成・編集・削除', + 'oauth.scope.journey:share.label': '日記共有を管理', + 'oauth.scope.journey:share.description': '公開共有リンクの作成・更新・無効化', +}; +export default oauth; diff --git a/shared/src/i18n/ja/packing.ts b/shared/src/i18n/ja/packing.ts new file mode 100644 index 00000000..2ef3fd08 --- /dev/null +++ b/shared/src/i18n/ja/packing.ts @@ -0,0 +1,185 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': '持ち物リスト', + 'packing.empty': '持ち物リストは空です', + 'packing.import': 'インポート', + 'packing.importTitle': '持ち物リストをインポート', + 'packing.importHint': + '1行につき1項目。形式:カテゴリ, 名前, 重量(g・任意), バッグ(任意), checked/unchecked(任意)', + 'packing.importPlaceholder': + '衛生用品, 歯ブラシ\n衣類, Tシャツ, 200\n書類, パスポート, , 機内持ち込み\n電子機器, 充電器, 50, スーツケース, checked', + 'packing.importCsv': 'CSV/TXTを読み込む', + 'packing.importAction': '{count}件をインポート', + 'packing.importSuccess': '{count}件インポートしました', + 'packing.importError': 'インポートに失敗しました', + 'packing.importEmpty': 'インポートする項目がありません', + 'packing.progress': '{packed}/{total} 梱包済み({percent}%)', + 'packing.clearChecked': 'チェック済み{count}件を削除', + 'packing.clearCheckedShort': '{count}件を削除', + 'packing.suggestions': 'おすすめ', + 'packing.suggestionsTitle': 'おすすめを追加', + 'packing.allSuggested': 'おすすめはすべて追加済み', + 'packing.allPacked': 'すべて梱包済み!', + 'packing.addPlaceholder': '新しい項目を追加...', + 'packing.categoryPlaceholder': 'カテゴリ...', + 'packing.filterAll': 'すべて', + 'packing.filterOpen': '未完了', + 'packing.filterDone': '完了', + 'packing.emptyTitle': '持ち物リストは空です', + 'packing.emptyHint': '項目を追加するか、おすすめを使いましょう', + 'packing.emptyFiltered': 'このフィルターに一致する項目はありません', + 'packing.menuRename': '名前を変更', + 'packing.menuCheckAll': 'すべてチェック', + 'packing.menuUncheckAll': 'すべて解除', + 'packing.menuDeleteCat': 'カテゴリを削除', + 'packing.noMembers': '旅行メンバーがいません', + 'packing.addItem': '項目を追加', + 'packing.addItemPlaceholder': '項目名...', + 'packing.addCategory': 'カテゴリを追加', + 'packing.newCategoryPlaceholder': 'カテゴリ名(例:衣類)', + 'packing.applyTemplate': 'テンプレートを適用', + 'packing.template': 'テンプレート', + 'packing.templateApplied': 'テンプレートから{count}件追加しました', + 'packing.templateError': 'テンプレートの適用に失敗しました', + 'packing.saveAsTemplate': 'テンプレートとして保存', + 'packing.templateName': 'テンプレート名', + 'packing.templateSaved': '持ち物リストをテンプレートとして保存しました', + 'packing.bags': 'バッグ', + 'packing.noBag': '未割り当て', + 'packing.totalWeight': '総重量', + 'packing.bagName': 'バッグ名...', + 'packing.addBag': 'バッグを追加', + 'packing.changeCategory': 'カテゴリを変更', + 'packing.confirm.clearChecked': 'チェック済み{count}件を削除しますか?', + 'packing.confirm.deleteCat': + '{count}件の項目があるカテゴリ「{name}」を削除しますか?', + 'packing.defaultCategory': 'その他', + 'packing.toast.saveError': '保存に失敗しました', + 'packing.toast.deleteError': '削除に失敗しました', + 'packing.toast.renameError': '名前の変更に失敗しました', + 'packing.toast.addError': '追加に失敗しました', + 'packing.suggestions.items': [ + { + name: 'パスポート', + category: '書類', + }, + { + name: '身分証明書', + category: '書類', + }, + { + name: '海外旅行保険', + category: '書類', + }, + { + name: '航空券', + category: '書類', + }, + { + name: 'クレジットカード', + category: '金融', + }, + { + name: '現金', + category: '金融', + }, + { + name: 'ビザ', + category: '書類', + }, + { + name: 'Tシャツ', + category: '衣類', + }, + { + name: 'ズボン', + category: '衣類', + }, + { + name: '下着', + category: '衣類', + }, + { + name: '靴下', + category: '衣類', + }, + { + name: '上着', + category: '衣類', + }, + { + name: '寝間着', + category: '衣類', + }, + { + name: '水着', + category: '衣類', + }, + { + name: 'レインジャケット', + category: '衣類', + }, + { + name: '歩きやすい靴', + category: '衣類', + }, + { + name: '歯ブラシ', + category: '洗面用具', + }, + { + name: '歯磨き粉', + category: '洗面用具', + }, + { + name: 'シャンプー', + category: '洗面用具', + }, + { + name: 'デオドラント', + category: '洗面用具', + }, + { + name: '日焼け止め', + category: '洗面用具', + }, + { + name: 'カミソリ', + category: '洗面用具', + }, + { + name: '充電器', + category: '電子機器', + }, + { + name: 'モバイルバッテリー', + category: '電子機器', + }, + { + name: 'ヘッドホン', + category: '電子機器', + }, + { + name: '変換プラグ', + category: '電子機器', + }, + { + name: 'カメラ', + category: '電子機器', + }, + { + name: '鎮痛薬', + category: '健康', + }, + { + name: '絆創膏', + category: '健康', + }, + { + name: '消毒液', + category: '健康', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/ja/pdf.ts b/shared/src/i18n/ja/pdf.ts new file mode 100644 index 00000000..5dd39fda --- /dev/null +++ b/shared/src/i18n/ja/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': '旅行計画', + 'pdf.planned': '予定', + 'pdf.costLabel': '費用(EUR)', + 'pdf.preview': 'PDFプレビュー', + 'pdf.saveAsPdf': 'PDFとして保存', +}; +export default pdf; diff --git a/shared/src/i18n/ja/perm.ts b/shared/src/i18n/ja/perm.ts new file mode 100644 index 00000000..eb652c78 --- /dev/null +++ b/shared/src/i18n/ja/perm.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': '権限設定', + 'perm.subtitle': 'アプリ全体の操作権限を管理', + 'perm.saved': '権限設定を保存しました', + 'perm.resetDefaults': '既定に戻す', + 'perm.customized': 'カスタマイズ済み', + 'perm.level.admin': '管理者のみ', + 'perm.level.tripOwner': '旅行オーナー', + 'perm.level.tripMember': '旅行メンバー', + 'perm.level.everybody': '全員', + 'perm.cat.trip': '旅行管理', + 'perm.cat.members': 'メンバー管理', + 'perm.cat.files': 'ファイル', + 'perm.cat.content': 'コンテンツと予定', + 'perm.cat.extras': '予算・持ち物・コラボ', + 'perm.action.trip_create': '旅行を作成', + 'perm.action.trip_edit': '旅行詳細を編集', + 'perm.action.trip_delete': '旅行を削除', + 'perm.action.trip_archive': '旅行をアーカイブ/復元', + 'perm.action.trip_cover_upload': 'カバー画像をアップロード', + 'perm.action.member_manage': 'メンバーを追加/削除', + 'perm.action.file_upload': 'ファイルをアップロード', + 'perm.action.file_edit': 'ファイル情報を編集', + 'perm.action.file_delete': 'ファイルを削除', + 'perm.action.place_edit': '場所を追加/編集/削除', + 'perm.action.day_edit': '日・メモ・割り当てを編集', + 'perm.action.reservation_edit': '予約を管理', + 'perm.action.budget_edit': '予算を管理', + 'perm.action.packing_edit': '持ち物を管理', + 'perm.action.collab_edit': 'コラボ(メモ・投票・チャット)', + 'perm.action.share_manage': '共有リンクを管理', + 'perm.actionHint.trip_create': '新しい旅行を作成できる人', + 'perm.actionHint.trip_edit': '旅行名や日付などを変更できる人', + 'perm.actionHint.trip_delete': '旅行を完全に削除できる人', + 'perm.actionHint.trip_archive': '旅行をアーカイブできる人', + 'perm.actionHint.trip_cover_upload': 'カバー画像を変更できる人', + 'perm.actionHint.member_manage': 'メンバーを招待/削除できる人', + 'perm.actionHint.file_upload': 'ファイルをアップロードできる人', + 'perm.actionHint.file_edit': 'ファイル説明やリンクを編集できる人', + 'perm.actionHint.file_delete': 'ファイルをゴミ箱へ移動/完全削除できる人', + 'perm.actionHint.place_edit': '場所を追加・編集・削除できる人', + 'perm.actionHint.day_edit': '日やメモ、割り当てを編集できる人', + 'perm.actionHint.reservation_edit': '予約を作成・編集・削除できる人', + 'perm.actionHint.budget_edit': '予算項目を管理できる人', + 'perm.actionHint.packing_edit': '持ち物やバッグを管理できる人', + 'perm.actionHint.collab_edit': 'メモや投票、メッセージを作成できる人', + 'perm.actionHint.share_manage': '公開共有リンクを管理できる人', +}; +export default perm; diff --git a/shared/src/i18n/ja/photos.ts b/shared/src/i18n/ja/photos.ts new file mode 100644 index 00000000..a0524650 --- /dev/null +++ b/shared/src/i18n/ja/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': '写真', + 'photos.subtitle': '{trip} の写真 {count} 枚', + 'photos.dropHere': 'ここに写真をドロップ…', + 'photos.dropHereActive': 'ここに写真をドロップ', + 'photos.captionForAll': 'キャプション(全体)', + 'photos.captionPlaceholder': '任意のキャプション…', + 'photos.addCaption': 'キャプションを追加…', + 'photos.allDays': 'すべての日', + 'photos.noPhotos': 'まだ写真はありません', + 'photos.uploadHint': '旅行の写真をアップロード', + 'photos.clickToSelect': 'またはクリックして選択', + 'photos.linkPlace': '場所を紐づけ', + 'photos.noPlace': '場所なし', + 'photos.uploadN': '{n} 枚の写真をアップロード', + 'photos.linkDay': '日を紐づけ', + 'photos.noDay': '日付なし', + 'photos.dayLabel': '{number}日目', + 'photos.photoSelected': '写真を選択しました', + 'photos.photosSelected': '写真を選択しました', + 'photos.fileTypeHint': 'JPG、PNG、WebP · 最大 10 MB · 最大 30 枚', +}; +export default photos; diff --git a/shared/src/i18n/ja/places.ts b/shared/src/i18n/ja/places.ts new file mode 100644 index 00000000..da196afb --- /dev/null +++ b/shared/src/i18n/ja/places.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': '場所/アクティビティを追加', + 'places.importFile': 'ファイルをインポート', + 'places.sidebarDrop': 'ドロップしてインポート', + 'places.importFileHint': + 'Google My Maps、Google Earth、GPSトラッカーなどの .gpx、.kml、.kmz ファイルをインポートできます。', + 'places.importFileDropHere': + 'クリックしてファイルを選択、またはここにドラッグ&ドロップ', + 'places.importFileDropActive': 'ドロップして選択', + 'places.importFileUnsupported': + '対応していないファイル形式です。.gpx、.kml、.kmz を使用してください。', + 'places.importFileTooLarge': + 'ファイルが大きすぎます。最大 {maxMb} MB までです。', + 'places.importFileError': 'インポートに失敗しました', + 'places.importAllSkipped': 'すべての場所は既に旅行に含まれています。', + 'places.gpxImported': 'GPXから {count} 件の場所をインポートしました', + 'places.gpxImportTypes': '何をインポートしますか?', + 'places.gpxImportWaypoints': 'ウェイポイント', + 'places.gpxImportRoutes': 'ルート', + 'places.gpxImportTracks': 'トラック(経路付き)', + 'places.gpxImportNoneSelected': '少なくとも1つ選択してください。', + 'places.kmlImportTypes': '何をインポートしますか?', + 'places.kmlImportPoints': 'ポイント(プレースマーク)', + 'places.kmlImportPaths': 'パス(ライン)', + 'places.kmlImportNoneSelected': '少なくとも1つ選択してください。', + 'places.selectionCount': '{count} 件選択中', + 'places.deleteSelected': '選択を削除', + 'places.kmlKmzImported': 'KMZ/KMLから {count} 件の場所をインポートしました', + 'places.urlResolved': 'URLから場所をインポートしました', + 'places.importList': 'リストをインポート', + 'places.kmlKmzSummaryValues': + 'プレースマーク: {total} • 追加: {created} • スキップ: {skipped}', + 'places.importGoogleList': 'Google リスト', + 'places.importNaverList': 'Naver リスト', + 'places.googleListHint': + '共有されたGoogleマップのリストリンクを貼り付けてください。', + 'places.googleListImported': + '「{list}」から {count} 件の場所をインポートしました', + 'places.googleListError': 'Googleマップのリストをインポートできませんでした', + 'places.naverListHint': + '共有されたNaverマップのリストリンクを貼り付けてください。', + 'places.naverListImported': + '「{list}」から {count} 件の場所をインポートしました', + 'places.naverListError': 'Naverマップのリストをインポートできませんでした', + 'places.viewDetails': '詳細を見る', + 'places.assignToDay': 'どの日に追加しますか?', + 'places.all': 'すべて', + 'places.unplanned': '未計画', + 'places.filterTracks': 'トラック', + 'places.search': '場所を検索…', + 'places.allCategories': 'すべてのカテゴリ', + 'places.categoriesSelected': 'カテゴリ', + 'places.clearFilter': 'フィルター解除', + 'places.count': '{count} 件の場所', + 'places.countSingular': '1 件の場所', + 'places.allPlanned': 'すべての場所が計画済みです', + 'places.noneFound': '場所が見つかりません', + 'places.editPlace': '場所を編集', + 'places.formName': '名前', + 'places.formNamePlaceholder': '例:エッフェル塔', + 'places.formDescription': '説明', + 'places.formDescriptionPlaceholder': '短い説明…', + 'places.formAddress': '住所', + 'places.formAddressPlaceholder': '通り、都市、国', + 'places.formLat': '緯度(例:48.8566)', + 'places.formLng': '経度(例:2.3522)', + 'places.formCategory': 'カテゴリ', + 'places.noCategory': 'カテゴリなし', + 'places.categoryNamePlaceholder': 'カテゴリ名', + 'places.formTime': '時間', + 'places.startTime': '開始', + 'places.endTime': '終了', + 'places.endTimeBeforeStart': '終了時間が開始時間より前です', + 'places.timeCollision': '時間が重複しています:', + 'places.formWebsite': 'ウェブサイト', + 'places.formNotes': 'メモ', + 'places.formNotesPlaceholder': '個人的なメモ…', + 'places.formReservation': '予約', + 'places.reservationNotesPlaceholder': '予約メモ、確認番号など…', + 'places.mapsSearchPlaceholder': '場所を検索…', + 'places.mapsSearchError': '場所の検索に失敗しました。', + 'places.loadingDetails': '詳細を読み込み中…', + 'places.osmHint': + 'OpenStreetMapで検索しています(写真・営業時間・評価なし)。設定でGoogle APIキーを追加すると詳細が表示されます。', + 'places.osmActive': + 'OpenStreetMapで検索中(写真・評価・営業時間なし)。設定でGoogle APIキーを追加してください。', + 'places.categoryCreateError': 'カテゴリの作成に失敗しました', + 'places.nameRequired': '名前を入力してください', + 'places.saveError': '保存に失敗しました', +}; +export default places; diff --git a/shared/src/i18n/ja/planner.ts b/shared/src/i18n/ja/planner.ts new file mode 100644 index 00000000..86457897 --- /dev/null +++ b/shared/src/i18n/ja/planner.ts @@ -0,0 +1,67 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': '場所', + 'planner.bookings': '予約', + 'planner.packingList': '持ち物リスト', + 'planner.documents': 'ドキュメント', + 'planner.dayPlan': '日別計画', + 'planner.reservations': '予約', + 'planner.minTwoPlaces': '座標付きの場所が少なくとも2つ必要です', + 'planner.noGeoPlaces': '座標付きの場所がありません', + 'planner.routeCalculated': 'ルートを計算しました', + 'planner.routeCalcFailed': 'ルートを計算できませんでした', + 'planner.routeError': 'ルート計算中にエラーが発生しました', + 'planner.icsExportFailed': 'ICSの書き出しに失敗しました', + 'planner.routeOptimized': 'ルートを最適化しました', + 'planner.reservationUpdated': '予約を更新しました', + 'planner.reservationAdded': '予約を追加しました', + 'planner.confirmDeleteReservation': '予約を削除しますか?', + 'planner.reservationDeleted': '予約を削除しました', + 'planner.days': '日', + 'planner.allPlaces': 'すべての場所', + 'planner.totalPlaces': '合計{n}件の場所', + 'planner.noDaysPlanned': '計画された日がありません', + 'planner.editTrip': '旅行を編集 →', + 'planner.placeOne': '1件の場所', + 'planner.placeN': '{n}件の場所', + 'planner.addNote': 'メモを追加', + 'planner.noEntries': 'この日の予定はありません', + 'planner.addPlace': '場所/アクティビティを追加', + 'planner.addPlaceShort': '+ 場所/アクティビティ', + 'planner.resPending': '予約保留 · ', + 'planner.resConfirmed': '予約確定 · ', + 'planner.notePlaceholder': 'メモ…', + 'planner.noteTimePlaceholder': '時刻(任意)', + 'planner.noteExamplePlaceholder': + '例:中央駅から14:30発のS3、7番桟橋からフェリー、昼食休憩…', + 'planner.totalCost': '合計費用', + 'planner.searchPlaces': '場所を検索…', + 'planner.allCategories': 'すべてのカテゴリ', + 'planner.noPlacesFound': '場所が見つかりません', + 'planner.addFirstPlace': '最初の場所を追加', + 'planner.noReservations': '予約はありません', + 'planner.addFirstReservation': '最初の予約を追加', + 'planner.new': '新規', + 'planner.addToDay': '+ 日', + 'planner.calculating': '計算中…', + 'planner.route': 'ルート', + 'planner.optimize': '最適化', + 'planner.openGoogleMaps': 'Googleマップで開く', + 'planner.selectDayHint': '左の一覧から日を選択すると、日別計画が表示されます', + 'planner.noPlacesForDay': 'この日の場所はまだありません', + 'planner.addPlacesLink': '場所を追加 →', + 'planner.minTotal': '最短合計', + 'planner.noReservation': '予約なし', + 'planner.removeFromDay': 'この日から削除', + 'planner.addToThisDay': 'この日に追加', + 'planner.overview': '概要', + 'planner.noDays': '日がありません', + 'planner.editTripToAddDays': '旅行を編集して日を追加', + 'planner.dayCount': '{n}日間', + 'planner.clickToUnlock': 'クリックして解除', + 'planner.keepPosition': '最適化中も位置を保持', + 'planner.dayDetails': '日詳細', + 'planner.dayN': '{n}日目', +}; +export default planner; diff --git a/shared/src/i18n/ja/register.ts b/shared/src/i18n/ja/register.ts new file mode 100644 index 00000000..a06743fc --- /dev/null +++ b/shared/src/i18n/ja/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'パスワードが一致しません', + 'register.passwordTooShort': 'パスワードは8文字以上必要です', + 'register.failed': '登録に失敗しました', + 'register.getStarted': '始める', + 'register.subtitle': 'アカウントを作成して、理想の旅行計画を始めましょう。', + 'register.feature1': '無制限の旅行プラン', + 'register.feature2': 'インタラクティブなマップ表示', + 'register.feature3': '場所とカテゴリを管理', + 'register.feature4': '予約を管理', + 'register.feature5': '持ち物リストを作成', + 'register.feature6': '写真・ファイルを保存', + 'register.createAccount': 'アカウントを作成', + 'register.startPlanning': '旅行計画を始める', + 'register.minChars': '最小6文字', + 'register.confirmPassword': 'パスワード確認', + 'register.repeatPassword': 'パスワードを再入力', + 'register.registering': '登録中...', + 'register.register': '登録', + 'register.hasAccount': 'すでにアカウントをお持ちですか?', + 'register.signIn': 'サインイン', +}; +export default register; diff --git a/shared/src/i18n/ja/reservations.ts b/shared/src/i18n/ja/reservations.ts new file mode 100644 index 00000000..67e9cfc7 --- /dev/null +++ b/shared/src/i18n/ja/reservations.ts @@ -0,0 +1,116 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': '予約', + 'reservations.empty': '予約はまだありません', + 'reservations.emptyHint': '航空券、ホテルなどの予約を追加しましょう', + 'reservations.add': '予約を追加', + 'reservations.addManual': '手動予約', + 'reservations.placeHint': + 'ヒント:予約は場所から直接作成すると、日別計画に紐づけやすくなります。', + 'reservations.confirmed': '確定', + 'reservations.pending': '保留', + 'reservations.summary': '確定 {confirmed}件、保留 {pending}件', + 'reservations.fromPlan': '計画から', + 'reservations.showFiles': 'ファイルを表示', + 'reservations.editTitle': '予約を編集', + 'reservations.status': 'ステータス', + 'reservations.datetime': '日時', + 'reservations.startTime': '開始時刻', + 'reservations.endTime': '終了時刻', + 'reservations.date': '日付', + 'reservations.time': '時間', + 'reservations.timeAlt': '時間(代替、例:19:30)', + 'reservations.notes': 'メモ', + 'reservations.notesPlaceholder': '追加のメモ...', + 'reservations.meta.airline': '航空会社', + 'reservations.meta.flightNumber': '便名', + 'reservations.meta.from': '出発地', + 'reservations.meta.to': '到着地', + 'reservations.needsReview': '要確認', + 'reservations.needsReviewHint': + '空港を自動で特定できませんでした。場所を確認してください。', + 'reservations.searchLocation': '駅・港・住所を検索…', + 'reservations.meta.trainNumber': '列車番号', + 'reservations.meta.platform': 'ホーム', + 'reservations.meta.seat': '座席', + 'reservations.meta.checkIn': 'チェックイン', + 'reservations.meta.checkOut': 'チェックアウト', + 'reservations.meta.linkAccommodation': '宿泊先', + 'reservations.meta.checkInUntil': 'チェックイン期限', + 'reservations.meta.pickAccommodation': '宿泊先にリンク', + 'reservations.meta.noAccommodation': 'なし', + 'reservations.meta.hotelPlace': '宿泊先', + 'reservations.meta.pickHotel': '宿泊先を選択', + 'reservations.meta.fromDay': '開始', + 'reservations.meta.toDay': '終了', + 'reservations.meta.selectDay': '日を選択', + 'reservations.type.flight': '航空便', + 'reservations.type.hotel': '宿泊', + 'reservations.type.restaurant': 'レストラン', + 'reservations.type.train': '列車', + 'reservations.type.car': 'レンタカー', + 'reservations.type.cruise': 'クルーズ', + 'reservations.type.event': 'イベント', + 'reservations.type.tour': 'ツアー', + 'reservations.type.other': 'その他', + 'reservations.confirm.delete': '予約「{name}」を削除しますか?', + 'reservations.confirm.deleteTitle': '予約を削除しますか?', + 'reservations.confirm.deleteBody': '「{name}」は完全に削除されます。', + 'reservations.toast.updated': '予約を更新しました', + 'reservations.toast.removed': '予約を削除しました', + 'reservations.toast.fileUploaded': 'ファイルをアップロードしました', + 'reservations.toast.uploadError': 'アップロードに失敗しました', + 'reservations.newTitle': '新しい予約', + 'reservations.bookingType': '予約タイプ', + 'reservations.titleLabel': 'タイトル', + 'reservations.titlePlaceholder': '例:Lufthansa LH123、Hotel Adlon', + 'reservations.locationAddress': '場所/住所', + 'reservations.locationPlaceholder': '住所、空港、ホテル...', + 'reservations.confirmationCode': '予約コード', + 'reservations.confirmationPlaceholder': '例:ABC12345', + 'reservations.day': '日', + 'reservations.noDay': '日なし', + 'reservations.place': '場所', + 'reservations.noPlace': '場所なし', + 'reservations.pendingSave': '保存されます…', + 'reservations.uploading': 'アップロード中...', + 'reservations.attachFile': 'ファイルを添付', + 'reservations.linkExisting': '既存ファイルをリンク', + 'reservations.toast.saveError': '保存に失敗しました', + 'reservations.toast.updateError': '更新に失敗しました', + 'reservations.toast.deleteError': '削除に失敗しました', + 'reservations.confirm.remove': '「{name}」の予約を削除しますか?', + 'reservations.linkAssignment': '日への割り当てにリンク', + 'reservations.pickAssignment': '計画から割り当てを選択...', + 'reservations.noAssignment': 'リンクなし(単独)', + 'reservations.price': '価格', + 'reservations.budgetCategory': '予算カテゴリ', + 'reservations.budgetCategoryPlaceholder': '例:交通、宿泊', + 'reservations.budgetCategoryAuto': '自動(予約タイプから)', + 'reservations.budgetHint': '保存時に予算エントリが自動で作成されます。', + 'reservations.departureDate': '出発', + 'reservations.arrivalDate': '到着', + 'reservations.departureTime': '出発時刻', + 'reservations.arrivalTime': '到着時刻', + 'reservations.pickupDate': '受取', + 'reservations.returnDate': '返却', + 'reservations.pickupTime': '受取時刻', + 'reservations.returnTime': '返却時刻', + 'reservations.endDate': '終了日', + 'reservations.meta.departureTimezone': '出発TZ', + 'reservations.meta.arrivalTimezone': '到着TZ', + 'reservations.span.departure': '出発', + 'reservations.span.arrival': '到着', + 'reservations.span.inTransit': '移動中', + 'reservations.span.pickup': '受取', + 'reservations.span.return': '返却', + 'reservations.span.active': '有効', + 'reservations.span.start': '開始', + 'reservations.span.end': '終了', + 'reservations.span.ongoing': '進行中', + 'reservations.validation.endBeforeStart': + '終了日時は開始日時より後である必要があります', + 'reservations.addBooking': '予約を追加', +}; +export default reservations; diff --git a/shared/src/i18n/ja/settings.ts b/shared/src/i18n/ja/settings.ts new file mode 100644 index 00000000..b9e08230 --- /dev/null +++ b/shared/src/i18n/ja/settings.ts @@ -0,0 +1,278 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': '設定', + 'settings.subtitle': '個人設定を管理', + 'settings.tabs.display': '表示', + 'settings.tabs.map': '地図', + 'settings.tabs.notifications': '通知', + 'settings.tabs.integrations': '連携', + 'settings.tabs.account': 'アカウント', + 'settings.tabs.offline': 'オフライン', + 'settings.tabs.about': '情報', + 'settings.map': '地図', + 'settings.mapTemplate': '地図テンプレート', + 'settings.mapTemplatePlaceholder.select': 'テンプレートを選択…', + 'settings.mapDefaultHint': '空欄の場合は OpenStreetMap(既定)を使用', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': '地図タイルのURLテンプレート', + 'settings.mapProvider': '地図プロバイダー', + 'settings.mapProviderHint': + '旅程プランナーと日記地図に影響します。Atlas は常に Leaflet を使用します。', + 'settings.mapLeafletSubtitle': 'クラシックな2D、任意のラスタータイル', + 'settings.mapMapboxSubtitle': 'ベクタータイル、3D建物・地形', + 'settings.mapExperimental': '実験的', + 'settings.mapMapboxToken': 'Mapbox アクセストークン', + 'settings.mapMapboxTokenHint': 'mapbox.com の公開トークン(pk.*)', + 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', + 'settings.mapStyle': '地図スタイル', + 'settings.mapStylePlaceholder': 'Mapboxスタイルを選択', + 'settings.mapStyleHint': 'プリセットまたは mapbox://styles/USER/ID のURL', + 'settings.map3dBuildings': '3D建物・地形', + 'settings.map3dHint': + 'ピッチ+実際の3D押し出し表示。衛星含む全スタイルで動作。', + 'settings.mapHighQuality': '高品質モード', + 'settings.mapHighQualityHint': + 'アンチエイリアス+地球投影で、より鮮明でリアルに表示。', + 'settings.mapHighQualityWarning': + '低性能デバイスではパフォーマンスに影響する場合があります。', + 'settings.mapTipLabel': 'ヒント:', + 'settings.mapTip': + '右クリック+ドラッグで回転/傾き。中クリックで場所を追加(右クリックは回転用)。', + 'settings.latitude': '緯度', + 'settings.longitude': '経度', + 'settings.saveMap': '地図を保存', + 'settings.apiKeys': 'APIキー', + 'settings.mapsKey': 'Google Maps APIキー', + 'settings.mapsKeyHint': + '場所検索用。Places API(新)が必要。console.cloud.google.com で取得', + 'settings.weatherKey': 'OpenWeatherMap APIキー', + 'settings.weatherKeyHint': '天気情報用。openweathermap.org/api で無料取得', + 'settings.keyPlaceholder': 'キーを入力…', + 'settings.configured': '設定済み', + 'settings.saveKeys': 'キーを保存', + 'settings.display': '表示', + 'settings.colorMode': 'カラーモード', + 'settings.light': 'ライト', + 'settings.dark': 'ダーク', + 'settings.auto': '自動', + 'settings.language': '言語', + 'settings.temperature': '温度単位', + 'settings.timeFormat': '時刻形式', + 'settings.bookingLabels': '予約ルートのラベル', + 'settings.bookingLabelsHint': + '地図に駅・空港名を表示。オフ時はアイコンのみ。', + 'settings.blurBookingCodes': '予約コードをぼかす', + 'settings.notifications': '通知', + 'settings.notifyTripInvite': '旅行の招待', + 'settings.notifyBookingChange': '予約の変更', + 'settings.notifyTripReminder': '旅行リマインダー', + 'settings.notifyTodoDue': 'ToDoの期限', + 'settings.notifyVacayInvite': 'Vacay fusion の招待', + 'settings.notifyPhotosShared': '共有写真(Immich)', + 'settings.notifyCollabMessage': 'チャットメッセージ(Collab)', + 'settings.notifyPackingTagged': '持ち物リスト:割り当て', + 'settings.notifyWebhook': 'Webhook通知', + 'settings.notifyVersionAvailable': '新しいバージョン', + 'settings.notificationPreferences.email': 'メール', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.inapp': 'アプリ内', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + '通知チャネルが未設定です。管理者に設定を依頼してください。', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Discord、Slack、または独自のWebhook URLを入力してください。', + 'settings.webhookUrl.saved': 'Webhook URLを保存しました', + 'settings.webhookUrl.test': 'テスト', + 'settings.webhookUrl.testSuccess': 'テストWebhookを送信しました', + 'settings.webhookUrl.testFailed': 'テストWebhookに失敗しました', + 'settings.ntfyUrl.topicLabel': 'Ntfy トピック', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy サーバーURL(任意)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'ntfyトピックを入力してください。サーバー未入力時は管理者設定の既定値を使用します。', + 'settings.ntfyUrl.tokenLabel': 'アクセストークン(任意)', + 'settings.ntfyUrl.tokenHint': 'パスワード保護トピックに必要です。', + 'settings.ntfyUrl.saved': 'Ntfy設定を保存しました', + 'settings.ntfyUrl.test': 'テスト', + 'settings.ntfyUrl.testSuccess': 'テスト通知を送信しました', + 'settings.ntfyUrl.testFailed': 'テスト通知に失敗しました', + 'settings.ntfyUrl.tokenCleared': 'アクセストークンを削除しました', + 'settings.notificationsDisabled': + '通知が未設定です。管理者に有効化を依頼してください。', + 'settings.notificationsActive': '有効なチャネル', + 'settings.notificationsManagedByAdmin': '通知イベントは管理者が設定します。', + 'settings.on': 'オン', + 'settings.off': 'オフ', + 'settings.mcp.title': 'MCP設定', + 'settings.mcp.endpoint': 'MCPエンドポイント', + 'settings.mcp.clientConfig': 'クライアント設定', + 'settings.mcp.clientConfigHint': + ' を下のAPIトークンに置き換えてください。', + 'settings.mcp.clientConfigHintOAuth': + ' をOAuth 2.1の認証情報に置き換えてください。', + 'settings.mcp.copy': 'コピー', + 'settings.mcp.copied': 'コピーしました!', + 'settings.mcp.apiTokens': 'APIトークン', + 'settings.mcp.createToken': '新しいトークン', + 'settings.mcp.noTokens': 'トークンがありません。作成してください。', + 'settings.mcp.tokenCreatedAt': '作成日', + 'settings.mcp.tokenUsedAt': '最終使用', + 'settings.mcp.deleteTokenTitle': 'トークン削除', + 'settings.mcp.deleteTokenMessage': 'このトークンは即時無効になります。', + 'settings.mcp.modal.createTitle': 'APIトークン作成', + 'settings.mcp.modal.tokenName': 'トークン名', + 'settings.mcp.modal.tokenNamePlaceholder': '例:Claude Desktop', + 'settings.mcp.modal.creating': '作成中…', + 'settings.mcp.modal.create': '作成', + 'settings.mcp.modal.createdTitle': 'トークン作成完了', + 'settings.mcp.modal.createdWarning': + '表示は一度きりです。今すぐ保存してください。', + 'settings.mcp.modal.done': '完了', + 'settings.mcp.toast.created': 'トークンを作成しました', + 'settings.mcp.toast.createError': 'トークン作成に失敗しました', + 'settings.mcp.toast.deleted': 'トークンを削除しました', + 'settings.mcp.toast.deleteError': 'トークン削除に失敗しました', + 'settings.mcp.apiTokensDeprecated': + 'APIトークンは非推奨です。OAuth 2.1 クライアントを使用してください。', + 'settings.oauth.clients': 'OAuth 2.1 クライアント', + 'settings.oauth.clientsHint': '第三者アプリが接続できるよう登録します。', + 'settings.oauth.createClient': '新規クライアント', + 'settings.oauth.noClients': '登録されたクライアントはありません。', + 'settings.oauth.clientId': 'クライアントID', + 'settings.oauth.clientSecret': 'クライアントシークレット', + 'settings.oauth.deleteClient': 'クライアント削除', + 'settings.oauth.deleteClientMessage': + 'このクライアントは完全に削除されます。', + 'settings.oauth.rotateSecret': 'シークレット更新', + 'settings.oauth.rotateSecretMessage': '新しいシークレットを生成します。', + 'settings.oauth.rotateSecretConfirm': '更新', + 'settings.oauth.rotateSecretConfirming': '更新中…', + 'settings.oauth.rotateSecretDoneTitle': '新しいシークレット', + 'settings.oauth.rotateSecretDoneWarning': + '表示は一度きりです。今すぐ保存してください。', + 'settings.oauth.activeSessions': '有効なセッション', + 'settings.oauth.sessionScopes': 'スコープ', + 'settings.oauth.sessionExpires': '有効期限', + 'settings.oauth.revoke': '取り消し', + 'settings.oauth.revokeSession': 'セッション取り消し', + 'settings.oauth.revokeSessionMessage': + 'このセッションのアクセスを即時無効にします。', + 'settings.oauth.modal.createTitle': 'OAuthクライアント登録', + 'settings.oauth.modal.presets': '簡単設定', + 'settings.oauth.modal.clientName': 'アプリ名', + 'settings.oauth.modal.clientNamePlaceholder': '例:Claude Web', + 'settings.oauth.modal.redirectUris': 'リダイレクトURI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback', + 'settings.oauth.modal.redirectUrisHint': '1行につき1つ。HTTPS必須。', + 'settings.oauth.modal.scopes': '許可スコープ', + 'settings.oauth.modal.scopesHint': + 'list_trips と get_trip_summary は常に利用可能です。', + 'settings.oauth.modal.selectAll': 'すべて選択', + 'settings.oauth.modal.deselectAll': 'すべて解除', + 'settings.oauth.modal.creating': '登録中…', + 'settings.oauth.modal.create': '登録', + 'settings.oauth.modal.createdTitle': '登録完了', + 'settings.oauth.modal.createdWarning': + 'シークレットは一度しか表示されません。', + 'settings.oauth.toast.createError': '登録に失敗しました', + 'settings.oauth.toast.deleted': 'クライアントを削除しました', + 'settings.oauth.toast.deleteError': '削除に失敗しました', + 'settings.oauth.toast.revoked': 'セッションを取り消しました', + 'settings.oauth.toast.revokeError': '取り消しに失敗しました', + 'settings.oauth.toast.rotateError': '更新に失敗しました', + 'settings.account': 'アカウント', + 'settings.about': '情報', + 'settings.about.reportBug': '不具合報告', + 'settings.about.reportBugHint': '問題を見つけたらお知らせください', + 'settings.about.featureRequest': '機能リクエスト', + 'settings.about.featureRequestHint': '新機能を提案', + 'settings.about.wikiHint': 'ドキュメント・ガイド', + 'settings.about.supporters.badge': '月額サポーター', + 'settings.about.supporters.title': 'TREKの旅仲間', + 'settings.about.supporters.subtitle': + '皆さんの支援がTREKの未来を支えています。', + 'settings.about.supporters.since': '{date}からサポート', + 'settings.about.supporters.tierEmpty': '最初の一人に', + 'settings.about.supporter.tier.noReturnTicket': '片道切符', + 'settings.about.supporter.tier.lostLuggageVip': 'ロストラゲージVIP', + 'settings.about.supporter.tier.businessClassDreamer': 'ビジネスクラスの夢', + 'settings.about.supporter.tier.budgetTraveller': '節約トラベラー', + 'settings.about.supporter.tier.hostelBunkmate': 'ホステル仲間', + 'settings.about.description': 'TREKはセルフホスト型の旅行プランナーです。', + 'settings.about.madeWith': 'Made with', + 'settings.about.madeBy': 'by Maurice とオープンソースコミュニティ。', + 'settings.username': 'ユーザー名', + 'settings.email': 'メール', + 'settings.role': '役割', + 'settings.roleAdmin': '管理者', + 'settings.oidcLinked': '連携先', + 'settings.changePassword': 'パスワード変更', + 'settings.currentPassword': '現在のパスワード', + 'settings.currentPasswordRequired': '現在のパスワードが必要です', + 'settings.newPassword': '新しいパスワード', + 'settings.confirmPassword': '新しいパスワード(確認)', + 'settings.updatePassword': 'パスワード更新', + 'settings.passwordRequired': '現在と新しいパスワードを入力してください', + 'settings.passwordTooShort': '8文字以上必要です', + 'settings.passwordMismatch': 'パスワードが一致しません', + 'settings.passwordWeak': '大文字・小文字・数字・記号を含めてください', + 'settings.passwordChanged': 'パスワードを変更しました', + 'settings.mustChangePassword': '続行するにはパスワード変更が必要です。', + 'settings.deleteAccount': 'アカウント削除', + 'settings.deleteAccountTitle': 'アカウントを削除しますか?', + 'settings.deleteAccountWarning': 'すべてのデータが完全に削除されます。', + 'settings.deleteAccountConfirm': '完全に削除', + 'settings.deleteBlockedTitle': '削除できません', + 'settings.deleteBlockedMessage': + '唯一の管理者です。別のユーザーを管理者にしてください。', + 'settings.roleUser': 'ユーザー', + 'settings.saveProfile': 'プロフィールを保存', + 'settings.toast.mapSaved': '地図設定を保存しました', + 'settings.toast.keysSaved': 'APIキーを保存しました', + 'settings.toast.displaySaved': '表示設定を保存しました', + 'settings.toast.profileSaved': 'プロフィールを保存しました', + 'settings.uploadAvatar': 'プロフィール画像をアップロード', + 'settings.removeAvatar': 'プロフィール画像を削除', + 'settings.avatarUploaded': 'プロフィール画像を更新しました', + 'settings.avatarRemoved': 'プロフィール画像を削除しました', + 'settings.avatarError': 'アップロードに失敗しました', + 'settings.mfa.title': '二要素認証(2FA)', + 'settings.mfa.description': 'サインイン時に追加の認証を行います。', + 'settings.mfa.requiredByPolicy': '管理者により2FAが必須です。', + 'settings.mfa.backupTitle': 'バックアップコード', + 'settings.mfa.backupDescription': '認証アプリが使えない場合に使用します。', + 'settings.mfa.backupWarning': + '今すぐ保存してください。各コードは1回限りです。', + 'settings.mfa.backupCopy': 'コードをコピー', + 'settings.mfa.backupDownload': 'TXTでダウンロード', + 'settings.mfa.backupPrint': '印刷 / PDF', + 'settings.mfa.backupCopied': 'バックアップコードをコピーしました', + 'settings.mfa.enabled': '2FAは有効です。', + 'settings.mfa.disabled': '2FAは無効です。', + 'settings.mfa.setup': '認証アプリを設定', + 'settings.mfa.scanQr': 'QRコードをスキャンするか、手動で入力してください。', + 'settings.mfa.secretLabel': 'シークレットキー(手動入力)', + 'settings.mfa.codePlaceholder': '6桁コード', + 'settings.mfa.enable': '2FAを有効化', + 'settings.mfa.cancelSetup': 'キャンセル', + 'settings.mfa.disableTitle': '2FAを無効化', + 'settings.mfa.disableHint': 'パスワードと現在のコードを入力してください。', + 'settings.mfa.disable': '2FAを無効化', + 'settings.mfa.toastEnabled': '2FAを有効にしました', + 'settings.mfa.toastDisabled': '2FAを無効にしました', + 'settings.mfa.demoBlocked': 'デモモードでは利用できません', + 'settings.oauth.modal.machineClient': + 'マシンクライアント(ブラウザログインなし)', + 'settings.oauth.modal.machineClientHint': + 'client_credentials グラントを使用します — リダイレクト URI は不要です。トークンは client_id + client_secret を介して直接発行され、選択したスコープ内であなたとして動作します。', + 'settings.oauth.modal.machineClientUsage': + 'トークンを取得するには、grant_type=client_credentials、client_id、client_secret を指定して POST /oauth/token を呼び出します。ブラウザもリフレッシュトークンも不要です。', + 'settings.oauth.badge.machine': 'マシン', +}; +export default settings; diff --git a/shared/src/i18n/ja/share.ts b/shared/src/i18n/ja/share.ts new file mode 100644 index 00000000..abdebbd2 --- /dev/null +++ b/shared/src/i18n/ja/share.ts @@ -0,0 +1,15 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': '公開リンク', + 'share.linkHint': 'ログイン不要で閲覧できるリンクを作成します(閲覧のみ)。', + 'share.createLink': 'リンク作成', + 'share.deleteLink': 'リンク削除', + 'share.createError': 'リンクを作成できませんでした', + 'share.permMap': '地図・プラン', + 'share.permBookings': '予約', + 'share.permPacking': '持ち物', + 'share.permBudget': '予算', + 'share.permCollab': 'チャット', +}; +export default share; diff --git a/shared/src/i18n/ja/shared.ts b/shared/src/i18n/ja/shared.ts new file mode 100644 index 00000000..611efab1 --- /dev/null +++ b/shared/src/i18n/ja/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'リンクが無効', + 'shared.expiredHint': 'この共有リンクは無効です。', + 'shared.readOnly': '読み取り専用', + 'shared.tabPlan': 'プラン', + 'shared.tabBookings': '予約', + 'shared.tabPacking': '持ち物', + 'shared.tabBudget': '予算', + 'shared.tabChat': 'チャット', + 'shared.days': '日', + 'shared.places': '場所', + 'shared.other': 'その他', + 'shared.totalBudget': '合計予算', + 'shared.messages': 'メッセージ', + 'shared.sharedVia': '共有元', + 'shared.confirmed': '確定', + 'shared.pending': '保留', +}; +export default shared; diff --git a/shared/src/i18n/ja/stats.ts b/shared/src/i18n/ja/stats.ts new file mode 100644 index 00000000..e4903f9b --- /dev/null +++ b/shared/src/i18n/ja/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': '国', + 'stats.cities': '都市', + 'stats.trips': '旅行', + 'stats.places': '場所', + 'stats.worldProgress': '世界進捗', + 'stats.visited': '訪問済み', + 'stats.remaining': '未訪問', + 'stats.visitedCountries': '訪問国', +}; +export default stats; diff --git a/shared/src/i18n/ja/system_notice.ts b/shared/src/i18n/ja/system_notice.ts new file mode 100644 index 00000000..2a908ceb --- /dev/null +++ b/shared/src/i18n/ja/system_notice.ts @@ -0,0 +1,53 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.v3_photos.title': '写真の場所が3.0で変更されました', + 'system_notice.v3_photos.body': + '旅行プランナー内の写真は削除されましたが、写真データは安全です。TREKがImmichやSynologyのライブラリを変更することはありません。\n\n写真は現在日記アドオンにあります。日記は任意機能です。未有効の場合は、管理画面 → アドオンで有効にしてください。', + 'system_notice.v3_journey.title': '日記登場 — 旅の日記', + 'system_notice.v3_journey.body': + 'タイムライン、写真ギャラリー、インタラクティブな地図で旅を物語に。', + 'system_notice.v3_journey.cta_label': '日記を開く', + 'system_notice.v3_journey.highlight_timeline': + '日ごとのタイムラインとギャラリー', + 'system_notice.v3_journey.highlight_photos': 'ImmichやSynologyからインポート', + 'system_notice.v3_journey.highlight_share': 'ログイン不要で公開共有', + 'system_notice.v3_journey.highlight_export': 'PDFフォトブックとして書き出し', + 'system_notice.v3_features.title': '3.0のその他の注目点', + 'system_notice.v3_features.body': '今回のリリースで知っておきたいポイント。', + 'system_notice.v3_features.highlight_dashboard': + 'モバイル重視のダッシュボード刷新', + 'system_notice.v3_features.highlight_offline': 'PWAとして完全オフライン対応', + 'system_notice.v3_features.highlight_search': 'リアルタイム場所検索', + 'system_notice.v3_features.highlight_import': 'KMZ/KMLから場所をインポート', + 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1に更新', + 'system_notice.v3_mcp.body': + 'MCP連携が全面的に刷新されました。OAuth 2.1が推奨認証方式です。従来の静的トークン(trek_…)は非推奨となり、将来削除されます。', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1推奨(mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24の詳細な権限スコープ', + 'system_notice.v3_mcp.highlight_deprecated': '静的trek_トークンは非推奨', + 'system_notice.v3_mcp.highlight_tools': 'ツールとプロンプトを拡張', + 'system_notice.v3_thankyou.title': '開発者より一言', + 'system_notice.v3_thankyou.body': + '少しだけお時間をください。\n\nTREKは、自分の旅のために作った小さな個人プロジェクトでした。それが今では4,000人以上に使ってもらえるとは思ってもいませんでした。スターも、Issueも、機能要望も、すべて目を通しています。\n\nTREKはこれからもオープンソース、自分でホストでき、あなたのものです。トラッキングなし、サブスクなし。旅が好きな人が作ったツールです。\n\nhttps://github.com/jubnlにも感謝を。3.0の多くはあなたのおかげです。\n\nバグ報告、翻訳、共有、利用してくれたすべての方へ—本当にありがとうございます。\n\nこれからも一緒に旅を。\n\n— Maurice', + 'system_notice.v3014_whitespace_collision.title': + '対応が必要:ユーザーアカウントの競合', + 'system_notice.v3014_whitespace_collision.body': + '3.0.14 へのアップグレードにより、保存されているアカウントの先頭または末尾の空白が原因で、ユーザー名またはメールアドレスの競合が1件以上検出されました。影響を受けたアカウントは自動的にリネームされています。対象となるアカウントを特定するには、サーバーログで **[migration] WHITESPACE COLLISION** で始まる行を確認してください。', + 'system_notice.welcome_v1.title': 'TREKへようこそ', + 'system_notice.welcome_v1.body': + 'オールインワンの旅行プランナー。旅程作成、共有、整理をオンライン・オフラインで。', + 'system_notice.welcome_v1.cta_label': '旅行を計画', + 'system_notice.welcome_v1.hero_alt': 'TREKのUIが重なった風景写真', + 'system_notice.welcome_v1.highlight_plan': '日ごとの旅程作成', + 'system_notice.welcome_v1.highlight_share': '仲間と共同編集', + 'system_notice.welcome_v1.highlight_offline': 'モバイルでオフライン対応', + 'system_notice.dev_test_modal.title': '[Dev] テスト通知', + 'system_notice.dev_test_modal.body': 'これは開発用テスト通知です。', + 'system_notice.pager.prev': '前へ', + 'system_notice.pager.next': '次へ', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': '通知{n}へ', + 'system_notice.pager.position': '{total}件中{current}件目', +}; +export default system_notice; diff --git a/shared/src/i18n/ja/todo.ts b/shared/src/i18n/ja/todo.ts new file mode 100644 index 00000000..87572053 --- /dev/null +++ b/shared/src/i18n/ja/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': '持ち物リスト', + 'todo.subtab.todo': 'ToDo', + 'todo.completed': '完了', + 'todo.filter.all': 'すべて', + 'todo.filter.open': '未完了', + 'todo.filter.done': '完了', + 'todo.uncategorized': '未分類', + 'todo.namePlaceholder': 'タスク名', + 'todo.descriptionPlaceholder': '説明(任意)', + 'todo.unassigned': '未割り当て', + 'todo.noCategory': 'カテゴリなし', + 'todo.hasDescription': '説明あり', + 'todo.addItem': '新しいタスクを追加...', + 'todo.sidebar.sortBy': '並び替え', + 'todo.priority': '優先度', + 'todo.newCategoryLabel': '新規', + 'todo.newCategory': 'カテゴリ名', + 'todo.addCategory': 'カテゴリを追加', + 'todo.newItem': '新しいタスク', + 'todo.empty': 'タスクはまだありません。追加して始めましょう!', + 'todo.filter.my': '自分のタスク', + 'todo.filter.overdue': '期限切れ', + 'todo.sidebar.tasks': 'タスク', + 'todo.sidebar.categories': 'カテゴリ', + 'todo.detail.title': 'タスク', + 'todo.detail.description': '説明', + 'todo.detail.category': 'カテゴリ', + 'todo.detail.dueDate': '期限', + 'todo.detail.assignedTo': '担当者', + 'todo.detail.delete': '削除', + 'todo.detail.save': '変更を保存', + 'todo.sortByPrio': '優先度', + 'todo.detail.priority': '優先度', + 'todo.detail.noPriority': 'なし', + 'todo.detail.create': 'タスクを作成', +}; +export default todo; diff --git a/shared/src/i18n/ja/transport.ts b/shared/src/i18n/ja/transport.ts new file mode 100644 index 00000000..ca58fa24 --- /dev/null +++ b/shared/src/i18n/ja/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': '移動手段を追加', + 'transport.modalTitle.create': '移動手段を追加', + 'transport.modalTitle.edit': '移動手段を編集', + 'transport.title': '移動手段', + 'transport.addManual': '手動で追加', +}; +export default transport; diff --git a/shared/src/i18n/ja/trip.ts b/shared/src/i18n/ja/trip.ts new file mode 100644 index 00000000..8cd2547c --- /dev/null +++ b/shared/src/i18n/ja/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': '計画', + 'trip.tabs.transports': '移動', + 'trip.tabs.reservations': '予約', + 'trip.tabs.reservationsShort': '予約', + 'trip.tabs.packing': '持ち物リスト', + 'trip.tabs.packingShort': '持ち物', + 'trip.tabs.lists': 'リスト', + 'trip.tabs.listsShort': 'リスト', + 'trip.tabs.budget': '予算', + 'trip.tabs.files': 'ファイル', + 'trip.loading': '旅行を読み込み中...', + 'trip.loadingPhotos': '場所の写真を読み込み中...', + 'trip.mobilePlan': '計画', + 'trip.mobilePlaces': '場所', + 'trip.toast.placeUpdated': '場所を更新しました', + 'trip.toast.placeAdded': '場所を追加しました', + 'trip.toast.placeDeleted': '場所を削除しました', + 'trip.toast.selectDay': 'まず日を選択してください', + 'trip.toast.assignedToDay': '場所を日に割り当てました', + 'trip.toast.reorderError': '並び替えに失敗しました', + 'trip.toast.reservationUpdated': '予約を更新しました', + 'trip.toast.reservationAdded': '予約を追加しました', + 'trip.toast.deleted': '削除しました', + 'trip.confirm.deletePlace': 'この場所を削除してもよろしいですか?', + 'trip.confirm.deletePlaces': '{count}件の場所を削除してもよろしいですか?', + 'trip.toast.placesDeleted': '{count}件の場所を削除しました', +}; +export default trip; diff --git a/shared/src/i18n/ja/trips.ts b/shared/src/i18n/ja/trips.ts new file mode 100644 index 00000000..04b53711 --- /dev/null +++ b/shared/src/i18n/ja/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} を削除しました', + 'trips.memberRemoveError': '削除に失敗しました', + 'trips.memberAdded': '{username} を追加しました', + 'trips.memberAddError': '追加に失敗しました', + 'trips.reminder': 'リマインダー', + 'trips.reminderNone': 'なし', + 'trips.reminderDay': '日', + 'trips.reminderDays': '日', + 'trips.reminderCustom': 'カスタム', + 'trips.reminderDaysBefore': '出発前', + 'trips.reminderDisabledHint': + '旅行のリマインダーは無効です。管理 > 設定 > 通知から有効にしてください。', +}; +export default trips; diff --git a/shared/src/i18n/ja/undo.ts b/shared/src/i18n/ja/undo.ts new file mode 100644 index 00000000..cb8aafab --- /dev/null +++ b/shared/src/i18n/ja/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': '元に戻す', + 'undo.tooltip': '元に戻す: {action}', + 'undo.assignPlace': '場所を日に割り当て', + 'undo.removeAssignment': '日の割り当てを解除', + 'undo.reorder': '場所を並び替え', + 'undo.optimize': 'ルートを最適化', + 'undo.deletePlace': '場所を削除', + 'undo.deletePlaces': '場所を削除', + 'undo.moveDay': '場所を別の日に移動', + 'undo.lock': '場所のロックを切り替え', + 'undo.importGpx': 'GPXをインポート', + 'undo.importKeyholeMarkup': 'KMZ/KMLをインポート', + 'undo.importGoogleList': 'Googleマップをインポート', + 'undo.importNaverList': 'Naverマップをインポート', + 'undo.addPlace': '場所を追加', + 'undo.done': '元に戻しました: {action}', +}; +export default undo; diff --git a/shared/src/i18n/ja/vacay.ts b/shared/src/i18n/ja/vacay.ts new file mode 100644 index 00000000..a0631e11 --- /dev/null +++ b/shared/src/i18n/ja/vacay.ts @@ -0,0 +1,97 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': '休暇日数の計画と管理', + 'vacay.settings': '設定', + 'vacay.year': '年', + 'vacay.addYear': '翌年を追加', + 'vacay.addPrevYear': '前年を追加', + 'vacay.removeYear': '年を削除', + 'vacay.removeYearConfirm': '{year}年を削除しますか?', + 'vacay.removeYearHint': + 'この年の休暇データと会社休日はすべて完全に削除されます。', + 'vacay.remove': '削除', + 'vacay.persons': '人物', + 'vacay.noPersons': '人物が追加されていません', + 'vacay.addPerson': '人物を追加', + 'vacay.editPerson': '人物を編集', + 'vacay.removePerson': '人物を削除', + 'vacay.removePersonConfirm': '{name}を削除しますか?', + 'vacay.removePersonHint': + 'この人物のすべての休暇データが完全に削除されます。', + 'vacay.personName': '名前', + 'vacay.personNamePlaceholder': '名前を入力', + 'vacay.color': '色', + 'vacay.add': '追加', + 'vacay.legend': '凡例', + 'vacay.publicHoliday': '祝日', + 'vacay.companyHoliday': '会社休日', + 'vacay.weekend': '週末', + 'vacay.modeVacation': '休暇', + 'vacay.modeCompany': '会社休日', + 'vacay.entitlement': '付与日数', + 'vacay.entitlementDays': '日', + 'vacay.used': '使用済み', + 'vacay.remaining': '残り', + 'vacay.carriedOver': '{year}年から繰越', + 'vacay.blockWeekends': '週末を除外', + 'vacay.blockWeekendsHint': '週末に休暇を登録できないようにします', + 'vacay.weekendDays': '週末', + 'vacay.mon': '月', + 'vacay.tue': '火', + 'vacay.wed': '水', + 'vacay.thu': '木', + 'vacay.fri': '金', + 'vacay.sat': '土', + 'vacay.sun': '日', + 'vacay.publicHolidays': '祝日', + 'vacay.publicHolidaysHint': 'カレンダーに祝日を表示', + 'vacay.selectCountry': '国を選択', + 'vacay.selectRegion': '地域を選択(任意)', + 'vacay.addCalendar': 'カレンダーを追加', + 'vacay.calendarLabel': 'ラベル(任意)', + 'vacay.calendarColor': '色', + 'vacay.noCalendars': '祝日カレンダーはまだありません', + 'vacay.companyHolidays': '会社休日', + 'vacay.companyHolidaysHint': '会社全体の休日を設定できます', + 'vacay.companyHolidaysNoDeduct': '会社休日は休暇日数に含まれません。', + 'vacay.weekStart': '週の開始', + 'vacay.weekStartHint': + 'カレンダーの週を月曜始まりにするか日曜始まりにするかを選択します', + 'vacay.carryOver': '繰越', + 'vacay.carryOverHint': '残りの休暇日数を翌年に自動で繰り越します', + 'vacay.sharing': '共有', + 'vacay.sharingHint': '他のTREKユーザーと休暇計画を共有', + 'vacay.owner': '所有者', + 'vacay.shareEmailPlaceholder': 'TREKユーザーのメール', + 'vacay.shareSuccess': '計画を共有しました', + 'vacay.shareError': '共有できませんでした', + 'vacay.dissolve': '統合を解除', + 'vacay.dissolveHint': 'カレンダーを分離します。データは保持されます。', + 'vacay.dissolveAction': '解除', + 'vacay.dissolved': 'カレンダーを分離しました', + 'vacay.fusedWith': '統合相手', + 'vacay.you': 'あなた', + 'vacay.noData': 'データなし', + 'vacay.changeColor': '色を変更', + 'vacay.inviteUser': 'ユーザーを招待', + 'vacay.inviteHint': + '別のTREKユーザーを招待して、休暇カレンダーを共有します。', + 'vacay.selectUser': 'ユーザーを選択', + 'vacay.sendInvite': '招待を送信', + 'vacay.inviteSent': '招待を送信しました', + 'vacay.inviteError': '招待を送信できませんでした', + 'vacay.pending': '保留中', + 'vacay.noUsersAvailable': '利用可能なユーザーがいません', + 'vacay.accept': '承認', + 'vacay.decline': '拒否', + 'vacay.acceptFusion': '承認して統合', + 'vacay.inviteTitle': '統合の招待', + 'vacay.inviteWantsToFuse': 'が休暇カレンダーの共有を希望しています。', + 'vacay.fuseInfo1': '双方が1つの共有カレンダーですべての休暇を確認できます。', + 'vacay.fuseInfo2': '双方が互いの予定を作成・編集できます。', + 'vacay.fuseInfo3': '双方が予定の削除や付与日数の変更を行えます。', + 'vacay.fuseInfo4': '祝日や会社休日などの設定は共有されます。', + 'vacay.fuseInfo5': '統合はいつでも解除できます。データは保持されます。', +}; +export default vacay; diff --git a/shared/src/i18n/ko/admin.ts b/shared/src/i18n/ko/admin.ts new file mode 100644 index 00000000..bc1743b6 --- /dev/null +++ b/shared/src/i18n/ko/admin.ts @@ -0,0 +1,353 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': '알림', + 'admin.notifications.hint': + '알림 채널을 하나 선택하세요. 한 번에 하나만 활성화할 수 있습니다.', + 'admin.notifications.none': '비활성화', + 'admin.notifications.email': '이메일 (SMTP)', + 'admin.notifications.webhook': '웹훅', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + '사용자가 자신의 ntfy 토픽을 설정하여 푸시 알림을 받을 수 있습니다. 아래에 기본 서버를 설정하면 사용자 설정에 미리 채워집니다.', + 'admin.notifications.save': '알림 설정 저장', + 'admin.notifications.saved': '알림 설정이 저장되었습니다', + 'admin.notifications.testWebhook': '테스트 웹훅 전송', + 'admin.notifications.testWebhookSuccess': + '테스트 웹훅이 성공적으로 전송되었습니다', + 'admin.notifications.testWebhookFailed': '테스트 웹훅 실패', + 'admin.notifications.testNtfy': '테스트 ntfy 전송', + 'admin.notifications.testNtfySuccess': + '테스트 ntfy가 성공적으로 전송되었습니다', + 'admin.notifications.testNtfyFailed': '테스트 ntfy 실패', + 'admin.notifications.emailPanel.title': '이메일 (SMTP)', + 'admin.notifications.webhookPanel.title': '웹훅', + 'admin.notifications.inappPanel.title': '앱 내', + 'admin.notifications.inappPanel.hint': + '앱 내 알림은 항상 활성화되어 있으며 전역으로 비활성화할 수 없습니다.', + 'admin.notifications.adminWebhookPanel.title': '관리자 웹훅', + 'admin.notifications.adminWebhookPanel.hint': + '이 웹훅은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 웹훅과 별개이며 설정 시 항상 실행됩니다.', + 'admin.notifications.adminWebhookPanel.saved': + '관리자 웹훅 URL이 저장되었습니다', + 'admin.notifications.adminWebhookPanel.testSuccess': + '테스트 웹훅이 성공적으로 전송되었습니다', + 'admin.notifications.adminWebhookPanel.testFailed': '테스트 웹훅 실패', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'URL이 설정되면 관리자 웹훅은 항상 실행됩니다', + 'admin.notifications.adminNtfyPanel.title': '관리자 Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + '이 ntfy 토픽은 관리자 알림 전용입니다 (예: 버전 알림). 사용자별 토픽과 별개이며 설정 시 항상 실행됩니다.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 서버 URL', + 'admin.notifications.adminNtfyPanel.serverHint': + '사용자 ntfy 알림의 기본 서버로도 사용됩니다. 비워두면 ntfy.sh가 기본값입니다. 사용자는 자신의 설정에서 변경할 수 있습니다.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': '관리자 토픽', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': '액세스 토큰 (선택)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + '관리자 액세스 토큰이 삭제되었습니다', + 'admin.notifications.adminNtfyPanel.saved': + '관리자 ntfy 설정이 저장되었습니다', + 'admin.notifications.adminNtfyPanel.test': '테스트 ntfy 전송', + 'admin.notifications.adminNtfyPanel.testSuccess': + '테스트 ntfy가 성공적으로 전송되었습니다', + 'admin.notifications.adminNtfyPanel.testFailed': '테스트 ntfy 실패', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + '토픽이 설정되면 관리자 ntfy는 항상 실행됩니다', + 'admin.notifications.adminNotificationsHint': + '관리자 전용 알림 (예: 버전 알림)을 전달할 채널을 설정하세요.', + 'admin.notifications.tripReminders.title': '여행 리마인더', + 'admin.notifications.tripReminders.hint': + '여행 시작 전에 리마인더 알림을 전송합니다 (여행에 리마인더 일수가 설정되어 있어야 합니다).', + 'admin.notifications.tripReminders.enabled': '여행 리마인더 활성화됨', + 'admin.notifications.tripReminders.disabled': '여행 리마인더 비활성화됨', + 'admin.smtp.title': '이메일 및 알림', + 'admin.smtp.hint': '이메일 알림 전송을 위한 SMTP 설정입니다.', + 'admin.smtp.testButton': '테스트 이메일 전송', + 'admin.webhook.hint': + '사용자가 알림용 웹훅 URL을 설정할 수 있습니다 (Discord, Slack 등).', + 'admin.smtp.testSuccess': '테스트 이메일이 성공적으로 전송되었습니다', + 'admin.smtp.testFailed': '테스트 이메일 실패', + 'admin.title': '관리자', + 'admin.subtitle': '사용자 관리 및 시스템 설정', + 'admin.tabs.users': '사용자', + 'admin.tabs.categories': '카테고리', + 'admin.tabs.backup': '백업', + 'admin.tabs.notifications': '알림', + 'admin.tabs.audit': '감사', + 'admin.stats.users': '사용자', + 'admin.stats.trips': '여행', + 'admin.stats.places': '장소', + 'admin.stats.photos': '사진', + 'admin.stats.files': '파일', + 'admin.table.user': '사용자', + 'admin.table.email': '이메일', + 'admin.table.role': '역할', + 'admin.table.created': '생성일', + 'admin.table.lastLogin': '마지막 로그인', + 'admin.table.actions': '작업', + 'admin.you': '(나)', + 'admin.editUser': '사용자 편집', + 'admin.newPassword': '새 비밀번호', + 'admin.newPasswordHint': '비워두면 현재 비밀번호 유지', + 'admin.deleteUser': + '사용자 "{name}"을(를) 삭제할까요? 모든 여행이 영구 삭제됩니다.', + 'admin.deleteUserTitle': '사용자 삭제', + 'admin.newPasswordPlaceholder': '새 비밀번호 입력…', + 'admin.toast.loadError': '관리자 데이터 불러오기 실패', + 'admin.toast.userUpdated': '사용자가 업데이트되었습니다', + 'admin.toast.updateError': '업데이트 실패', + 'admin.toast.userDeleted': '사용자가 삭제되었습니다', + 'admin.toast.deleteError': '삭제 실패', + 'admin.toast.cannotDeleteSelf': '자신의 계정은 삭제할 수 없습니다', + 'admin.toast.userCreated': '사용자가 생성되었습니다', + 'admin.toast.createError': '사용자 생성 실패', + 'admin.toast.fieldsRequired': '사용자 이름, 이메일, 비밀번호가 필요합니다', + 'admin.createUser': '사용자 만들기', + 'admin.invite.title': '초대 링크', + 'admin.invite.subtitle': '일회용 회원가입 링크 만들기', + 'admin.invite.create': '링크 만들기', + 'admin.invite.createAndCopy': '만들기 및 복사', + 'admin.invite.empty': '아직 초대 링크가 없습니다', + 'admin.invite.maxUses': '최대 사용 횟수', + 'admin.invite.expiry': '만료 기간', + 'admin.invite.uses': '사용됨', + 'admin.invite.expiresAt': '만료', + 'admin.invite.createdBy': '작성자', + 'admin.invite.active': '활성', + 'admin.invite.expired': '만료됨', + 'admin.invite.usedUp': '사용 완료', + 'admin.invite.copied': '초대 링크가 클립보드에 복사되었습니다', + 'admin.invite.copyLink': '링크 복사', + 'admin.invite.deleted': '초대 링크가 삭제되었습니다', + 'admin.invite.createError': '초대 링크 생성 실패', + 'admin.invite.deleteError': '초대 링크 삭제 실패', + 'admin.tabs.settings': '설정', + 'admin.allowRegistration': '회원가입 허용', + 'admin.allowRegistrationHint': '새 사용자가 직접 회원가입할 수 있습니다', + 'admin.authMethods': '인증 방법', + 'admin.passwordLogin': '비밀번호 로그인', + 'admin.passwordLoginHint': + '사용자가 이메일과 비밀번호로 로그인할 수 있습니다', + 'admin.passwordRegistration': '비밀번호 회원가입', + 'admin.passwordRegistrationHint': + '새 사용자가 이메일과 비밀번호로 가입할 수 있습니다', + 'admin.oidcLogin': 'SSO 로그인', + 'admin.oidcLoginHint': '사용자가 SSO로 로그인할 수 있습니다', + 'admin.oidcRegistration': 'SSO 자동 프로비저닝', + 'admin.oidcRegistrationHint': '새 SSO 사용자의 계정을 자동으로 생성합니다', + 'admin.envOverrideHint': + '비밀번호 로그인 설정은 OIDC_ONLY 환경 변수로 제어되며 여기서 변경할 수 없습니다.', + 'admin.lockoutWarning': '최소 하나의 로그인 방법이 활성화되어 있어야 합니다', + 'admin.requireMfa': '2단계 인증 (2FA) 요구', + 'admin.requireMfaHint': + '2FA가 없는 사용자는 앱을 사용하기 전에 설정에서 설정을 완료해야 합니다.', + 'admin.apiKeys': 'API 키', + 'admin.apiKeysHint': + '선택 사항. 사진 및 날씨 등 확장된 장소 데이터를 활성화합니다.', + 'admin.mapsKey': 'Google Maps API 키', + 'admin.mapsKeyHint': + '장소 검색에 필요합니다. console.cloud.google.com에서 발급', + 'admin.mapsKeyHintLong': + 'API 키 없이는 장소 검색에 OpenStreetMap이 사용됩니다. Google API 키가 있으면 사진, 평점, 영업 시간도 불러올 수 있습니다. console.cloud.google.com에서 발급하세요.', + 'admin.recommended': '권장', + 'admin.weatherKey': 'OpenWeatherMap API 키', + 'admin.weatherKeyHint': '날씨 데이터용. openweathermap.org에서 무료 발급', + 'admin.validateKey': '테스트', + 'admin.keyValid': '연결됨', + 'admin.keyInvalid': '유효하지 않음', + 'admin.keySaved': 'API 키가 저장되었습니다', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Google, Apple, Authentik 또는 Keycloak 등 외부 공급자를 통한 로그인을 허용합니다.', + 'admin.oidcDisplayName': '표시 이름', + 'admin.oidcIssuer': '발급자 URL', + 'admin.oidcIssuerHint': + '공급자의 OpenID Connect 발급자 URL. 예: https://accounts.google.com', + 'admin.oidcSaved': 'OIDC 설정이 저장되었습니다', + 'admin.oidcOnlyMode': '비밀번호 인증 비활성화', + 'admin.oidcOnlyModeHint': + '활성화하면 SSO 로그인만 허용됩니다. 비밀번호 기반 로그인 및 회원가입이 차단됩니다.', + 'admin.fileTypes': '허용된 파일 형식', + 'admin.fileTypesHint': '사용자가 업로드할 수 있는 파일 형식을 설정합니다.', + 'admin.fileTypesFormat': + '쉼표로 구분된 확장자 (예: jpg,png,pdf,doc). 모든 형식을 허용하려면 *를 사용하세요.', + 'admin.fileTypesSaved': '파일 형식 설정이 저장되었습니다', + 'admin.placesPhotos.title': '장소 사진', + 'admin.placesPhotos.subtitle': + 'Google Places API에서 사진을 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다. Wikimedia 사진은 영향을 받지 않습니다.', + 'admin.placesAutocomplete.title': '장소 자동완성', + 'admin.placesAutocomplete.subtitle': + '검색 제안에 Google Places API를 사용합니다. API 할당량 절약을 위해 비활성화할 수 있습니다.', + 'admin.placesDetails.title': '장소 상세 정보', + 'admin.placesDetails.subtitle': + 'Google Places API에서 상세 장소 정보 (영업 시간, 평점, 웹사이트)를 가져옵니다. API 할당량 절약을 위해 비활성화할 수 있습니다.', + 'admin.bagTracking.title': '가방 추적', + 'admin.bagTracking.subtitle': '짐 항목에 무게 및 가방 배정을 활성화합니다', + 'admin.collab.chat.title': '채팅', + 'admin.collab.chat.subtitle': '여행 협업을 위한 실시간 메시지', + 'admin.collab.notes.title': '메모', + 'admin.collab.notes.subtitle': '공유 메모 및 문서', + 'admin.collab.polls.title': '투표', + 'admin.collab.polls.subtitle': '그룹 투표', + 'admin.collab.whatsnext.title': '다음 할 일', + 'admin.collab.whatsnext.subtitle': '활동 제안 및 다음 단계', + 'admin.tabs.config': '개인 설정', + 'admin.tabs.defaults': '기본값', + 'admin.defaultSettings.title': '기본 사용자 설정', + 'admin.defaultSettings.description': + '인스턴스 전체 기본값을 설정합니다. 설정을 변경하지 않은 사용자에게 이 값이 표시됩니다. 사용자의 변경 사항이 항상 우선합니다.', + 'admin.defaultSettings.saved': '기본값이 저장되었습니다', + 'admin.defaultSettings.reset': '기본 내장값으로 초기화', + 'admin.defaultSettings.resetToBuiltIn': '초기화', + 'admin.tabs.templates': '짐 목록 템플릿', + 'admin.packingTemplates.title': '짐 목록 템플릿', + 'admin.packingTemplates.subtitle': + '여행을 위한 재사용 가능한 짐 목록을 만드세요', + 'admin.packingTemplates.create': '새 템플릿', + 'admin.packingTemplates.namePlaceholder': '템플릿 이름 (예: 해변 휴가)', + 'admin.packingTemplates.empty': '아직 템플릿이 없습니다', + 'admin.packingTemplates.items': '항목', + 'admin.packingTemplates.categories': '카테고리', + 'admin.packingTemplates.itemName': '항목 이름', + 'admin.packingTemplates.itemCategory': '카테고리', + 'admin.packingTemplates.categoryName': '카테고리 이름 (예: 의류)', + 'admin.packingTemplates.addCategory': '카테고리 추가', + 'admin.packingTemplates.created': '템플릿이 생성되었습니다', + 'admin.packingTemplates.deleted': '템플릿이 삭제되었습니다', + 'admin.packingTemplates.loadError': '템플릿 불러오기 실패', + 'admin.packingTemplates.createError': '템플릿 생성 실패', + 'admin.packingTemplates.deleteError': '템플릿 삭제 실패', + 'admin.packingTemplates.saveError': '저장 실패', + 'admin.tabs.addons': '애드온', + 'admin.addons.title': '애드온', + 'admin.addons.subtitle': + '기능을 활성화 또는 비활성화하여 TREK 경험을 맞춤 설정하세요.', + 'admin.addons.catalog.packing.name': '목록', + 'admin.addons.catalog.packing.description': + '여행을 위한 짐 목록 및 할 일 작업', + 'admin.addons.catalog.budget.name': '예산', + 'admin.addons.catalog.budget.description': '지출 추적 및 여행 예산 계획', + 'admin.addons.catalog.documents.name': '문서', + 'admin.addons.catalog.documents.description': '여행 서류 저장 및 관리', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + '캘린더 보기가 있는 개인 휴가 플래너', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + '방문한 나라와 여행 통계가 있는 세계 지도', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + '여행 계획을 위한 실시간 메모, 투표, 채팅', + 'admin.addons.catalog.memories.name': '사진 (Immich)', + 'admin.addons.catalog.memories.description': + 'Immich 인스턴스를 통해 여행 사진 공유', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'AI 어시스턴트 통합을 위한 모델 컨텍스트 프로토콜', + 'admin.addons.subtitleBefore': '기능을 활성화 또는 비활성화하여 ', + 'admin.addons.subtitleAfter': ' 경험을 맞춤 설정하세요.', + 'admin.addons.enabled': '활성화됨', + 'admin.addons.disabled': '비활성화됨', + 'admin.addons.type.trip': '여행', + 'admin.addons.type.global': '전역', + 'admin.addons.type.integration': '통합', + 'admin.addons.tripHint': '각 여행 내 탭으로 사용 가능', + 'admin.addons.globalHint': '메인 내비게이션의 독립 섹션으로 사용 가능', + 'admin.addons.integrationHint': + '전용 페이지가 없는 백엔드 서비스 및 API 통합', + 'admin.addons.toast.updated': '애드온이 업데이트되었습니다', + 'admin.addons.toast.error': '애드온 업데이트 실패', + 'admin.addons.noAddons': '사용 가능한 애드온이 없습니다', + 'admin.weather.title': '날씨 데이터', + 'admin.weather.badge': '2026년 3월 24일부터', + 'admin.weather.description': + 'TREK은 날씨 데이터 소스로 Open-Meteo를 사용합니다. Open-Meteo는 무료 오픈 소스 날씨 서비스로 API 키가 필요 없습니다.', + 'admin.weather.forecast': '16일 예보', + 'admin.weather.forecastDesc': '이전: 5일 (OpenWeatherMap)', + 'admin.weather.climate': '과거 기후 데이터', + 'admin.weather.climateDesc': '16일 예보 이후 날짜의 85년 평균값', + 'admin.weather.requests': '하루 10,000 요청', + 'admin.weather.requestsDesc': '무료, API 키 불필요', + 'admin.weather.locationHint': + '날씨는 각 날의 좌표가 있는 첫 번째 장소를 기준으로 합니다. 날에 배정된 장소가 없으면 장소 목록의 임의 장소가 참조로 사용됩니다.', + 'admin.tabs.mcpTokens': 'MCP 접근', + 'admin.mcpTokens.title': 'MCP 접근', + 'admin.mcpTokens.subtitle': + '모든 사용자의 OAuth 세션 및 API 토큰을 관리합니다', + 'admin.mcpTokens.sectionTitle': 'API 토큰', + 'admin.mcpTokens.owner': '소유자', + 'admin.mcpTokens.tokenName': '토큰 이름', + 'admin.mcpTokens.created': '생성일', + 'admin.mcpTokens.lastUsed': '마지막 사용', + 'admin.mcpTokens.never': '없음', + 'admin.mcpTokens.empty': '생성된 MCP 토큰이 없습니다', + 'admin.mcpTokens.deleteTitle': '토큰 삭제', + 'admin.mcpTokens.deleteMessage': + '토큰이 즉시 취소됩니다. 사용자는 이 토큰을 통한 MCP 접근 권한을 잃게 됩니다.', + 'admin.mcpTokens.deleteSuccess': '토큰이 삭제되었습니다', + 'admin.mcpTokens.deleteError': '토큰 삭제 실패', + 'admin.mcpTokens.loadError': '토큰 불러오기 실패', + 'admin.oauthSessions.sectionTitle': 'OAuth 세션', + 'admin.oauthSessions.clientName': '클라이언트', + 'admin.oauthSessions.owner': '소유자', + 'admin.oauthSessions.scopes': '권한 범위', + 'admin.oauthSessions.created': '생성일', + 'admin.oauthSessions.empty': '활성 OAuth 세션이 없습니다', + 'admin.oauthSessions.revokeTitle': '세션 취소', + 'admin.oauthSessions.revokeMessage': + 'OAuth 세션이 즉시 취소됩니다. 클라이언트는 MCP 접근 권한을 잃게 됩니다.', + 'admin.oauthSessions.revokeSuccess': '세션이 취소되었습니다', + 'admin.oauthSessions.revokeError': '세션 취소 실패', + 'admin.oauthSessions.loadError': 'OAuth 세션 불러오기 실패', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': '보안 및 관리 이벤트 (백업, 사용자, MFA, 설정).', + 'admin.audit.empty': '아직 감사 항목이 없습니다.', + 'admin.audit.refresh': '새로 고침', + 'admin.audit.loadMore': '더 불러오기', + 'admin.audit.showing': '{count}개 불러옴 · 총 {total}개', + 'admin.audit.col.time': '시간', + 'admin.audit.col.user': '사용자', + 'admin.audit.col.action': '작업', + 'admin.audit.col.resource': '리소스', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': '상세', + 'admin.github.title': '릴리스 히스토리', + 'admin.github.subtitle': '{repo}의 최신 업데이트', + 'admin.github.latest': '최신', + 'admin.github.prerelease': '사전 릴리스', + 'admin.github.showDetails': '상세 보기', + 'admin.github.hideDetails': '상세 숨기기', + 'admin.github.loadMore': '더 불러오기', + 'admin.github.loading': '불러오는 중...', + 'admin.github.error': '릴리스 불러오기 실패', + 'admin.github.by': '작성자', + 'admin.github.support': 'TREK 개발 지속에 도움이 됩니다', + 'admin.update.available': '업데이트 사용 가능', + 'admin.update.text': + 'TREK {version}이(가) 사용 가능합니다. 현재 {current}을(를) 실행 중입니다.', + 'admin.update.button': 'GitHub에서 보기', + 'admin.update.install': '업데이트 설치', + 'admin.update.confirmTitle': '업데이트를 설치할까요?', + 'admin.update.confirmText': + 'TREK이 {current}에서 {version}으로 업데이트됩니다. 서버가 이후 자동으로 재시작됩니다.', + 'admin.update.dataInfo': + '모든 데이터 (여행, 사용자, API 키, 업로드, Vacay, Atlas, 예산)가 보존됩니다.', + 'admin.update.warning': '재시작 중에 앱이 잠시 사용할 수 없게 됩니다.', + 'admin.update.confirm': '지금 업데이트', + 'admin.update.installing': '업데이트 중…', + 'admin.update.success': '업데이트 완료! 서버가 재시작 중입니다…', + 'admin.update.failed': '업데이트 실패', + 'admin.update.backupHint': '업데이트 전에 백업을 생성하는 것을 권장합니다.', + 'admin.update.backupLink': '백업으로 이동', + 'admin.update.howTo': '업데이트 방법', + 'admin.update.dockerText': + 'TREK 인스턴스가 Docker에서 실행 중입니다. {version}으로 업데이트하려면 서버에서 다음 명령을 실행하세요:', + 'admin.update.reloadHint': '잠시 후 페이지를 새로 고침하세요.', + 'admin.tabs.permissions': '권한', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + '체크인, 사진, 일별 이야기가 있는 여행 기록 및 여행 일지', +}; +export default admin; diff --git a/shared/src/i18n/ko/airport.ts b/shared/src/i18n/ko/airport.ts new file mode 100644 index 00000000..19dccd83 --- /dev/null +++ b/shared/src/i18n/ko/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': '공항 코드 또는 도시 (예: ICN)', +}; +export default airport; diff --git a/shared/src/i18n/ko/atlas.ts b/shared/src/i18n/ko/atlas.ts new file mode 100644 index 00000000..ea8a9a4e --- /dev/null +++ b/shared/src/i18n/ko/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': '전 세계의 나의 여행 발자취', + 'atlas.countries': '국가', + 'atlas.trips': '여행', + 'atlas.places': '장소', + 'atlas.unmark': '제거', + 'atlas.confirmMark': '이 나라를 방문한 곳으로 표시할까요?', + 'atlas.confirmUnmark': '방문 목록에서 이 나라를 제거할까요?', + 'atlas.confirmUnmarkRegion': '방문 목록에서 이 지역을 제거할까요?', + 'atlas.markVisited': '방문으로 표시', + 'atlas.markVisitedHint': '방문 목록에 이 나라를 추가합니다', + 'atlas.markRegionVisitedHint': '방문 목록에 이 지역을 추가합니다', + 'atlas.addToBucket': '버킷 리스트에 추가', + 'atlas.addPoi': '장소 추가', + 'atlas.searchCountry': '국가 검색...', + 'atlas.bucketNamePlaceholder': '이름 (국가, 도시, 장소...)', + 'atlas.month': '월', + 'atlas.year': '연도', + 'atlas.addToBucketHint': '방문하고 싶은 장소로 저장', + 'atlas.bucketWhen': '방문 예정 시기는 언제인가요?', + 'atlas.statsTab': '통계', + 'atlas.bucketTab': '버킷 리스트', + 'atlas.addBucket': '버킷 리스트에 추가', + 'atlas.bucketNotesPlaceholder': '메모 (선택)', + 'atlas.bucketEmpty': '버킷 리스트가 비어 있습니다', + 'atlas.bucketEmptyHint': '꿈꾸는 방문지를 추가하세요', + 'atlas.days': '일', + 'atlas.visitedCountries': '방문한 나라', + 'atlas.cities': '도시', + 'atlas.noData': '아직 여행 데이터가 없습니다', + 'atlas.noDataHint': '여행을 만들고 장소를 추가하여 세계 지도를 확인하세요', + 'atlas.lastTrip': '마지막 여행', + 'atlas.nextTrip': '다음 여행', + 'atlas.daysLeft': '일 남음', + 'atlas.streak': '연속', + 'atlas.years': '년', + 'atlas.yearInRow': '연 연속', + 'atlas.yearsInRow': '년 연속', + 'atlas.tripIn': '에서 여행', + 'atlas.tripsIn': '에서 여행', + 'atlas.since': '부터', + 'atlas.europe': '유럽', + 'atlas.asia': '아시아', + 'atlas.northAmerica': '북미', + 'atlas.southAmerica': '남미', + 'atlas.africa': '아프리카', + 'atlas.oceania': '오세아니아', + 'atlas.other': '기타', + 'atlas.firstVisit': '첫 번째 여행', + 'atlas.lastVisitLabel': '마지막 여행', + 'atlas.tripSingular': '여행', + 'atlas.tripPlural': '여행', + 'atlas.placeVisited': '방문한 장소', + 'atlas.placesVisited': '방문한 장소', +}; +export default atlas; diff --git a/shared/src/i18n/ko/backup.ts b/shared/src/i18n/ko/backup.ts new file mode 100644 index 00000000..e7de69c0 --- /dev/null +++ b/shared/src/i18n/ko/backup.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': '데이터 백업', + 'backup.subtitle': '데이터베이스 및 모든 업로드된 파일', + 'backup.refresh': '새로 고침', + 'backup.upload': '백업 업로드', + 'backup.uploading': '업로드 중…', + 'backup.create': '백업 만들기', + 'backup.creating': '생성 중…', + 'backup.empty': '아직 백업이 없습니다', + 'backup.createFirst': '첫 번째 백업 만들기', + 'backup.download': '다운로드', + 'backup.restore': '복원', + 'backup.confirm.restore': + '백업 "{name}"을(를) 복원할까요?\n\n현재 모든 데이터가 백업으로 교체됩니다.', + 'backup.confirm.uploadRestore': + '백업 파일 "{name}"을(를) 업로드하고 복원할까요?\n\n현재 모든 데이터가 덮어쓰여집니다.', + 'backup.confirm.delete': '백업 "{name}"을(를) 삭제할까요?', + 'backup.toast.loadError': '백업 불러오기 실패', + 'backup.toast.created': '백업이 성공적으로 생성되었습니다', + 'backup.toast.createError': '백업 생성 실패', + 'backup.toast.restored': '백업이 복원되었습니다. 페이지가 새로 고침됩니다…', + 'backup.toast.restoreError': '복원 실패', + 'backup.toast.uploadError': '업로드 실패', + 'backup.toast.deleted': '백업이 삭제되었습니다', + 'backup.toast.deleteError': '삭제 실패', + 'backup.toast.downloadError': '다운로드 실패', + 'backup.toast.settingsSaved': '자동 백업 설정이 저장되었습니다', + 'backup.toast.settingsError': '설정 저장 실패', + 'backup.auto.title': '자동 백업', + 'backup.auto.subtitle': '일정에 따른 자동 백업', + 'backup.auto.enable': '자동 백업 활성화', + 'backup.auto.enableHint': '선택한 일정에 따라 자동으로 백업이 생성됩니다', + 'backup.auto.interval': '간격', + 'backup.auto.hour': '실행 시간', + 'backup.auto.hourHint': '서버 현지 시간 ({format} 형식)', + 'backup.auto.dayOfWeek': '요일', + 'backup.auto.dayOfMonth': '매월 몇 일', + 'backup.auto.dayOfMonthHint': '모든 달과의 호환성을 위해 1-28로 제한', + 'backup.auto.scheduleSummary': '일정', + 'backup.auto.summaryDaily': '매일 {hour}:00', + 'backup.auto.summaryWeekly': '매주 {day} {hour}:00', + 'backup.auto.summaryMonthly': '매월 {day}일 {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + '자동 백업이 Docker 환경 변수로 설정되어 있습니다. 설정을 변경하려면 docker-compose.yml을 업데이트하고 컨테이너를 재시작하세요.', + 'backup.auto.copyEnv': 'Docker 환경 변수 복사', + 'backup.auto.envCopied': 'Docker 환경 변수가 클립보드에 복사되었습니다', + 'backup.auto.keepLabel': '이후 오래된 백업 삭제', + 'backup.dow.sunday': '일', + 'backup.dow.monday': '월', + 'backup.dow.tuesday': '화', + 'backup.dow.wednesday': '수', + 'backup.dow.thursday': '목', + 'backup.dow.friday': '금', + 'backup.dow.saturday': '토', + 'backup.interval.hourly': '매시간', + 'backup.interval.daily': '매일', + 'backup.interval.weekly': '매주', + 'backup.interval.monthly': '매월', + 'backup.keep.1day': '1일', + 'backup.keep.3days': '3일', + 'backup.keep.7days': '7일', + 'backup.keep.14days': '14일', + 'backup.keep.30days': '30일', + 'backup.keep.forever': '영구 보관', + 'backup.restoreConfirmTitle': '백업을 복원할까요?', + 'backup.restoreWarning': + '현재 모든 데이터 (여행, 장소, 사용자, 업로드)가 백업으로 영구 교체됩니다. 이 작업은 취소할 수 없습니다.', + 'backup.restoreTip': '팁: 복원 전에 현재 상태의 백업을 만드세요.', + 'backup.restoreConfirm': '예, 복원', +}; +export default backup; diff --git a/shared/src/i18n/ko/budget.ts b/shared/src/i18n/ko/budget.ts new file mode 100644 index 00000000..27b17093 --- /dev/null +++ b/shared/src/i18n/ko/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': '예산', + 'budget.exportCsv': 'CSV 내보내기', + 'budget.emptyTitle': '아직 예산이 없습니다', + 'budget.emptyText': '카테고리와 항목을 만들어 여행 예산을 계획하세요', + 'budget.emptyPlaceholder': '카테고리 이름을 입력하세요...', + 'budget.createCategory': '카테고리 만들기', + 'budget.category': '카테고리', + 'budget.categoryName': '카테고리 이름', + 'budget.table.name': '이름', + 'budget.table.total': '합계', + 'budget.table.persons': '인원', + 'budget.table.days': '일수', + 'budget.table.perPerson': '1인당', + 'budget.table.perDay': '일당', + 'budget.table.perPersonDay': '1인/일', + 'budget.table.note': '메모', + 'budget.table.date': '날짜', + 'budget.newEntry': '새 항목', + 'budget.defaultEntry': '새 항목', + 'budget.defaultCategory': '새 카테고리', + 'budget.total': '합계', + 'budget.totalBudget': '총 예산', + 'budget.byCategory': '카테고리별', + 'budget.editTooltip': '클릭하여 편집', + 'budget.linkedToReservation': '예약에 연결됨 — 이름은 예약에서 편집하세요', + 'budget.confirm.deleteCategory': + '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?', + 'budget.deleteCategory': '카테고리 삭제', + 'budget.perPerson': '1인당', + 'budget.paid': '지불됨', + 'budget.open': '미결', + 'budget.noMembers': '배정된 멤버가 없습니다', + 'budget.settlement': '정산', + 'budget.settlementInfo': + '예산 항목의 멤버 아바타를 클릭하면 녹색으로 표시됩니다 — 해당 멤버가 지불했음을 의미합니다. 그러면 정산에서 누가 누구에게 얼마를 지불해야 하는지 보여줍니다.', + 'budget.netBalances': '순 잔액', + 'budget.categoriesLabel': '카테고리', +}; +export default budget; diff --git a/shared/src/i18n/ko/categories.ts b/shared/src/i18n/ko/categories.ts new file mode 100644 index 00000000..793149f7 --- /dev/null +++ b/shared/src/i18n/ko/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': '카테고리', + 'categories.subtitle': '장소 카테고리 관리', + 'categories.new': '새 카테고리', + 'categories.empty': '아직 카테고리가 없습니다', + 'categories.namePlaceholder': '카테고리 이름', + 'categories.icon': '아이콘', + 'categories.color': '색상', + 'categories.customColor': '사용자 지정 색상 선택', + 'categories.preview': '미리보기', + 'categories.defaultName': '카테고리', + 'categories.update': '업데이트', + 'categories.create': '생성', + 'categories.confirm.delete': + '카테고리를 삭제할까요? 이 카테고리의 장소는 삭제되지 않습니다.', + 'categories.toast.loadError': '카테고리 불러오기 실패', + 'categories.toast.nameRequired': '이름을 입력하세요', + 'categories.toast.updated': '카테고리가 업데이트되었습니다', + 'categories.toast.created': '카테고리가 생성되었습니다', + 'categories.toast.saveError': '저장 실패', + 'categories.toast.deleted': '카테고리가 삭제되었습니다', + 'categories.toast.deleteError': '삭제 실패', +}; +export default categories; diff --git a/shared/src/i18n/ko/collab.ts b/shared/src/i18n/ko/collab.ts new file mode 100644 index 00000000..c5d9ca44 --- /dev/null +++ b/shared/src/i18n/ko/collab.ts @@ -0,0 +1,73 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': '채팅', + 'collab.tabs.notes': '메모', + 'collab.tabs.polls': '투표', + 'collab.whatsNext.title': '다음 할 일', + 'collab.whatsNext.today': '오늘', + 'collab.whatsNext.tomorrow': '내일', + 'collab.whatsNext.empty': '예정된 활동이 없습니다', + 'collab.whatsNext.until': '까지', + 'collab.whatsNext.emptyHint': '시간이 있는 활동이 여기에 표시됩니다', + 'collab.chat.send': '전송', + 'collab.chat.placeholder': '메시지 입력...', + 'collab.chat.empty': '대화를 시작하세요', + 'collab.chat.emptyHint': '메시지는 모든 여행 멤버와 공유됩니다', + 'collab.chat.emptyDesc': '여행 그룹과 아이디어, 계획, 업데이트를 공유하세요', + 'collab.chat.today': '오늘', + 'collab.chat.yesterday': '어제', + 'collab.chat.deletedMessage': '메시지를 삭제했습니다', + 'collab.chat.reply': '답장', + 'collab.chat.loadMore': '이전 메시지 불러오기', + 'collab.chat.justNow': '방금 전', + 'collab.chat.minutesAgo': '{n}분 전', + 'collab.chat.hoursAgo': '{n}시간 전', + 'collab.notes.title': '메모', + 'collab.notes.new': '새 메모', + 'collab.notes.empty': '아직 메모가 없습니다', + 'collab.notes.emptyHint': '아이디어와 계획을 기록하세요', + 'collab.notes.all': '전체', + 'collab.notes.titlePlaceholder': '메모 제목', + 'collab.notes.contentPlaceholder': '내용을 입력하세요...', + 'collab.notes.categoryPlaceholder': '카테고리', + 'collab.notes.newCategory': '새 카테고리...', + 'collab.notes.category': '카테고리', + 'collab.notes.noCategory': '카테고리 없음', + 'collab.notes.color': '색상', + 'collab.notes.save': '저장', + 'collab.notes.cancel': '취소', + 'collab.notes.edit': '편집', + 'collab.notes.delete': '삭제', + 'collab.notes.pin': '고정', + 'collab.notes.unpin': '고정 해제', + 'collab.notes.daysAgo': '{n}일 전', + 'collab.notes.categorySettings': '카테고리 관리', + 'collab.notes.create': '생성', + 'collab.notes.website': '웹사이트', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': '파일 첨부', + 'collab.notes.noCategoriesYet': '아직 카테고리가 없습니다', + 'collab.notes.emptyDesc': '메모를 만들어 시작하세요', + 'collab.polls.title': '투표', + 'collab.polls.new': '새 투표', + 'collab.polls.empty': '아직 투표가 없습니다', + 'collab.polls.emptyHint': '그룹에 질문하고 함께 투표하세요', + 'collab.polls.question': '질문', + 'collab.polls.questionPlaceholder': '무엇을 할까요?', + 'collab.polls.addOption': '+ 옵션 추가', + 'collab.polls.optionPlaceholder': '옵션 {n}', + 'collab.polls.create': '투표 만들기', + 'collab.polls.close': '닫기', + 'collab.polls.closed': '종료됨', + 'collab.polls.votes': '{n}표', + 'collab.polls.vote': '{n}표', + 'collab.polls.multipleChoice': '복수 선택', + 'collab.polls.multiChoice': '복수 선택', + 'collab.polls.deadline': '마감일', + 'collab.polls.option': '옵션', + 'collab.polls.options': '옵션', + 'collab.polls.delete': '삭제', + 'collab.polls.closedSection': '종료됨', +}; +export default collab; diff --git a/shared/src/i18n/ko/common.ts b/shared/src/i18n/ko/common.ts new file mode 100644 index 00000000..d1259469 --- /dev/null +++ b/shared/src/i18n/ko/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': '저장', + 'common.showMore': '더 보기', + 'common.showLess': '접기', + 'common.cancel': '취소', + 'common.clear': '지우기', + 'common.delete': '삭제', + 'common.edit': '편집', + 'common.add': '추가', + 'common.loading': '로딩 중...', + 'common.import': '가져오기', + 'common.select': '선택', + 'common.selectAll': '전체 선택', + 'common.deselectAll': '전체 해제', + 'common.error': '오류', + 'common.unknownError': '알 수 없는 오류', + 'common.tooManyAttempts': + '시도 횟수가 너무 많습니다. 잠시 후 다시 시도해 주세요.', + 'common.back': '뒤로', + 'common.all': '전체', + 'common.close': '닫기', + 'common.open': '열기', + 'common.upload': '업로드', + 'common.search': '검색', + 'common.confirm': '확인', + 'common.ok': '확인', + 'common.yes': '예', + 'common.no': '아니오', + 'common.or': '또는', + 'common.none': '없음', + 'common.date': '날짜', + 'common.rename': '이름 변경', + 'common.discardChanges': '변경 사항 취소', + 'common.discard': '취소', + 'common.name': '이름', + 'common.email': '이메일', + 'common.password': '비밀번호', + 'common.saving': '저장 중...', + 'common.justNow': '방금 전', + 'common.hoursAgo': '{count}시간 전', + 'common.daysAgo': '{count}일 전', + 'common.saved': '저장됨', + 'common.update': '업데이트', + 'common.change': '변경', + 'common.uploading': '업로드 중…', + 'common.backToPlanning': '계획으로 돌아가기', + 'common.reset': '초기화', + 'common.expand': '펼치기', + 'common.collapse': '접기', + 'common.copy': '복사', + 'common.copied': '복사됨', +}; +export default common; diff --git a/shared/src/i18n/ko/dashboard.ts b/shared/src/i18n/ko/dashboard.ts new file mode 100644 index 00000000..54a48604 --- /dev/null +++ b/shared/src/i18n/ko/dashboard.ts @@ -0,0 +1,120 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': '내 여행', + 'dashboard.subtitle.loading': '여행 불러오는 중...', + 'dashboard.subtitle.trips': '{count}개 여행 ({archived}개 보관됨)', + 'dashboard.subtitle.empty': '첫 번째 여행을 시작하세요', + 'dashboard.subtitle.activeOne': '활성 여행 {count}개', + 'dashboard.subtitle.activeMany': '활성 여행 {count}개', + 'dashboard.subtitle.archivedSuffix': ' · {count}개 보관됨', + 'dashboard.newTrip': '새 여행', + 'dashboard.gridView': '격자 보기', + 'dashboard.listView': '목록 보기', + 'dashboard.currency': '통화', + 'dashboard.timezone': '시간대', + 'dashboard.localTime': '현지', + 'dashboard.timezoneCustomTitle': '사용자 지정 시간대', + 'dashboard.timezoneCustomLabelPlaceholder': '레이블 (선택)', + 'dashboard.timezoneCustomTzPlaceholder': '예: America/New_York', + 'dashboard.timezoneCustomAdd': '추가', + 'dashboard.timezoneCustomErrorEmpty': '시간대 식별자를 입력하세요', + 'dashboard.timezoneCustomErrorInvalid': + '잘못된 시간대입니다. Europe/Berlin 같은 형식을 사용하세요', + 'dashboard.timezoneCustomErrorDuplicate': '이미 추가됨', + 'dashboard.emptyTitle': '아직 여행이 없습니다', + 'dashboard.emptyText': '첫 번째 여행을 만들고 계획을 시작하세요!', + 'dashboard.emptyButton': '첫 번째 여행 만들기', + 'dashboard.nextTrip': '다음 여행', + 'dashboard.shared': '공유됨', + 'dashboard.sharedBy': '{name}이(가) 공유', + 'dashboard.days': '일', + 'dashboard.places': '장소', + 'dashboard.members': '동행자', + 'dashboard.archive': '보관', + 'dashboard.copyTrip': '복사', + 'dashboard.copySuffix': '사본', + 'dashboard.restore': '복원', + 'dashboard.archived': '보관됨', + 'dashboard.status.ongoing': '진행 중', + 'dashboard.status.today': '오늘', + 'dashboard.status.tomorrow': '내일', + 'dashboard.status.past': '지난 여행', + 'dashboard.status.daysLeft': '{count}일 남음', + 'dashboard.toast.loadError': '여행 불러오기 실패', + 'dashboard.toast.created': '여행이 생성되었습니다!', + 'dashboard.toast.createError': '여행 생성 실패', + 'dashboard.toast.updated': '여행이 업데이트되었습니다!', + 'dashboard.toast.updateError': '여행 업데이트 실패', + 'dashboard.toast.deleted': '여행이 삭제되었습니다', + 'dashboard.toast.deleteError': '여행 삭제 실패', + 'dashboard.toast.archived': '여행이 보관되었습니다', + 'dashboard.toast.archiveError': '여행 보관 실패', + 'dashboard.toast.restored': '여행이 복원되었습니다', + 'dashboard.toast.restoreError': '여행 복원 실패', + 'dashboard.toast.copied': '여행이 복사되었습니다!', + 'dashboard.toast.copyError': '여행 복사 실패', + 'dashboard.confirm.delete': + '여행 "{title}"을(를) 삭제할까요? 모든 장소와 계획이 영구 삭제됩니다.', + 'dashboard.confirm.copy.title': '이 여행을 복사할까요?', + 'dashboard.confirm.copy.willCopy': '복사될 항목', + 'dashboard.confirm.copy.will1': '일정, 장소 및 일별 배정', + 'dashboard.confirm.copy.will2': '숙박 및 예약', + 'dashboard.confirm.copy.will3': '예산 항목 및 카테고리 순서', + 'dashboard.confirm.copy.will4': '짐 목록 (체크 해제 상태)', + 'dashboard.confirm.copy.will5': '할 일 (미배정 및 미완료)', + 'dashboard.confirm.copy.will6': '일별 메모', + 'dashboard.confirm.copy.wontCopy': '복사되지 않는 항목', + 'dashboard.confirm.copy.wont1': '공동 작업자 및 멤버 배정', + 'dashboard.confirm.copy.wont2': '공동 작업 메모, 투표 및 메시지', + 'dashboard.confirm.copy.wont3': '파일 및 사진', + 'dashboard.confirm.copy.wont4': '공유 토큰', + 'dashboard.confirm.copy.confirm': '여행 복사', + 'dashboard.editTrip': '여행 편집', + 'dashboard.createTrip': '새 여행 만들기', + 'dashboard.tripTitle': '제목', + 'dashboard.tripTitlePlaceholder': '예: 일본에서의 여름', + 'dashboard.tripDescription': '설명', + 'dashboard.tripDescriptionPlaceholder': '이 여행에 대해 설명해 주세요', + 'dashboard.startDate': '시작일', + 'dashboard.endDate': '종료일', + 'dashboard.dayCount': '일수', + 'dashboard.dayCountHint': '여행 날짜가 설정되지 않은 경우 계획할 일수입니다.', + 'dashboard.noDateHint': + '날짜 미설정 — 기본 7일이 생성됩니다. 언제든지 변경할 수 있습니다.', + 'dashboard.coverImage': '커버 이미지', + 'dashboard.addCoverImage': '커버 이미지 추가 (또는 끌어다 놓기)', + 'dashboard.addMembers': '동행자', + 'dashboard.addMember': '멤버 추가', + 'dashboard.coverSaved': '커버 이미지가 저장되었습니다', + 'dashboard.coverUploadError': '업로드 실패', + 'dashboard.coverRemoveError': '삭제 실패', + 'dashboard.titleRequired': '제목을 입력하세요', + 'dashboard.endDateError': '종료일은 시작일 이후여야 합니다', + 'dashboard.greeting.morning': '좋은 아침이에요,', + 'dashboard.greeting.afternoon': '안녕하세요,', + 'dashboard.greeting.evening': '좋은 저녁이에요,', + 'dashboard.mobile.liveNow': '지금 라이브', + 'dashboard.mobile.tripProgress': '여행 진행도', + 'dashboard.mobile.daysLeft': '{count}일 남음', + 'dashboard.mobile.places': '장소', + 'dashboard.mobile.buddies': '동행자', + 'dashboard.mobile.newTrip': '새 여행', + 'dashboard.mobile.currency': '통화', + 'dashboard.mobile.timezone': '시간대', + 'dashboard.mobile.upcomingTrips': '예정된 여행', + 'dashboard.mobile.yourTrips': '내 여행', + 'dashboard.mobile.trips': '개 여행', + 'dashboard.mobile.starts': '시작', + 'dashboard.mobile.duration': '기간', + 'dashboard.mobile.day': '일', + 'dashboard.mobile.days': '일', + 'dashboard.mobile.ongoing': '진행 중', + 'dashboard.mobile.startsToday': '오늘 시작', + 'dashboard.mobile.tomorrow': '내일', + 'dashboard.mobile.inDays': '{count}일 후', + 'dashboard.mobile.inMonths': '{count}개월 후', + 'dashboard.mobile.completed': '완료됨', + 'dashboard.mobile.currencyConverter': '환율 계산기', +}; +export default dashboard; diff --git a/shared/src/i18n/ko/day.ts b/shared/src/i18n/ko/day.ts new file mode 100644 index 00000000..b733f9dd --- /dev/null +++ b/shared/src/i18n/ko/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': '강수 확률', + 'day.precipitation': '강수량', + 'day.wind': '바람', + 'day.sunrise': '일출', + 'day.sunset': '일몰', + 'day.hourlyForecast': '시간별 예보', + 'day.climateHint': + '역사적 평균값 — 이 날짜로부터 16일 이내의 실제 예보를 사용할 수 있습니다.', + 'day.noWeather': '날씨 데이터가 없습니다. 좌표가 있는 장소를 추가하세요.', + 'day.overview': '일별 개요', + 'day.accommodation': '숙박', + 'day.addAccommodation': '숙박 추가', + 'day.hotelDayRange': '적용할 날', + 'day.noPlacesForHotel': '먼저 여행에 장소를 추가하세요', + 'day.allDays': '전체', + 'day.checkIn': '체크인', + 'day.checkInUntil': '까지', + 'day.checkOut': '체크아웃', + 'day.confirmation': '확인', + 'day.editAccommodation': '숙박 편집', + 'day.reservations': '예약', +}; +export default day; diff --git a/shared/src/i18n/ko/dayplan.ts b/shared/src/i18n/ko/dayplan.ts new file mode 100644 index 00000000..20741475 --- /dev/null +++ b/shared/src/i18n/ko/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': '캘린더 내보내기 (ICS)', + 'dayplan.emptyDay': '이 날에 계획된 장소가 없습니다', + 'dayplan.cannotReorderTransport': + '고정된 시간이 있는 예약은 순서를 변경할 수 없습니다', + 'dayplan.confirmRemoveTimeTitle': '시간을 제거할까요?', + 'dayplan.confirmRemoveTimeBody': + '이 장소에 고정된 시간 ({time})이 있습니다. 이동하면 시간이 제거되고 자유 정렬이 허용됩니다.', + 'dayplan.confirmRemoveTimeAction': '시간 제거 및 이동', + 'dayplan.cannotDropOnTimed': '시간이 고정된 항목 사이에 배치할 수 없습니다', + 'dayplan.cannotBreakChronology': + '이 작업은 시간 고정 항목과 예약의 시간 순서를 깨뜨립니다', + 'dayplan.addNote': '메모 추가', + 'dayplan.expandAll': '모든 날 펼치기', + 'dayplan.collapseAll': '모든 날 접기', + 'dayplan.editNote': '메모 편집', + 'dayplan.noteAdd': '메모 추가', + 'dayplan.noteEdit': '메모 편집', + 'dayplan.noteTitle': '메모', + 'dayplan.noteSubtitle': '일별 메모', + 'dayplan.totalCost': '총 비용', + 'dayplan.days': '일', + 'dayplan.dayN': '{n}일차', + 'dayplan.calculating': '계산 중...', + 'dayplan.route': '경로', + 'dayplan.optimize': '최적화', + 'dayplan.optimized': '경로가 최적화되었습니다', + 'dayplan.routeError': '경로 계산 실패', + 'dayplan.toast.needTwoPlaces': + '경로 최적화에는 최소 두 개의 장소가 필요합니다', + 'dayplan.toast.routeOptimized': '경로가 최적화되었습니다', + 'dayplan.toast.noGeoPlaces': '경로 계산을 위한 좌표가 있는 장소가 없습니다', + 'dayplan.confirmed': '확정됨', + 'dayplan.pendingRes': '대기 중', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': '일별 계획을 PDF로 내보내기', + 'dayplan.pdfError': 'PDF 내보내기 실패', + 'dayplan.mobile.addPlace': '장소 추가', + 'dayplan.mobile.searchPlaces': '장소 검색...', + 'dayplan.mobile.allAssigned': '모든 장소가 배정되었습니다', + 'dayplan.mobile.noMatch': '일치 없음', + 'dayplan.mobile.createNew': '새 장소 만들기', +}; +export default dayplan; diff --git a/shared/src/i18n/ko/externalNotifications.ts b/shared/src/i18n/ko/externalNotifications.ts new file mode 100644 index 00000000..fa5d7067 --- /dev/null +++ b/shared/src/i18n/ko/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const ko: NotificationLocale = { + email: { + footer: 'TREK에서 알림을 활성화했기 때문에 이 이메일을 받으셨습니다.', + manage: '설정에서 환경설정 관리', + madeWith: 'Made with', + openTrek: 'TREK 열기', + }, + events: { + trip_invite: (p) => ({ + title: `"${p.trip}" 여행 초대`, + body: `${p.actor}이(가) ${p.invitee || '멤버'}를 "${p.trip}" 여행에 초대했습니다.`, + }), + booking_change: (p) => ({ + title: `새 예약: ${p.booking}`, + body: `${p.actor}이(가) "${p.trip}"에 "${p.booking}" (${p.type}) 예약을 추가했습니다.`, + }), + trip_reminder: (p) => ({ + title: `여행 알림: ${p.trip}`, + body: `"${p.trip}" 여행이 곧 시작됩니다!`, + }), + todo_due: (p) => ({ + title: `할 일 마감: ${p.todo}`, + body: `"${p.trip}"의 "${p.todo}"은(는) ${p.due}에 마감됩니다.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion 초대', + body: `${p.actor}이(가) 휴가 계획을 합치도록 초대했습니다. TREK을 열어 수락하거나 거절하세요.`, + }), + photos_shared: (p) => ({ + title: `${p.count}장의 사진이 공유되었습니다`, + body: `${p.actor}이(가) "${p.trip}"에서 ${p.count}장의 사진을 공유했습니다.`, + }), + collab_message: (p) => ({ + title: `"${p.trip}"의 새 메시지`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `짐 꾸리기: ${p.category}`, + body: `${p.actor}이(가) "${p.trip}"의 "${p.category}" 카테고리에 당신을 할당했습니다.`, + }), + version_available: (p) => ({ + title: '새 TREK 버전 사용 가능', + body: `TREK ${p.version}을 사용할 수 있습니다. 관리자 패널에서 업데이트하세요.`, + }), + synology_session_cleared: () => ({ + title: 'Synology 세션이 초기화되었습니다', + body: 'Synology 계정 또는 URL이 변경되었습니다. Synology Photos에서 로그아웃되었습니다.', + }), + }, + passwordReset: { + subject: '비밀번호 재설정', + greeting: '안녕하세요', + body: 'TREK 계정 비밀번호 재설정 요청을 받았습니다. 아래 버튼을 클릭하여 새 비밀번호를 설정하세요.', + ctaIntro: '비밀번호 재설정', + expiry: '이 링크는 60분 후에 만료됩니다.', + ignore: + '본인이 요청하지 않으셨다면 이 이메일을 무시하셔도 됩니다 — 비밀번호는 변경되지 않습니다.', + }, +}; + +export default ko; diff --git a/shared/src/i18n/ko/files.ts b/shared/src/i18n/ko/files.ts new file mode 100644 index 00000000..540e9e6b --- /dev/null +++ b/shared/src/i18n/ko/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': '파일', + 'files.pageTitle': '파일 및 서류', + 'files.subtitle': '{trip}의 파일 {count}개', + 'files.download': '다운로드', + 'files.openError': '파일을 열 수 없습니다', + 'files.downloadPdf': 'PDF 다운로드', + 'files.count': '파일 {count}개', + 'files.countSingular': '파일 1개', + 'files.uploaded': '{count}개 업로드됨', + 'files.uploadError': '업로드 실패', + 'files.dropzone': '여기에 파일을 놓으세요', + 'files.dropzoneHint': '또는 클릭하여 탐색', + 'files.allowedTypes': + '이미지, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · 최대 50 MB', + 'files.uploading': '업로드 중...', + 'files.filterAll': '전체', + 'files.filterPdf': 'PDF', + 'files.filterImages': '이미지', + 'files.filterDocs': '문서', + 'files.filterCollab': 'Collab 메모', + 'files.sourceCollab': 'Collab 메모에서', + 'files.empty': '아직 파일이 없습니다', + 'files.emptyHint': '파일을 업로드하여 여행에 첨부하세요', + 'files.openTab': '새 탭에서 열기', + 'files.confirm.delete': '이 파일을 삭제할까요?', + 'files.toast.deleted': '파일이 삭제되었습니다', + 'files.toast.deleteError': '파일 삭제 실패', + 'files.sourcePlan': '일별 계획', + 'files.sourceBooking': '예약', + 'files.sourceTransport': '교통', + 'files.attach': '첨부', + 'files.pasteHint': '클립보드에서 이미지를 붙여넣을 수도 있습니다 (Ctrl+V)', + 'files.trash': '휴지통', + 'files.trashEmpty': '휴지통이 비어 있습니다', + 'files.emptyTrash': '휴지통 비우기', + 'files.restore': '복원', + 'files.star': '즐겨찾기', + 'files.unstar': '즐겨찾기 해제', + 'files.assign': '배정', + 'files.assignTitle': '파일 배정', + 'files.assignPlace': '장소', + 'files.assignBooking': '예약', + 'files.assignTransport': '교통', + 'files.unassigned': '미배정', + 'files.unlink': '연결 해제', + 'files.toast.trashed': '휴지통으로 이동됨', + 'files.toast.restored': '파일이 복원되었습니다', + 'files.toast.trashEmptied': '휴지통이 비워졌습니다', + 'files.toast.assigned': '파일이 배정되었습니다', + 'files.toast.assignError': '배정 실패', + 'files.toast.restoreError': '복원 실패', + 'files.confirm.permanentDelete': + '이 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.', + 'files.confirm.emptyTrash': + '휴지통의 모든 파일을 영구 삭제할까요? 이 작업은 취소할 수 없습니다.', + 'files.noteLabel': '메모', + 'files.notePlaceholder': '메모 추가...', +}; +export default files; diff --git a/shared/src/i18n/ko/index.ts b/shared/src/i18n/ko/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/ko/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/ko/inspector.ts b/shared/src/i18n/ko/inspector.ts new file mode 100644 index 00000000..ba127617 --- /dev/null +++ b/shared/src/i18n/ko/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': '영업 중', + 'inspector.closed': '영업 종료', + 'inspector.openingHours': '영업 시간', + 'inspector.showHours': '영업 시간 보기', + 'inspector.files': '파일', + 'inspector.filesCount': '{count}개 파일', + 'inspector.remove': '제거', + 'inspector.removeFromDay': '날에서 제거', + 'inspector.addToDay': '날에 추가', + 'inspector.confirmedRes': '확정된 예약', + 'inspector.pendingRes': '대기 중인 예약', + 'inspector.google': 'Google Maps에서 열기', + 'inspector.website': '웹사이트 열기', + 'inspector.addRes': '예약', + 'inspector.editRes': '예약 편집', + 'inspector.participants': '참가자', + 'inspector.trackStats': '트랙 통계', +}; +export default inspector; diff --git a/shared/src/i18n/ko/journey.ts b/shared/src/i18n/ko/journey.ts new file mode 100644 index 00000000..b17d7f97 --- /dev/null +++ b/shared/src/i18n/ko/journey.ts @@ -0,0 +1,244 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Journey 검색…', + 'journey.search.noResults': '"{query}"와(과) 일치하는 Journey가 없습니다', + 'journey.title': 'Journey', + 'journey.subtitle': '여행을 실시간으로 기록하세요', + 'journey.new': '새 Journey', + 'journey.create': '만들기', + 'journey.titlePlaceholder': '어디로 가시나요?', + 'journey.empty': '아직 Journey가 없습니다', + 'journey.emptyHint': '다음 여행을 기록하기 시작하세요', + 'journey.deleted': 'Journey가 삭제되었습니다', + 'journey.createError': 'Journey를 만들 수 없습니다', + 'journey.deleteError': 'Journey를 삭제할 수 없습니다', + 'journey.deleteConfirmTitle': '삭제', + 'journey.deleteConfirmMessage': + '"{title}"을(를) 삭제할까요? 이 작업은 취소할 수 없습니다.', + 'journey.deleteConfirmGeneric': '정말로 삭제할까요?', + 'journey.notFound': 'Journey를 찾을 수 없습니다', + 'journey.photos': '사진', + 'journey.timelineEmpty': '아직 정류장이 없습니다', + 'journey.timelineEmptyHint': '체크인을 추가하거나 일기를 작성하여 시작하세요', + 'journey.status.draft': '초안', + 'journey.status.active': '활성', + 'journey.status.completed': '완료됨', + 'journey.status.upcoming': '예정됨', + 'journey.status.archived': '보관됨', + 'journey.checkin.add': '체크인', + 'journey.checkin.namePlaceholder': '위치 이름', + 'journey.checkin.notesPlaceholder': '메모 (선택)', + 'journey.checkin.save': '저장', + 'journey.checkin.error': '체크인을 저장할 수 없습니다', + 'journey.entry.add': '일기', + 'journey.entry.edit': '항목 편집', + 'journey.entry.titlePlaceholder': '제목 (선택)', + 'journey.entry.bodyPlaceholder': '오늘 무슨 일이 있었나요?', + 'journey.entry.save': '저장', + 'journey.entry.error': '항목을 저장할 수 없습니다', + 'journey.photo.add': '사진', + 'journey.photo.uploadError': '업로드 실패', + 'journey.share.share': '공유', + 'journey.share.public': '공개', + 'journey.share.linkCopied': '공개 링크가 복사되었습니다', + 'journey.share.disabled': '공개 공유 비활성화됨', + 'journey.editor.titlePlaceholder': '이 순간에 이름을 붙여주세요...', + 'journey.editor.bodyPlaceholder': '오늘의 이야기를 들려주세요...', + 'journey.editor.placePlaceholder': '위치 (선택)', + 'journey.editor.tagsPlaceholder': + '태그: 숨겨진 명소, 최고의 식사, 다시 방문...', + 'journey.visibility.private': '비공개', + 'journey.visibility.shared': '공유됨', + 'journey.visibility.public': '공개', + 'journey.emptyState.title': '여기서 이야기가 시작됩니다', + 'journey.emptyState.subtitle': + '장소에 체크인하거나 첫 번째 일기 항목을 작성하세요', + 'journey.frontpage.subtitle': '여행을 영원히 잊지 못할 이야기로 만드세요', + 'journey.frontpage.createJourney': 'Journey 만들기', + 'journey.frontpage.activeJourney': '활성 Journey', + 'journey.frontpage.allJourneys': '모든 Journey', + 'journey.frontpage.journeys': '개 Journey', + 'journey.frontpage.createNew': '새 Journey 만들기', + 'journey.frontpage.createNewSub': + '여행을 선택하고, 이야기를 쓰고, 모험을 공유하세요', + 'journey.frontpage.live': '라이브', + 'journey.frontpage.synced': '동기화됨', + 'journey.frontpage.continueWriting': '계속 쓰기', + 'journey.frontpage.updated': '{time}에 업데이트됨', + 'journey.frontpage.suggestionLabel': '여행이 방금 종료됨', + 'journey.frontpage.suggestionText': + '{title}을(를) Journey로 만들어보세요', + 'journey.frontpage.dismiss': '닫기', + 'journey.frontpage.journeyName': 'Journey 이름', + 'journey.frontpage.namePlaceholder': '예: 동남아시아 2026', + 'journey.frontpage.selectTrips': '여행 선택', + 'journey.frontpage.tripsSelected': '개 여행 선택됨', + 'journey.frontpage.trips': '개 여행', + 'journey.frontpage.placesImported': '개 장소가 가져와집니다', + 'journey.frontpage.places': '개 장소', + 'journey.detail.backToJourney': 'Journey로 돌아가기', + 'journey.detail.syncedWithTrips': '여행과 동기화됨', + 'journey.detail.addEntry': '항목 추가', + 'journey.detail.newEntry': '새 항목', + 'journey.detail.editEntry': '항목 편집', + 'journey.detail.noEntries': '아직 항목이 없습니다', + 'journey.detail.noEntriesHint': + '여행을 추가하여 스켈레톤 항목으로 시작하세요', + 'journey.detail.noPhotos': '아직 사진이 없습니다', + 'journey.detail.noPhotosHint': + '항목에 사진을 업로드하거나 Immich/Synology 라이브러리를 탐색하세요', + 'journey.detail.journeyTab': 'Journey', + 'journey.detail.journeyStats': 'Journey 통계', + 'journey.detail.syncedTrips': '동기화된 여행', + 'journey.detail.noTripsLinked': '아직 연결된 여행이 없습니다', + 'journey.detail.contributors': '기여자', + 'journey.detail.readMore': '더 읽기', + 'journey.detail.prosCons': '장단점', + 'journey.detail.photos': '장', + 'journey.detail.day': '{number}일차', + 'journey.detail.places': '개 장소', + 'journey.stats.days': '일', + 'journey.stats.cities': '도시', + 'journey.stats.entries': '항목', + 'journey.stats.photos': '사진', + 'journey.stats.places': '장소', + 'journey.skeletons.show': '제안 보기', + 'journey.skeletons.hide': '제안 숨기기', + 'journey.verdict.lovedIt': '정말 좋았어요', + 'journey.verdict.couldBeBetter': '더 좋을 수 있었어요', + 'journey.synced.places': '개 장소', + 'journey.synced.synced': '동기화됨', + 'journey.editor.discardChangesConfirm': + '저장되지 않은 변경 사항이 있습니다. 취소할까요?', + 'journey.editor.uploadPhotos': '사진 업로드', + 'journey.editor.uploading': '업로드 중...', + 'journey.editor.fromGallery': '갤러리에서', + 'journey.editor.allPhotosAdded': '모든 사진이 이미 추가되었습니다', + 'journey.editor.writeStory': '이야기를 써주세요...', + 'journey.editor.prosCons': '장단점', + 'journey.editor.pros': '장점', + 'journey.editor.cons': '단점', + 'journey.editor.proPlaceholder': '좋은 점...', + 'journey.editor.conPlaceholder': '아쉬운 점...', + 'journey.editor.addAnother': '하나 더 추가', + 'journey.editor.date': '날짜', + 'journey.editor.location': '위치', + 'journey.editor.searchLocation': '위치 검색...', + 'journey.editor.mood': '기분', + 'journey.editor.weather': '날씨', + 'journey.editor.photoFirst': '1번째', + 'journey.editor.makeFirst': '1번째로 설정', + 'journey.editor.searching': '검색 중...', + 'journey.mood.amazing': '최고!', + 'journey.mood.good': '좋음', + 'journey.mood.neutral': '보통', + 'journey.mood.rough': '힘들었음', + 'journey.weather.sunny': '맑음', + 'journey.weather.partly': '구름 조금', + 'journey.weather.cloudy': '흐림', + 'journey.weather.rainy': '비', + 'journey.weather.stormy': '폭풍', + 'journey.weather.cold': '눈', + 'journey.trips.linkTrip': '여행 연결', + 'journey.trips.searchTrip': '여행 검색', + 'journey.trips.searchPlaceholder': '여행 이름 또는 목적지...', + 'journey.trips.noTripsAvailable': '사용 가능한 여행이 없습니다', + 'journey.trips.link': '연결', + 'journey.trips.tripLinked': '여행이 연결되었습니다', + 'journey.trips.linkFailed': '여행 연결 실패', + 'journey.trips.addTrip': '여행 추가', + 'journey.trips.unlinkTrip': '여행 연결 해제', + 'journey.trips.unlinkMessage': + '"{title}"을(를) 연결 해제할까요? 이 여행의 동기화된 모든 항목과 사진이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.', + 'journey.trips.unlink': '연결 해제', + 'journey.trips.tripUnlinked': '여행 연결이 해제되었습니다', + 'journey.trips.unlinkFailed': '여행 연결 해제 실패', + 'journey.trips.noTripsLinkedSettings': '연결된 여행이 없습니다', + 'journey.contributors.invite': '기여자 초대', + 'journey.contributors.searchUser': '사용자 검색', + 'journey.contributors.searchPlaceholder': '사용자 이름 또는 이메일...', + 'journey.contributors.noUsers': '사용자를 찾을 수 없습니다', + 'journey.contributors.role': '역할', + 'journey.contributors.added': '기여자가 추가되었습니다', + 'journey.contributors.addFailed': '기여자 추가 실패', + 'journey.contributors.remove': '기여자 제거', + 'journey.contributors.removeConfirm': + '{username}을(를) 이 Journey에서 제거할까요?', + 'journey.contributors.removed': '기여자가 제거되었습니다', + 'journey.contributors.removeFailed': '기여자 제거 실패', + 'journey.share.publicShare': '공개 공유', + 'journey.share.createLink': '공유 링크 만들기', + 'journey.share.linkCreated': '공유 링크가 생성되었습니다', + 'journey.share.createFailed': '링크 생성 실패', + 'journey.share.copy': '복사', + 'journey.share.copied': '복사됨!', + 'journey.share.timeline': '타임라인', + 'journey.share.gallery': '갤러리', + 'journey.share.map': '지도', + 'journey.share.removeLink': '공유 링크 제거', + 'journey.share.linkDeleted': '공유 링크가 삭제되었습니다', + 'journey.share.deleteFailed': '삭제 실패', + 'journey.share.updateFailed': '업데이트 실패', + 'journey.invite.role': '역할', + 'journey.invite.viewer': '뷰어', + 'journey.invite.editor': '편집자', + 'journey.invite.invite': '초대', + 'journey.invite.inviting': '초대 중...', + 'journey.settings.title': 'Journey 설정', + 'journey.settings.coverImage': '커버 이미지', + 'journey.settings.changeCover': '커버 변경', + 'journey.settings.addCover': '커버 이미지 추가', + 'journey.settings.name': '이름', + 'journey.settings.subtitle': '부제목', + 'journey.settings.subtitlePlaceholder': '예: 태국, 베트남 & 캄보디아', + 'journey.settings.endJourney': 'Journey 보관', + 'journey.settings.reopenJourney': 'Journey 복원', + 'journey.settings.archived': 'Journey가 보관되었습니다', + 'journey.settings.reopened': 'Journey가 복원되었습니다', + 'journey.settings.endDescription': + '라이브 배지를 숨깁니다. 언제든지 다시 열 수 있습니다.', + 'journey.settings.delete': '삭제', + 'journey.settings.deleteJourney': 'Journey 삭제', + 'journey.settings.deleteMessage': + '"{title}"을(를) 삭제할까요? 모든 항목과 사진이 삭제됩니다.', + 'journey.settings.saved': '설정이 저장되었습니다', + 'journey.settings.saveFailed': '저장 실패', + 'journey.settings.coverUpdated': '커버가 업데이트되었습니다', + 'journey.settings.coverFailed': '업로드 실패', + 'journey.settings.failedToDelete': '삭제 실패', + 'journey.entries.deleteTitle': '항목 삭제', + 'journey.photosUploaded': '{count}장 사진이 업로드되었습니다', + 'journey.photosAdded': '{count}장 사진이 추가되었습니다', + 'journey.public.notFound': '찾을 수 없습니다', + 'journey.public.notFoundMessage': + '이 Journey가 존재하지 않거나 링크가 만료되었습니다.', + 'journey.public.readOnly': '읽기 전용 · 공개 Journey', + 'journey.public.tagline': '여행 기록 및 탐험 키트', + 'journey.public.sharedVia': '공유 경로', + 'journey.public.madeWith': '으로 만들어짐', + 'journey.pdf.journeyBook': 'Journey 책', + 'journey.pdf.madeWith': 'TREK으로 만들어짐', + 'journey.pdf.day': '일차', + 'journey.pdf.theEnd': '끝', + 'journey.pdf.saveAsPdf': 'PDF로 저장', + 'journey.pdf.pages': '페이지', + 'journey.picker.tripPeriod': '여행 기간', + 'journey.picker.dateRange': '날짜 범위', + 'journey.picker.allPhotos': '모든 사진', + 'journey.picker.albums': '앨범', + 'journey.picker.selected': '선택됨', + 'journey.picker.addTo': '추가', + 'journey.picker.newGallery': '새 갤러리', + 'journey.picker.selectAll': '전체 선택', + 'journey.picker.deselectAll': '전체 해제', + 'journey.picker.noAlbums': '앨범을 찾을 수 없습니다', + 'journey.picker.selectDate': '날짜 선택', + 'journey.picker.search': '검색', + 'journey.editor.uploadingProgress': '업로드 중 {done}/{total}…', + 'journey.editor.uploadFailed': '사진 업로드 실패', + 'journey.editor.uploadPartialFailed': + '{total}개 중 {failed}개의 사진을 업로드하지 못했습니다 — 다시 저장하여 재시도하세요', + 'journey.photosUploadFailed': '일부 사진을 업로드하지 못했습니다', +}; +export default journey; diff --git a/shared/src/i18n/ko/login.ts b/shared/src/i18n/ko/login.ts new file mode 100644 index 00000000..39436fcc --- /dev/null +++ b/shared/src/i18n/ko/login.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': '로그인 실패. 자격 증명을 확인하세요.', + 'login.tagline': '나의 여행.\n나의 계획.', + 'login.description': + '인터랙티브 지도, 예산, 실시간 동기화로 함께 여행을 계획하세요.', + 'login.features.maps': '인터랙티브 지도', + 'login.features.mapsDesc': 'Google Places, 경로 및 클러스터링', + 'login.features.realtime': '실시간 동기화', + 'login.features.realtimeDesc': 'WebSocket으로 함께 계획', + 'login.features.budget': '예산 추적', + 'login.features.budgetDesc': '카테고리, 차트 및 1인당 비용', + 'login.features.collab': '협업', + 'login.features.collabDesc': '공유 여행으로 다중 사용자 지원', + 'login.features.packing': '짐 목록', + 'login.features.packingDesc': '카테고리, 진행 상황 및 제안', + 'login.features.bookings': '예약', + 'login.features.bookingsDesc': '항공, 호텔, 레스토랑 등', + 'login.features.files': '문서', + 'login.features.filesDesc': '문서 업로드 및 관리', + 'login.features.routes': '스마트 경로', + 'login.features.routesDesc': '자동 최적화 및 Google Maps 내보내기', + 'login.selfHosted': '자체 호스팅 · 오픈 소스 · 내 데이터는 내 것', + 'login.title': '로그인', + 'login.subtitle': '다시 오신 것을 환영합니다', + 'login.signingIn': '로그인 중…', + 'login.signIn': '로그인', + 'login.createAdmin': '관리자 계정 만들기', + 'login.createAdminHint': 'TREK의 첫 번째 관리자 계정을 설정하세요.', + 'login.setNewPassword': '새 비밀번호 설정', + 'login.setNewPasswordHint': '계속하기 전에 비밀번호를 변경해야 합니다.', + 'login.createAccount': '계정 만들기', + 'login.createAccountHint': '새 계정을 등록하세요.', + 'login.creating': '생성 중…', + 'login.noAccount': '계정이 없으신가요?', + 'login.hasAccount': '이미 계정이 있으신가요?', + 'login.register': '회원가입', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': '사용자 이름', + 'login.oidc.registrationDisabled': + '회원가입이 비활성화되어 있습니다. 관리자에게 문의하세요.', + 'login.oidc.noEmail': '공급자로부터 이메일을 받지 못했습니다.', + 'login.oidc.tokenFailed': '인증 실패.', + 'login.oidc.invalidState': '유효하지 않은 세션입니다. 다시 시도하세요.', + 'login.demoFailed': '데모 로그인 실패', + 'login.oidcSignIn': '{name}으로 로그인', + 'login.oidcOnly': + '비밀번호 인증이 비활성화되었습니다. SSO 공급자로 로그인하세요.', + 'login.oidcLoggedOut': '로그아웃되었습니다. SSO 공급자로 다시 로그인하세요.', + 'login.demoHint': '데모 체험 — 회원가입 불필요', + 'login.mfaTitle': '2단계 인증', + 'login.mfaSubtitle': '인증 앱의 6자리 코드를 입력하세요.', + 'login.mfaCodeLabel': '인증 코드', + 'login.mfaCodeRequired': '인증 앱의 코드를 입력하세요.', + 'login.mfaHint': + 'Google Authenticator, Authy 또는 다른 TOTP 앱을 열어주세요.', + 'login.mfaBack': '← 로그인으로 돌아가기', + 'login.mfaVerify': '인증', + 'login.invalidInviteLink': '유효하지 않거나 만료된 초대 링크입니다', + 'login.oidcFailed': 'OIDC 로그인 실패', + 'login.usernameRequired': '사용자 이름을 입력하세요', + 'login.passwordMinLength': '비밀번호는 최소 8자 이상이어야 합니다', + 'login.forgotPassword': '비밀번호를 잊으셨나요?', + 'login.forgotPasswordTitle': '비밀번호 재설정', + 'login.forgotPasswordBody': + '가입 시 사용한 이메일 주소를 입력하세요. 계정이 존재하면 재설정 링크를 보내드립니다.', + 'login.forgotPasswordSubmit': '재설정 링크 전송', + 'login.forgotPasswordSentTitle': '이메일을 확인하세요', + 'login.forgotPasswordSentBody': + '해당 이메일로 계정이 있다면 재설정 링크가 전송 중입니다. 링크는 60분 후 만료됩니다.', + 'login.forgotPasswordSmtpHintOff': + '알림: 관리자가 SMTP를 설정하지 않아 재설정 링크가 이메일 대신 서버 콘솔에 기록됩니다.', + 'login.backToLogin': '로그인으로 돌아가기', + 'login.newPassword': '새 비밀번호', + 'login.confirmPassword': '새 비밀번호 확인', + 'login.passwordsDontMatch': '비밀번호가 일치하지 않습니다', + 'login.mfaCode': '2FA 코드', + 'login.resetPasswordTitle': '새 비밀번호 설정', + 'login.resetPasswordBody': + '이전에 사용하지 않은 강력한 비밀번호를 선택하세요. 최소 8자.', + 'login.resetPasswordMfaBody': + '재설정을 완료하려면 2FA 코드 또는 백업 코드를 입력하세요.', + 'login.resetPasswordSubmit': '비밀번호 재설정', + 'login.resetPasswordVerify': '인증 및 재설정', + 'login.resetPasswordSuccessTitle': '비밀번호가 업데이트되었습니다', + 'login.resetPasswordSuccessBody': '새 비밀번호로 로그인할 수 있습니다.', + 'login.resetPasswordInvalidLink': '유효하지 않은 재설정 링크', + 'login.resetPasswordInvalidLinkBody': + '이 링크가 없거나 손상되었습니다. 새 링크를 요청하세요.', + 'login.resetPasswordFailed': '재설정 실패. 링크가 만료되었을 수 있습니다.', +}; +export default login; diff --git a/shared/src/i18n/ko/map.ts b/shared/src/i18n/ko/map.ts new file mode 100644 index 00000000..90186cf6 --- /dev/null +++ b/shared/src/i18n/ko/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': '연결', + 'map.showConnections': '예약 경로 표시', + 'map.hideConnections': '예약 경로 숨기기', +}; +export default map; diff --git a/shared/src/i18n/ko/members.ts b/shared/src/i18n/ko/members.ts new file mode 100644 index 00000000..58243541 --- /dev/null +++ b/shared/src/i18n/ko/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': '여행 공유', + 'members.inviteUser': '사용자 초대', + 'members.selectUser': '사용자 선택…', + 'members.invite': '초대', + 'members.allHaveAccess': '모든 사용자가 이미 접근 권한을 가지고 있습니다.', + 'members.access': '접근', + 'members.person': '명', + 'members.persons': '명', + 'members.you': '나', + 'members.owner': '소유자', + 'members.leaveTrip': '여행 떠나기', + 'members.removeAccess': '접근 권한 제거', + 'members.confirmLeave': '여행을 떠날까요? 접근 권한을 잃게 됩니다.', + 'members.confirmRemove': '이 사용자의 접근 권한을 제거할까요?', + 'members.loadError': '멤버 불러오기 실패', + 'members.added': '추가됨', + 'members.addError': '추가 실패', + 'members.removed': '멤버가 제거되었습니다', + 'members.removeError': '제거 실패', +}; +export default members; diff --git a/shared/src/i18n/ko/memories.ts b/shared/src/i18n/ko/memories.ts new file mode 100644 index 00000000..874b5dff --- /dev/null +++ b/shared/src/i18n/ko/memories.ts @@ -0,0 +1,80 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': '사진', + 'memories.notConnected': '{provider_name}이(가) 연결되지 않았습니다', + 'memories.notConnectedHint': + '이 여행에 사진을 추가하려면 설정에서 {provider_name} 인스턴스를 연결하세요.', + 'memories.notConnectedMultipleHint': + '이 여행에 사진을 추가하려면 설정에서 다음 사진 공급자 중 하나를 연결하세요: {provider_names}', + 'memories.noDates': '사진을 불러오려면 여행에 날짜를 추가하세요.', + 'memories.noPhotos': '사진을 찾을 수 없습니다', + 'memories.noPhotosHint': + '{provider_name}에서 이 여행의 날짜 범위에 해당하는 사진을 찾을 수 없습니다.', + 'memories.photosFound': '장', + 'memories.fromOthers': '다른 사람으로부터', + 'memories.sharePhotos': '사진 공유', + 'memories.sharing': '공유', + 'memories.reviewTitle': '사진 검토', + 'memories.reviewHint': '사진을 클릭하여 공유에서 제외하세요.', + 'memories.shareCount': '사진 {count}장 공유', + 'memories.providerUrl': '서버 URL', + 'memories.providerApiKey': 'API 키', + 'memories.providerUsername': '사용자 이름', + 'memories.providerPassword': '비밀번호', + 'memories.providerOTP': 'MFA 코드 (활성화된 경우)', + 'memories.skipSSLVerification': 'SSL 인증서 확인 건너뛰기', + 'memories.immichAutoUpload': '업로드 시 Journey 사진을 Immich에 미러링', + 'memories.providerUrlHintSynology': + 'URL에 Photos 앱 경로를 포함하세요. 예: https://nas:5001/photo', + 'memories.testConnection': '연결 테스트', + 'memories.testShort': '테스트', + 'memories.testFirst': '먼저 연결을 테스트하세요', + 'memories.connected': '연결됨', + 'memories.disconnected': '연결되지 않음', + 'memories.connectionSuccess': '{provider_name}에 연결되었습니다', + 'memories.connectionError': '{provider_name}에 연결할 수 없습니다', + 'memories.saved': '{provider_name} 설정이 저장되었습니다', + 'memories.providerDisconnectedBanner': + '{provider_name} 연결이 끊어졌습니다. 사진을 보려면 설정에서 다시 연결하세요.', + 'memories.saveError': '{provider_name} 설정을 저장할 수 없습니다', + 'memories.addPhotos': '사진 추가', + 'memories.linkAlbum': '앨범 연결', + 'memories.selectAlbum': '{provider_name} 앨범 선택', + 'memories.selectAlbumMultiple': '앨범 선택', + 'memories.noAlbums': '앨범을 찾을 수 없습니다', + 'memories.syncAlbum': '앨범 동기화', + 'memories.unlinkAlbum': '앨범 연결 해제', + 'memories.photos': '장', + 'memories.selectPhotos': '{provider_name}에서 사진 선택', + 'memories.selectPhotosMultiple': '사진 선택', + 'memories.selectHint': '사진을 탭하여 선택하세요.', + 'memories.selected': '선택됨', + 'memories.addSelected': '{count}장 추가', + 'memories.alreadyAdded': '추가됨', + 'memories.private': '비공개', + 'memories.stopSharing': '공유 중지', + 'memories.oldest': '오래된 것 먼저', + 'memories.newest': '최신 것 먼저', + 'memories.allLocations': '모든 위치', + 'memories.tripDates': '여행 날짜', + 'memories.allPhotos': '모든 사진', + 'memories.confirmShareTitle': '여행 멤버와 공유할까요?', + 'memories.confirmShareHint': + '{count}장의 사진이 이 여행의 모든 멤버에게 표시됩니다. 나중에 개별 사진을 비공개로 만들 수 있습니다.', + 'memories.confirmShareButton': '사진 공유', + 'memories.error.loadAlbums': '앨범 불러오기 실패', + 'memories.error.linkAlbum': '앨범 연결 실패', + 'memories.error.unlinkAlbum': '앨범 연결 해제 실패', + 'memories.error.syncAlbum': '앨범 동기화 실패', + 'memories.error.loadPhotos': '사진 불러오기 실패', + 'memories.error.addPhotos': '사진 추가 실패', + 'memories.error.removePhoto': '사진 제거 실패', + 'memories.error.toggleSharing': '공유 업데이트 실패', + 'memories.saveRouteNotConfigured': + '이 공급자에 대해 저장 경로가 설정되지 않았습니다', + 'memories.testRouteNotConfigured': + '이 공급자에 대해 테스트 경로가 설정되지 않았습니다', + 'memories.fillRequiredFields': '모든 필수 항목을 입력하세요', +}; +export default memories; diff --git a/shared/src/i18n/ko/nav.ts b/shared/src/i18n/ko/nav.ts new file mode 100644 index 00000000..44cb2b11 --- /dev/null +++ b/shared/src/i18n/ko/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': '여행', + 'nav.share': '공유', + 'nav.settings': '설정', + 'nav.admin': '관리자', + 'nav.logout': '로그아웃', + 'nav.lightMode': '라이트 모드', + 'nav.darkMode': '다크 모드', + 'nav.autoMode': '자동 모드', + 'nav.administrator': '관리자', + 'nav.myTrips': '내 여행', + 'nav.profile': '프로필', + 'nav.bottomSettings': '설정', + 'nav.bottomAdmin': '관리자 설정', + 'nav.bottomLogout': '로그아웃', + 'nav.bottomAdminBadge': '관리자', +}; +export default nav; diff --git a/shared/src/i18n/ko/notif.ts b/shared/src/i18n/ko/notif.ts new file mode 100644 index 00000000..984dc8b4 --- /dev/null +++ b/shared/src/i18n/ko/notif.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[테스트] 알림', + 'notif.test.simple.text': '간단한 테스트 알림입니다.', + 'notif.test.boolean.text': '이 테스트 알림을 수락하시겠습니까?', + 'notif.test.navigate.text': '아래를 클릭하여 대시보드로 이동하세요.', + 'notif.trip_invite.title': '여행 초대', + 'notif.trip_invite.text': '{actor}이(가) {trip}에 초대했습니다', + 'notif.booking_change.title': '예약 업데이트됨', + 'notif.booking_change.text': '{actor}이(가) {trip}의 예약을 업데이트했습니다', + 'notif.trip_reminder.title': '여행 리마인더', + 'notif.trip_reminder.text': '여행 {trip}이(가) 곧 시작됩니다!', + 'notif.todo_due.title': '할 일 마감', + 'notif.todo_due.text': '{trip}의 {todo}이(가) {due}에 마감됩니다', + 'notif.vacay_invite.title': 'Vacay 퓨전 초대', + 'notif.vacay_invite.text': '{actor}이(가) 휴가 계획 공유에 초대했습니다', + 'notif.photos_shared.title': '사진 공유됨', + 'notif.photos_shared.text': + '{actor}이(가) {trip}에서 {count}장의 사진을 공유했습니다', + 'notif.collab_message.title': '새 메시지', + 'notif.collab_message.text': '{actor}이(가) {trip}에서 메시지를 보냈습니다', + 'notif.packing_tagged.title': '짐 목록 배정', + 'notif.packing_tagged.text': + '{actor}이(가) {trip}의 {category}에 배정했습니다', + 'notif.version_available.title': '새 버전 사용 가능', + 'notif.version_available.text': 'TREK {version}이(가) 사용 가능합니다', + 'notif.action.view_trip': '여행 보기', + 'notif.action.view_collab': '메시지 보기', + 'notif.action.view_packing': '짐 목록 보기', + 'notif.action.view_photos': '사진 보기', + 'notif.action.view_vacay': 'Vacay 보기', + 'notif.action.view_admin': '관리자로 이동', + 'notif.action.view': '보기', + 'notif.action.accept': '수락', + 'notif.action.decline': '거절', + 'notif.generic.title': '알림', + 'notif.generic.text': '새 알림이 있습니다', + 'notif.dev.unknown_event.title': '[DEV] 알 수 없는 이벤트', + 'notif.dev.unknown_event.text': + '이벤트 유형 "{event}"이(가) EVENT_NOTIFICATION_CONFIG에 등록되지 않았습니다', +}; +export default notif; diff --git a/shared/src/i18n/ko/notifications.ts b/shared/src/i18n/ko/notifications.ts new file mode 100644 index 00000000..e3d339e5 --- /dev/null +++ b/shared/src/i18n/ko/notifications.ts @@ -0,0 +1,39 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': '알림', + 'notifications.markAllRead': '모두 읽음으로 표시', + 'notifications.deleteAll': '모두 삭제', + 'notifications.showAll': '모든 알림 보기', + 'notifications.empty': '알림 없음', + 'notifications.emptyDescription': '모두 확인했습니다!', + 'notifications.all': '전체', + 'notifications.unreadOnly': '읽지 않음', + 'notifications.markRead': '읽음으로 표시', + 'notifications.markUnread': '읽지 않음으로 표시', + 'notifications.delete': '삭제', + 'notifications.system': '시스템', + 'notifications.synologySessionCleared.title': 'Synology Photos 연결 해제됨', + 'notifications.synologySessionCleared.text': + '서버 또는 계정이 변경되었습니다 — 설정에서 연결을 다시 테스트하세요.', + 'notifications.versionAvailable.title': '업데이트 사용 가능', + 'notifications.versionAvailable.text': + 'TREK {version}이(가) 사용 가능합니다.', + 'notifications.versionAvailable.button': '상세 보기', + 'notifications.test.title': '{actor}의 테스트 알림', + 'notifications.test.text': '간단한 테스트 알림입니다.', + 'notifications.test.booleanTitle': '{actor}이(가) 승인을 요청합니다', + 'notifications.test.booleanText': + '테스트 boolean 알림입니다. 아래에서 작업을 선택하세요.', + 'notifications.test.accept': '승인', + 'notifications.test.decline': '거절', + 'notifications.test.navigateTitle': '확인할 항목이 있습니다', + 'notifications.test.navigateText': '테스트 navigate 알림입니다.', + 'notifications.test.goThere': '이동', + 'notifications.test.adminTitle': '관리자 방송', + 'notifications.test.adminText': + '{actor}이(가) 모든 관리자에게 테스트 알림을 보냈습니다.', + 'notifications.test.tripTitle': '{actor}이(가) 여행에 게시했습니다', + 'notifications.test.tripText': '여행 "{trip}"의 테스트 알림입니다.', +}; +export default notifications; diff --git a/shared/src/i18n/ko/oauth.ts b/shared/src/i18n/ko/oauth.ts new file mode 100644 index 00000000..e04c287e --- /dev/null +++ b/shared/src/i18n/ko/oauth.ts @@ -0,0 +1,87 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': '여행', + 'oauth.scope.group.places': '장소', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': '짐 목록', + 'oauth.scope.group.todos': '할 일', + 'oauth.scope.group.budget': '예산', + 'oauth.scope.group.reservations': '예약', + 'oauth.scope.group.collab': '협업', + 'oauth.scope.group.notifications': '알림', + 'oauth.scope.group.vacay': '휴가', + 'oauth.scope.group.geo': '지리', + 'oauth.scope.group.weather': '날씨', + 'oauth.scope.group.journey': 'Journey', + 'oauth.scope.trips:read.label': '여행 및 일정 보기', + 'oauth.scope.trips:read.description': '여행, 날, 일별 메모, 멤버 읽기', + 'oauth.scope.trips:write.label': '여행 및 일정 편집', + 'oauth.scope.trips:write.description': + '여행, 날, 메모 만들기 및 업데이트, 멤버 관리', + 'oauth.scope.trips:delete.label': '여행 삭제', + 'oauth.scope.trips:delete.description': + '전체 여행 영구 삭제 — 이 작업은 되돌릴 수 없습니다', + 'oauth.scope.trips:share.label': '공유 링크 관리', + 'oauth.scope.trips:share.description': + '여행의 공개 공유 링크 만들기, 업데이트, 취소', + 'oauth.scope.places:read.label': '장소 및 지도 데이터 보기', + 'oauth.scope.places:read.description': '장소, 날 배정, 태그, 카테고리 읽기', + 'oauth.scope.places:write.label': '장소 관리', + 'oauth.scope.places:write.description': + '장소, 배정, 태그 만들기, 업데이트, 삭제', + 'oauth.scope.atlas:read.label': 'Atlas 보기', + 'oauth.scope.atlas:read.description': '방문한 나라, 지역, 버킷 리스트 읽기', + 'oauth.scope.atlas:write.label': 'Atlas 관리', + 'oauth.scope.atlas:write.description': + '방문한 나라 및 지역 표시, 버킷 리스트 관리', + 'oauth.scope.packing:read.label': '짐 목록 보기', + 'oauth.scope.packing:read.description': '짐 항목, 가방, 카테고리 배정 읽기', + 'oauth.scope.packing:write.label': '짐 목록 관리', + 'oauth.scope.packing:write.description': + '짐 항목 및 가방 추가, 업데이트, 삭제, 체크, 순서 변경', + 'oauth.scope.todos:read.label': '할 일 목록 보기', + 'oauth.scope.todos:read.description': '여행 할 일 항목 및 카테고리 배정 읽기', + 'oauth.scope.todos:write.label': '할 일 목록 관리', + 'oauth.scope.todos:write.description': + '할 일 항목 만들기, 업데이트, 체크, 삭제, 순서 변경', + 'oauth.scope.budget:read.label': '예산 보기', + 'oauth.scope.budget:read.description': '예산 항목 및 지출 내역 읽기', + 'oauth.scope.budget:write.label': '예산 관리', + 'oauth.scope.budget:write.description': '예산 항목 만들기, 업데이트, 삭제', + 'oauth.scope.reservations:read.label': '예약 보기', + 'oauth.scope.reservations:read.description': '예약 및 숙박 상세 정보 읽기', + 'oauth.scope.reservations:write.label': '예약 관리', + 'oauth.scope.reservations:write.description': + '예약 만들기, 업데이트, 삭제, 순서 변경', + 'oauth.scope.collab:read.label': '협업 보기', + 'oauth.scope.collab:read.description': '협업 메모, 투표, 메시지 읽기', + 'oauth.scope.collab:write.label': '협업 관리', + 'oauth.scope.collab:write.description': + '협업 메모, 투표, 메시지 만들기, 업데이트, 삭제', + 'oauth.scope.notifications:read.label': '알림 보기', + 'oauth.scope.notifications:read.description': + '앱 내 알림 및 읽지 않은 수 읽기', + 'oauth.scope.notifications:write.label': '알림 관리', + 'oauth.scope.notifications:write.description': '알림 읽음 표시 및 응답', + 'oauth.scope.vacay:read.label': '휴가 계획 보기', + 'oauth.scope.vacay:read.description': '휴가 계획 데이터, 항목, 통계 읽기', + 'oauth.scope.vacay:write.label': '휴가 계획 관리', + 'oauth.scope.vacay:write.description': + '휴가 항목, 공휴일, 팀 계획 만들기 및 관리', + 'oauth.scope.geo:read.label': '지도 및 지오코딩', + 'oauth.scope.geo:read.description': + '위치 검색, 지도 URL 확인, 좌표 역지오코딩', + 'oauth.scope.weather:read.label': '날씨 예보', + 'oauth.scope.weather:read.description': + '여행 위치 및 날짜의 날씨 예보 가져오기', + 'oauth.scope.journey:read.label': 'Journey 보기', + 'oauth.scope.journey:read.description': 'Journey, 항목, 기여자 목록 읽기', + 'oauth.scope.journey:write.label': 'Journey 관리', + 'oauth.scope.journey:write.description': + 'Journey 및 항목 만들기, 업데이트, 삭제', + 'oauth.scope.journey:share.label': 'Journey 링크 관리', + 'oauth.scope.journey:share.description': + 'Journey의 공개 공유 링크 만들기, 업데이트, 취소', +}; +export default oauth; diff --git a/shared/src/i18n/ko/packing.ts b/shared/src/i18n/ko/packing.ts new file mode 100644 index 00000000..042fbfe9 --- /dev/null +++ b/shared/src/i18n/ko/packing.ts @@ -0,0 +1,185 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': '짐 목록', + 'packing.empty': '짐 목록이 비어 있습니다', + 'packing.import': '가져오기', + 'packing.importTitle': '짐 목록 가져오기', + 'packing.importHint': + '한 줄에 하나의 항목. 형식: 카테고리, 이름, 무게(g, 선택), 가방(선택), checked/unchecked(선택)', + 'packing.importPlaceholder': + '위생, 칫솔\n의류, 티셔츠, 200\n서류, 여권, , 기내 수하물\n전자기기, 충전기, 50, 캐리어, checked', + 'packing.importCsv': 'CSV/TXT 불러오기', + 'packing.importAction': '{count}개 가져오기', + 'packing.importSuccess': '{count}개 항목을 가져왔습니다', + 'packing.importError': '가져오기 실패', + 'packing.importEmpty': '가져올 항목이 없습니다', + 'packing.progress': '{total}개 중 {packed}개 완료 ({percent}%)', + 'packing.clearChecked': '체크된 {count}개 제거', + 'packing.clearCheckedShort': '{count}개 제거', + 'packing.suggestions': '제안', + 'packing.suggestionsTitle': '제안 추가', + 'packing.allSuggested': '모든 제안이 추가되었습니다', + 'packing.allPacked': '모두 완료!', + 'packing.addPlaceholder': '새 항목 추가...', + 'packing.categoryPlaceholder': '카테고리...', + 'packing.filterAll': '전체', + 'packing.filterOpen': '미완료', + 'packing.filterDone': '완료', + 'packing.emptyTitle': '짐 목록이 비어 있습니다', + 'packing.emptyHint': '항목을 추가하거나 제안을 사용하세요', + 'packing.emptyFiltered': '이 필터와 일치하는 항목이 없습니다', + 'packing.menuRename': '이름 변경', + 'packing.menuCheckAll': '전체 체크', + 'packing.menuUncheckAll': '전체 체크 해제', + 'packing.menuDeleteCat': '카테고리 삭제', + 'packing.noMembers': '여행 멤버가 없습니다', + 'packing.addItem': '항목 추가', + 'packing.addItemPlaceholder': '항목 이름...', + 'packing.addCategory': '카테고리 추가', + 'packing.newCategoryPlaceholder': '카테고리 이름 (예: 의류)', + 'packing.applyTemplate': '템플릿 적용', + 'packing.template': '템플릿', + 'packing.templateApplied': '템플릿에서 {count}개 항목이 추가되었습니다', + 'packing.templateError': '템플릿 적용 실패', + 'packing.saveAsTemplate': '템플릿으로 저장', + 'packing.templateName': '템플릿 이름', + 'packing.templateSaved': '짐 목록이 템플릿으로 저장되었습니다', + 'packing.bags': '가방', + 'packing.noBag': '미배정', + 'packing.totalWeight': '총 무게', + 'packing.bagName': '가방 이름...', + 'packing.addBag': '가방 추가', + 'packing.changeCategory': '카테고리 변경', + 'packing.confirm.clearChecked': '체크된 {count}개 항목을 제거할까요?', + 'packing.confirm.deleteCat': + '카테고리 "{name}"을(를) {count}개 항목과 함께 삭제할까요?', + 'packing.defaultCategory': '기타', + 'packing.toast.saveError': '저장 실패', + 'packing.toast.deleteError': '삭제 실패', + 'packing.toast.renameError': '이름 변경 실패', + 'packing.toast.addError': '추가 실패', + 'packing.suggestions.items': [ + { + name: '여권', + category: '서류', + }, + { + name: '신분증', + category: '서류', + }, + { + name: '여행자 보험', + category: '서류', + }, + { + name: '항공권', + category: '서류', + }, + { + name: '신용카드', + category: '금융', + }, + { + name: '현금', + category: '금융', + }, + { + name: '비자', + category: '서류', + }, + { + name: '티셔츠', + category: '의류', + }, + { + name: '바지', + category: '의류', + }, + { + name: '속옷', + category: '의류', + }, + { + name: '양말', + category: '의류', + }, + { + name: '재킷', + category: '의류', + }, + { + name: '잠옷', + category: '의류', + }, + { + name: '수영복', + category: '의류', + }, + { + name: '우비', + category: '의류', + }, + { + name: '편한 신발', + category: '의류', + }, + { + name: '칫솔', + category: '세면도구', + }, + { + name: '치약', + category: '세면도구', + }, + { + name: '샴푸', + category: '세면도구', + }, + { + name: '데오도란트', + category: '세면도구', + }, + { + name: '자외선 차단제', + category: '세면도구', + }, + { + name: '면도기', + category: '세면도구', + }, + { + name: '충전기', + category: '전자기기', + }, + { + name: '보조 배터리', + category: '전자기기', + }, + { + name: '헤드폰', + category: '전자기기', + }, + { + name: '여행용 어댑터', + category: '전자기기', + }, + { + name: '카메라', + category: '전자기기', + }, + { + name: '진통제', + category: '건강', + }, + { + name: '반창고', + category: '건강', + }, + { + name: '소독제', + category: '건강', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/ko/pdf.ts b/shared/src/i18n/ko/pdf.ts new file mode 100644 index 00000000..f6ca1417 --- /dev/null +++ b/shared/src/i18n/ko/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': '여행 계획', + 'pdf.planned': '계획됨', + 'pdf.costLabel': '비용 (원)', + 'pdf.preview': 'PDF 미리보기', + 'pdf.saveAsPdf': 'PDF로 저장', +}; +export default pdf; diff --git a/shared/src/i18n/ko/perm.ts b/shared/src/i18n/ko/perm.ts new file mode 100644 index 00000000..2ff9efd2 --- /dev/null +++ b/shared/src/i18n/ko/perm.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': '권한 설정', + 'perm.subtitle': '앱 전체에서 누가 작업을 수행할 수 있는지 제어합니다', + 'perm.saved': '권한 설정이 저장되었습니다', + 'perm.resetDefaults': '기본값으로 초기화', + 'perm.customized': '맞춤 설정됨', + 'perm.level.admin': '관리자만', + 'perm.level.tripOwner': '여행 소유자', + 'perm.level.tripMember': '여행 멤버', + 'perm.level.everybody': '모든 사람', + 'perm.cat.trip': '여행 관리', + 'perm.cat.members': '멤버 관리', + 'perm.cat.files': '파일', + 'perm.cat.content': '콘텐츠 및 일정', + 'perm.cat.extras': '예산, 짐 목록 및 협업', + 'perm.action.trip_create': '여행 만들기', + 'perm.action.trip_edit': '여행 상세 편집', + 'perm.action.trip_delete': '여행 삭제', + 'perm.action.trip_archive': '여행 보관/복원', + 'perm.action.trip_cover_upload': '커버 이미지 업로드', + 'perm.action.member_manage': '멤버 추가/제거', + 'perm.action.file_upload': '파일 업로드', + 'perm.action.file_edit': '파일 메타데이터 편집', + 'perm.action.file_delete': '파일 삭제', + 'perm.action.place_edit': '장소 추가/편집/삭제', + 'perm.action.day_edit': '날, 메모 및 배정 편집', + 'perm.action.reservation_edit': '예약 관리', + 'perm.action.budget_edit': '예산 관리', + 'perm.action.packing_edit': '짐 목록 관리', + 'perm.action.collab_edit': '협업 (메모, 투표, 채팅)', + 'perm.action.share_manage': '공유 링크 관리', + 'perm.actionHint.trip_create': '누가 새 여행을 만들 수 있는지', + 'perm.actionHint.trip_edit': + '누가 여행 이름, 날짜, 설명, 통화를 변경할 수 있는지', + 'perm.actionHint.trip_delete': '누가 여행을 영구 삭제할 수 있는지', + 'perm.actionHint.trip_archive': '누가 여행을 보관하거나 복원할 수 있는지', + 'perm.actionHint.trip_cover_upload': + '누가 커버 이미지를 업로드하거나 변경할 수 있는지', + 'perm.actionHint.member_manage': + '누가 여행 멤버를 초대하거나 제거할 수 있는지', + 'perm.actionHint.file_upload': '누가 여행에 파일을 업로드할 수 있는지', + 'perm.actionHint.file_edit': '누가 파일 설명 및 링크를 편집할 수 있는지', + 'perm.actionHint.file_delete': + '누가 파일을 휴지통으로 이동하거나 영구 삭제할 수 있는지', + 'perm.actionHint.place_edit': '누가 장소를 추가, 편집, 삭제할 수 있는지', + 'perm.actionHint.day_edit': + '누가 날, 일별 메모, 장소 배정을 편집할 수 있는지', + 'perm.actionHint.reservation_edit': + '누가 예약을 만들고, 편집하고, 삭제할 수 있는지', + 'perm.actionHint.budget_edit': + '누가 예산 항목을 만들고, 편집하고, 삭제할 수 있는지', + 'perm.actionHint.packing_edit': '누가 짐 항목과 가방을 관리할 수 있는지', + 'perm.actionHint.collab_edit': + '누가 메모, 투표를 만들고 메시지를 보낼 수 있는지', + 'perm.actionHint.share_manage': + '누가 공개 공유 링크를 만들거나 삭제할 수 있는지', +}; +export default perm; diff --git a/shared/src/i18n/ko/photos.ts b/shared/src/i18n/ko/photos.ts new file mode 100644 index 00000000..807d20a6 --- /dev/null +++ b/shared/src/i18n/ko/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': '사진', + 'photos.subtitle': '{trip}의 사진 {count}장', + 'photos.dropHere': '여기에 사진을 놓으세요...', + 'photos.dropHereActive': '여기에 사진을 놓으세요', + 'photos.captionForAll': '캡션 (전체)', + 'photos.captionPlaceholder': '선택적 캡션...', + 'photos.addCaption': '캡션 추가...', + 'photos.allDays': '모든 날', + 'photos.noPhotos': '아직 사진이 없습니다', + 'photos.uploadHint': '여행 사진을 업로드하세요', + 'photos.clickToSelect': '또는 클릭하여 선택', + 'photos.linkPlace': '장소 연결', + 'photos.noPlace': '장소 없음', + 'photos.uploadN': '{n}장 사진 업로드', + 'photos.linkDay': '날 연결', + 'photos.noDay': '날 없음', + 'photos.dayLabel': '{number}일차', + 'photos.photoSelected': '사진 선택됨', + 'photos.photosSelected': '사진 선택됨', + 'photos.fileTypeHint': 'JPG, PNG, WebP · 최대 10 MB · 최대 30장', +}; +export default photos; diff --git a/shared/src/i18n/ko/places.ts b/shared/src/i18n/ko/places.ts new file mode 100644 index 00000000..a14719da --- /dev/null +++ b/shared/src/i18n/ko/places.ts @@ -0,0 +1,90 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': '장소/활동 추가', + 'places.importFile': '파일 가져오기', + 'places.sidebarDrop': '끌어다 가져오기', + 'places.importFileHint': + 'Google My Maps, Google Earth 또는 GPS 추적기 등의 .gpx, .kml, .kmz 파일을 가져옵니다.', + 'places.importFileDropHere': '파일을 선택하거나 여기에 끌어다 놓으세요', + 'places.importFileDropActive': '파일을 놓으세요', + 'places.importFileUnsupported': + '지원하지 않는 파일 형식입니다. .gpx, .kml 또는 .kmz를 사용하세요.', + 'places.importFileTooLarge': + '파일이 너무 큽니다. 최대 업로드 크기는 {maxMb} MB입니다.', + 'places.importFileError': '가져오기 실패', + 'places.importAllSkipped': '모든 장소가 이미 여행에 포함되어 있습니다.', + 'places.gpxImported': 'GPX에서 {count}개 장소를 가져왔습니다', + 'places.gpxImportTypes': '무엇을 가져올까요?', + 'places.gpxImportWaypoints': '웨이포인트', + 'places.gpxImportRoutes': '경로', + 'places.gpxImportTracks': '트랙 (경로 형상 포함)', + 'places.gpxImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.', + 'places.kmlImportTypes': '무엇을 가져올까요?', + 'places.kmlImportPoints': '포인트 (Placemarks)', + 'places.kmlImportPaths': '경로 (LineStrings)', + 'places.kmlImportNoneSelected': '가져올 유형을 하나 이상 선택하세요.', + 'places.selectionCount': '{count}개 선택됨', + 'places.deleteSelected': '선택 항목 삭제', + 'places.kmlKmzImported': 'KMZ/KML에서 {count}개 장소를 가져왔습니다', + 'places.urlResolved': 'URL에서 장소를 가져왔습니다', + 'places.importList': '목록 가져오기', + 'places.kmlKmzSummaryValues': + '총 Placemarks: {total} · 가져옴: {created} · 건너뜀: {skipped}', + 'places.importGoogleList': 'Google 목록', + 'places.importNaverList': '네이버 목록', + 'places.googleListHint': + '공유된 Google Maps 목록 링크를 붙여넣어 모든 장소를 가져옵니다.', + 'places.googleListImported': '"{list}"에서 {count}개 장소를 가져왔습니다', + 'places.googleListError': 'Google Maps 목록 가져오기 실패', + 'places.naverListHint': + '공유된 네이버 지도 목록 링크를 붙여넣어 모든 장소를 가져옵니다.', + 'places.naverListImported': '"{list}"에서 {count}개 장소를 가져왔습니다', + 'places.naverListError': '네이버 지도 목록 가져오기 실패', + 'places.viewDetails': '상세 보기', + 'places.assignToDay': '어느 날에 추가할까요?', + 'places.all': '전체', + 'places.unplanned': '미계획', + 'places.filterTracks': '트랙', + 'places.search': '장소 검색...', + 'places.allCategories': '모든 카테고리', + 'places.categoriesSelected': '카테고리', + 'places.clearFilter': '필터 지우기', + 'places.count': '장소 {count}개', + 'places.countSingular': '장소 1개', + 'places.allPlanned': '모든 장소가 계획되었습니다', + 'places.noneFound': '장소를 찾을 수 없습니다', + 'places.editPlace': '장소 편집', + 'places.formName': '이름', + 'places.formNamePlaceholder': '예: 에펠탑', + 'places.formDescription': '설명', + 'places.formDescriptionPlaceholder': '간단한 설명...', + 'places.formAddress': '주소', + 'places.formAddressPlaceholder': '도로, 도시, 국가', + 'places.formLat': '위도 (예: 48.8566)', + 'places.formLng': '경도 (예: 2.3522)', + 'places.formCategory': '카테고리', + 'places.noCategory': '카테고리 없음', + 'places.categoryNamePlaceholder': '카테고리 이름', + 'places.formTime': '시간', + 'places.startTime': '시작', + 'places.endTime': '종료', + 'places.endTimeBeforeStart': '종료 시간이 시작 시간보다 앞입니다', + 'places.timeCollision': '시간 겹침:', + 'places.formWebsite': '웹사이트', + 'places.formNotes': '메모', + 'places.formNotesPlaceholder': '개인 메모...', + 'places.formReservation': '예약', + 'places.reservationNotesPlaceholder': '예약 메모, 확인 번호...', + 'places.mapsSearchPlaceholder': '장소 검색...', + 'places.mapsSearchError': '장소 검색 실패.', + 'places.loadingDetails': '장소 상세 정보 불러오는 중…', + 'places.osmHint': + 'OpenStreetMap 검색 사용 중 (사진, 영업 시간, 평점 없음). 전체 정보를 위해 설정에서 Google API 키를 추가하세요.', + 'places.osmActive': + 'OpenStreetMap으로 검색 중 (사진, 평점, 영업 시간 없음). 향상된 데이터를 위해 설정에서 Google API 키를 추가하세요.', + 'places.categoryCreateError': '카테고리 생성 실패', + 'places.nameRequired': '이름을 입력하세요', + 'places.saveError': '저장 실패', +}; +export default places; diff --git a/shared/src/i18n/ko/planner.ts b/shared/src/i18n/ko/planner.ts new file mode 100644 index 00000000..f15132d7 --- /dev/null +++ b/shared/src/i18n/ko/planner.ts @@ -0,0 +1,67 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': '장소', + 'planner.bookings': '예약', + 'planner.packingList': '짐 목록', + 'planner.documents': '문서', + 'planner.dayPlan': '일별 계획', + 'planner.reservations': '예약', + 'planner.minTwoPlaces': '경로 계산에 좌표가 있는 장소가 최소 2개 필요합니다', + 'planner.noGeoPlaces': '좌표가 있는 장소가 없습니다', + 'planner.routeCalculated': '경로가 계산되었습니다', + 'planner.routeCalcFailed': '경로를 계산할 수 없습니다', + 'planner.routeError': '경로 계산 중 오류', + 'planner.icsExportFailed': 'ICS 내보내기 실패', + 'planner.routeOptimized': '경로가 최적화되었습니다', + 'planner.reservationUpdated': '예약이 업데이트되었습니다', + 'planner.reservationAdded': '예약이 추가되었습니다', + 'planner.confirmDeleteReservation': '예약을 삭제할까요?', + 'planner.reservationDeleted': '예약이 삭제되었습니다', + 'planner.days': '일', + 'planner.allPlaces': '모든 장소', + 'planner.totalPlaces': '총 {n}개 장소', + 'planner.noDaysPlanned': '아직 계획된 날이 없습니다', + 'planner.editTrip': '여행 편집 →', + 'planner.placeOne': '장소 1개', + 'planner.placeN': '장소 {n}개', + 'planner.addNote': '메모 추가', + 'planner.noEntries': '이 날에 항목이 없습니다', + 'planner.addPlace': '장소/활동 추가', + 'planner.addPlaceShort': '+ 장소/활동 추가', + 'planner.resPending': '예약 대기 중 · ', + 'planner.resConfirmed': '예약 확정 · ', + 'planner.notePlaceholder': '메모…', + 'planner.noteTimePlaceholder': '시간 (선택)', + 'planner.noteExamplePlaceholder': + '예: 14:30 중앙역에서 S3, 7번 부두에서 페리, 점심 휴식…', + 'planner.totalCost': '총 비용', + 'planner.searchPlaces': '장소 검색…', + 'planner.allCategories': '모든 카테고리', + 'planner.noPlacesFound': '장소를 찾을 수 없습니다', + 'planner.addFirstPlace': '첫 번째 장소 추가', + 'planner.noReservations': '예약 없음', + 'planner.addFirstReservation': '첫 번째 예약 추가', + 'planner.new': '새로 만들기', + 'planner.addToDay': '+ 날에 추가', + 'planner.calculating': '계산 중…', + 'planner.route': '경로', + 'planner.optimize': '최적화', + 'planner.openGoogleMaps': 'Google Maps에서 열기', + 'planner.selectDayHint': '왼쪽 목록에서 날을 선택하여 일별 계획을 보세요', + 'planner.noPlacesForDay': '이 날에 아직 장소가 없습니다', + 'planner.addPlacesLink': '장소 추가 →', + 'planner.minTotal': '분 합계', + 'planner.noReservation': '예약 없음', + 'planner.removeFromDay': '날에서 제거', + 'planner.addToThisDay': '날에 추가', + 'planner.overview': '개요', + 'planner.noDays': '아직 날이 없습니다', + 'planner.editTripToAddDays': '여행을 편집하여 날을 추가하세요', + 'planner.dayCount': '{n}일', + 'planner.clickToUnlock': '클릭하여 잠금 해제', + 'planner.keepPosition': '경로 최적화 중 위치 유지', + 'planner.dayDetails': '일별 상세', + 'planner.dayN': '{n}일차', +}; +export default planner; diff --git a/shared/src/i18n/ko/register.ts b/shared/src/i18n/ko/register.ts new file mode 100644 index 00000000..3a8b3fe4 --- /dev/null +++ b/shared/src/i18n/ko/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': '비밀번호가 일치하지 않습니다', + 'register.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다', + 'register.failed': '회원가입 실패', + 'register.getStarted': '시작하기', + 'register.subtitle': '계정을 만들고 꿈의 여행을 계획하세요.', + 'register.feature1': '무제한 여행 계획', + 'register.feature2': '인터랙티브 지도 보기', + 'register.feature3': '장소 및 카테고리 관리', + 'register.feature4': '예약 추적', + 'register.feature5': '짐 목록 만들기', + 'register.feature6': '사진 및 파일 저장', + 'register.createAccount': '계정 만들기', + 'register.startPlanning': '여행 계획 시작', + 'register.minChars': '최소 6자', + 'register.confirmPassword': '비밀번호 확인', + 'register.repeatPassword': '비밀번호 재입력', + 'register.registering': '가입 중...', + 'register.register': '회원가입', + 'register.hasAccount': '이미 계정이 있으신가요?', + 'register.signIn': '로그인', +}; +export default register; diff --git a/shared/src/i18n/ko/reservations.ts b/shared/src/i18n/ko/reservations.ts new file mode 100644 index 00000000..5af4cbaa --- /dev/null +++ b/shared/src/i18n/ko/reservations.ts @@ -0,0 +1,116 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': '예약', + 'reservations.empty': '아직 예약이 없습니다', + 'reservations.emptyHint': '항공, 호텔 등의 예약을 추가하세요', + 'reservations.add': '예약 추가', + 'reservations.addManual': '직접 예약', + 'reservations.placeHint': + '팁: 예약은 일별 계획과 연결하기 위해 장소에서 직접 만드는 것이 가장 좋습니다.', + 'reservations.confirmed': '확정됨', + 'reservations.pending': '대기 중', + 'reservations.summary': '{confirmed}개 확정, {pending}개 대기 중', + 'reservations.fromPlan': '계획에서', + 'reservations.showFiles': '파일 보기', + 'reservations.editTitle': '예약 편집', + 'reservations.status': '상태', + 'reservations.datetime': '날짜 및 시간', + 'reservations.startTime': '시작 시간', + 'reservations.endTime': '종료 시간', + 'reservations.date': '날짜', + 'reservations.time': '시간', + 'reservations.timeAlt': '시간 (대안, 예: 19:30)', + 'reservations.notes': '메모', + 'reservations.notesPlaceholder': '추가 메모...', + 'reservations.meta.airline': '항공사', + 'reservations.meta.flightNumber': '항공편 번호', + 'reservations.meta.from': '출발', + 'reservations.meta.to': '도착', + 'reservations.needsReview': '검토 필요', + 'reservations.needsReviewHint': + '공항이 자동으로 매칭되지 않았습니다 — 위치를 확인해 주세요.', + 'reservations.searchLocation': '역, 항구, 주소 검색…', + 'reservations.meta.trainNumber': '열차 번호', + 'reservations.meta.platform': '플랫폼', + 'reservations.meta.seat': '좌석', + 'reservations.meta.checkIn': '체크인', + 'reservations.meta.checkInUntil': '체크인 마감', + 'reservations.meta.checkOut': '체크아웃', + 'reservations.meta.linkAccommodation': '숙박', + 'reservations.meta.pickAccommodation': '숙박 연결', + 'reservations.meta.noAccommodation': '없음', + 'reservations.meta.hotelPlace': '숙박', + 'reservations.meta.pickHotel': '숙박 선택', + 'reservations.meta.fromDay': '부터', + 'reservations.meta.toDay': '까지', + 'reservations.meta.selectDay': '날 선택', + 'reservations.type.flight': '항공', + 'reservations.type.hotel': '숙박', + 'reservations.type.restaurant': '레스토랑', + 'reservations.type.train': '기차', + 'reservations.type.car': '차량', + 'reservations.type.cruise': '크루즈', + 'reservations.type.event': '이벤트', + 'reservations.type.tour': '투어', + 'reservations.type.other': '기타', + 'reservations.confirm.delete': '예약 "{name}"을(를) 삭제할까요?', + 'reservations.confirm.deleteTitle': '예약을 삭제할까요?', + 'reservations.confirm.deleteBody': '"{name}"이(가) 영구 삭제됩니다.', + 'reservations.toast.updated': '예약이 업데이트되었습니다', + 'reservations.toast.removed': '예약이 삭제되었습니다', + 'reservations.toast.fileUploaded': '파일이 업로드되었습니다', + 'reservations.toast.uploadError': '업로드 실패', + 'reservations.newTitle': '새 예약', + 'reservations.bookingType': '예약 유형', + 'reservations.titleLabel': '제목', + 'reservations.titlePlaceholder': '예: 대한항공 KE123, 호텔 신라, ...', + 'reservations.locationAddress': '위치 / 주소', + 'reservations.locationPlaceholder': '주소, 공항, 호텔...', + 'reservations.confirmationCode': '예약 코드', + 'reservations.confirmationPlaceholder': '예: ABC12345', + 'reservations.day': '날', + 'reservations.noDay': '날 없음', + 'reservations.place': '장소', + 'reservations.noPlace': '장소 없음', + 'reservations.pendingSave': '저장될 예정…', + 'reservations.uploading': '업로드 중...', + 'reservations.attachFile': '파일 첨부', + 'reservations.linkExisting': '기존 파일 연결', + 'reservations.toast.saveError': '저장 실패', + 'reservations.toast.updateError': '업데이트 실패', + 'reservations.toast.deleteError': '삭제 실패', + 'reservations.confirm.remove': '"{name}"의 예약을 제거할까요?', + 'reservations.linkAssignment': '날 배정에 연결', + 'reservations.pickAssignment': '계획에서 배정을 선택하세요...', + 'reservations.noAssignment': '연결 없음 (독립)', + 'reservations.price': '가격', + 'reservations.budgetCategory': '예산 카테고리', + 'reservations.budgetCategoryPlaceholder': '예: 교통, 숙박', + 'reservations.budgetCategoryAuto': '자동 (예약 유형에서)', + 'reservations.budgetHint': '저장 시 예산 항목이 자동으로 생성됩니다.', + 'reservations.departureDate': '출발', + 'reservations.arrivalDate': '도착', + 'reservations.departureTime': '출발 시간', + 'reservations.arrivalTime': '도착 시간', + 'reservations.pickupDate': '픽업', + 'reservations.returnDate': '반납', + 'reservations.pickupTime': '픽업 시간', + 'reservations.returnTime': '반납 시간', + 'reservations.endDate': '종료 날짜', + 'reservations.meta.departureTimezone': '출발 시간대', + 'reservations.meta.arrivalTimezone': '도착 시간대', + 'reservations.span.departure': '출발', + 'reservations.span.arrival': '도착', + 'reservations.span.inTransit': '이동 중', + 'reservations.span.pickup': '픽업', + 'reservations.span.return': '반납', + 'reservations.span.active': '활성', + 'reservations.span.start': '시작', + 'reservations.span.end': '종료', + 'reservations.span.ongoing': '진행 중', + 'reservations.validation.endBeforeStart': + '종료 날짜/시간은 시작 날짜/시간 이후여야 합니다', + 'reservations.addBooking': '예약 추가', +}; +export default reservations; diff --git a/shared/src/i18n/ko/settings.ts b/shared/src/i18n/ko/settings.ts new file mode 100644 index 00000000..3ac19ef1 --- /dev/null +++ b/shared/src/i18n/ko/settings.ts @@ -0,0 +1,295 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': '설정', + 'settings.subtitle': '개인 설정을 구성하세요', + 'settings.tabs.display': '화면', + 'settings.tabs.map': '지도', + 'settings.tabs.notifications': '알림', + 'settings.tabs.integrations': '통합', + 'settings.tabs.account': '계정', + 'settings.tabs.offline': '오프라인', + 'settings.tabs.about': '정보', + 'settings.map': '지도', + 'settings.mapTemplate': '지도 템플릿', + 'settings.mapTemplatePlaceholder.select': '템플릿 선택...', + 'settings.mapDefaultHint': '비워두면 OpenStreetMap (기본값) 사용', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': '지도 타일 URL 템플릿', + 'settings.mapProvider': '지도 공급자', + 'settings.mapProviderHint': + '여행 플래너 및 Journey 지도에 영향을 줍니다. Atlas는 항상 Leaflet을 사용합니다.', + 'settings.mapLeafletSubtitle': '클래식 2D, 모든 래스터 타일', + 'settings.mapMapboxSubtitle': '벡터 타일, 3D 건물 및 지형', + 'settings.mapExperimental': '실험적', + 'settings.mapMapboxToken': 'Mapbox 액세스 토큰', + 'settings.mapMapboxTokenHint': '공개 토큰 (pk.*) 출처', + 'settings.mapMapboxTokenLink': 'mapbox.com → 액세스 토큰', + 'settings.mapStyle': '지도 스타일', + 'settings.mapStylePlaceholder': 'Mapbox 스타일 선택', + 'settings.mapStyleHint': '프리셋 또는 mapbox://styles/USER/ID URL 직접 입력', + 'settings.map3dBuildings': '3D 건물 및 지형', + 'settings.map3dHint': + '기울기 + 실제 3D 건물 돌출 — 위성 포함 모든 스타일에서 작동합니다.', + 'settings.mapHighQuality': '고품질 모드', + 'settings.mapHighQualityHint': + '안티앨리어싱 + 구형 투영으로 선명한 경계와 현실적인 지구 뷰를 제공합니다.', + 'settings.mapHighQualityWarning': + '저사양 기기에서 성능에 영향을 줄 수 있습니다.', + 'settings.mapTipLabel': '팁:', + 'settings.mapTip': + '우클릭 후 드래그하여 지도를 회전/기울이세요. 가운데 클릭으로 장소를 추가할 수 있습니다 (우클릭은 회전 전용).', + 'settings.latitude': '위도', + 'settings.longitude': '경도', + 'settings.saveMap': '지도 저장', + 'settings.apiKeys': 'API 키', + 'settings.mapsKey': 'Google Maps API 키', + 'settings.mapsKeyHint': + '장소 검색용. Places API (New) 필요. console.cloud.google.com에서 발급', + 'settings.weatherKey': 'OpenWeatherMap API 키', + 'settings.weatherKeyHint': + '날씨 데이터용. openweathermap.org/api에서 무료 발급', + 'settings.keyPlaceholder': '키 입력...', + 'settings.configured': '설정됨', + 'settings.saveKeys': '키 저장', + 'settings.display': '화면', + 'settings.colorMode': '색상 모드', + 'settings.light': '라이트', + 'settings.dark': '다크', + 'settings.auto': '자동', + 'settings.language': '언어', + 'settings.temperature': '온도 단위', + 'settings.timeFormat': '시간 형식', + 'settings.bookingLabels': '예약 경로 레이블', + 'settings.bookingLabelsHint': + '지도에 역 / 공항 이름을 표시합니다. 끄면 아이콘만 표시됩니다.', + 'settings.blurBookingCodes': '예약 코드 흐리게', + 'settings.notifications': '알림', + 'settings.notifyTripInvite': '여행 초대', + 'settings.notifyBookingChange': '예약 변경', + 'settings.notifyTripReminder': '여행 리마인더', + 'settings.notifyTodoDue': '할 일 마감 임박', + 'settings.notifyVacayInvite': 'Vacay 퓨전 초대', + 'settings.notifyPhotosShared': '공유된 사진 (Immich)', + 'settings.notifyCollabMessage': '채팅 메시지 (Collab)', + 'settings.notifyPackingTagged': '짐 목록: 배정', + 'settings.notifyWebhook': '웹훅 알림', + 'settings.notifyVersionAvailable': '새 버전 사용 가능', + 'settings.notificationPreferences.email': '이메일', + 'settings.notificationPreferences.webhook': '웹훅', + 'settings.notificationPreferences.inapp': '앱 내', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + '알림 채널이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 설정을 요청하세요.', + 'settings.webhookUrl.label': '웹훅 URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Discord, Slack 또는 사용자 지정 웹훅 URL을 입력하여 알림을 받으세요.', + 'settings.webhookUrl.saved': '웹훅 URL이 저장되었습니다', + 'settings.webhookUrl.test': '테스트', + 'settings.webhookUrl.testSuccess': '테스트 웹훅이 성공적으로 전송되었습니다', + 'settings.webhookUrl.testFailed': '테스트 웹훅 실패', + 'settings.ntfyUrl.topicLabel': 'Ntfy 토픽', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy 서버 URL (선택)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'ntfy 토픽을 입력하여 푸시 알림을 받으세요. 서버를 비워두면 관리자가 설정한 기본값을 사용합니다.', + 'settings.ntfyUrl.tokenLabel': '액세스 토큰 (선택)', + 'settings.ntfyUrl.tokenHint': '비밀번호로 보호된 토픽에 필요합니다.', + 'settings.ntfyUrl.saved': 'Ntfy 설정이 저장되었습니다', + 'settings.ntfyUrl.test': '테스트', + 'settings.ntfyUrl.testSuccess': + '테스트 ntfy 알림이 성공적으로 전송되었습니다', + 'settings.ntfyUrl.testFailed': '테스트 ntfy 알림 실패', + 'settings.ntfyUrl.tokenCleared': '액세스 토큰이 삭제되었습니다', + 'settings.notificationsDisabled': + '알림이 설정되지 않았습니다. 관리자에게 이메일 또는 웹훅 알림 활성화를 요청하세요.', + 'settings.notificationsActive': '활성 채널', + 'settings.notificationsManagedByAdmin': '알림 이벤트는 관리자가 설정합니다.', + 'settings.on': '켜기', + 'settings.off': '끄기', + 'settings.mcp.title': 'MCP 설정', + 'settings.mcp.endpoint': 'MCP 엔드포인트', + 'settings.mcp.clientConfig': '클라이언트 설정', + 'settings.mcp.clientConfigHint': + '을 아래 목록의 API 토큰으로 교체하세요. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).', + 'settings.mcp.clientConfigHintOAuth': + '을 위에서 생성한 OAuth 2.1 클라이언트 자격 증명으로 교체하세요. mcp-remote가 처음 연결 시 브라우저를 열어 인증을 완료합니다. npx 경로는 시스템에 따라 조정해야 할 수 있습니다 (예: Windows의 경우 C:\\PROGRA~1\\nodejs\\npx.cmd).', + 'settings.mcp.copy': '복사', + 'settings.mcp.copied': '복사됨!', + 'settings.mcp.apiTokens': 'API 토큰', + 'settings.mcp.createToken': '새 토큰 만들기', + 'settings.mcp.noTokens': + '토큰이 없습니다. MCP 클라이언트 연결을 위해 토큰을 만드세요.', + 'settings.mcp.tokenCreatedAt': '생성일', + 'settings.mcp.tokenUsedAt': '사용일', + 'settings.mcp.deleteTokenTitle': '토큰 삭제', + 'settings.mcp.deleteTokenMessage': + '이 토큰은 즉시 무효화됩니다. 이 토큰을 사용하는 MCP 클라이언트는 접근 권한을 잃게 됩니다.', + 'settings.mcp.modal.createTitle': 'API 토큰 만들기', + 'settings.mcp.modal.tokenName': '토큰 이름', + 'settings.mcp.modal.tokenNamePlaceholder': + '예: Claude Desktop, 업무용 노트북', + 'settings.mcp.modal.creating': '생성 중…', + 'settings.mcp.modal.create': '토큰 만들기', + 'settings.mcp.modal.createdTitle': '토큰이 생성되었습니다', + 'settings.mcp.modal.createdWarning': + '이 토큰은 한 번만 표시됩니다. 지금 복사하여 저장하세요 — 다시 복구할 수 없습니다.', + 'settings.mcp.modal.done': '완료', + 'settings.mcp.toast.created': '토큰이 생성되었습니다', + 'settings.mcp.toast.createError': '토큰 생성 실패', + 'settings.mcp.toast.deleted': '토큰이 삭제되었습니다', + 'settings.mcp.toast.deleteError': '토큰 삭제 실패', + 'settings.mcp.apiTokensDeprecated': + 'API 토큰은 더 이상 사용되지 않으며 향후 버전에서 제거될 예정입니다. 대신 OAuth 2.1 클라이언트를 사용하세요.', + 'settings.oauth.clients': 'OAuth 2.1 클라이언트', + 'settings.oauth.clientsHint': + 'OAuth 2.1 클라이언트를 등록하여 타사 MCP 앱 (Claude Web, Cursor 등)이 정적 토큰 없이 연결할 수 있도록 하세요.', + 'settings.oauth.createClient': '새 클라이언트', + 'settings.oauth.noClients': '등록된 OAuth 클라이언트가 없습니다.', + 'settings.oauth.clientId': '클라이언트 ID', + 'settings.oauth.clientSecret': '클라이언트 시크릿', + 'settings.oauth.deleteClient': '클라이언트 삭제', + 'settings.oauth.deleteClientMessage': + '이 클라이언트와 모든 활성 세션이 영구 삭제됩니다. 이 클라이언트를 사용하는 앱은 즉시 접근 권한을 잃게 됩니다.', + 'settings.oauth.rotateSecret': '시크릿 교체', + 'settings.oauth.rotateSecretMessage': + '새 클라이언트 시크릿이 생성되고 기존 세션은 즉시 무효화됩니다. 이 대화창을 닫기 전에 앱을 업데이트하세요.', + 'settings.oauth.rotateSecretConfirm': '교체', + 'settings.oauth.rotateSecretConfirming': '교체 중…', + 'settings.oauth.rotateSecretDoneTitle': '새 시크릿이 생성되었습니다', + 'settings.oauth.rotateSecretDoneWarning': + '이 시크릿은 한 번만 표시됩니다. 지금 복사하여 앱을 업데이트하세요 — 이전 세션은 모두 무효화되었습니다.', + 'settings.oauth.activeSessions': '활성 OAuth 세션', + 'settings.oauth.sessionScopes': '권한 범위', + 'settings.oauth.sessionExpires': '만료', + 'settings.oauth.revoke': '취소', + 'settings.oauth.revokeSession': '세션 취소', + 'settings.oauth.revokeSessionMessage': + '이 OAuth 세션의 접근 권한이 즉시 취소됩니다.', + 'settings.oauth.modal.createTitle': 'OAuth 클라이언트 등록', + 'settings.oauth.modal.presets': '빠른 프리셋', + 'settings.oauth.modal.clientName': '앱 이름', + 'settings.oauth.modal.clientNamePlaceholder': '예: Claude Web, My MCP App', + 'settings.oauth.modal.redirectUris': '리디렉션 URI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + '한 줄에 URI 하나. HTTPS 필수 (localhost 예외). 정확히 일치해야 합니다.', + 'settings.oauth.modal.scopes': '허용 권한 범위', + 'settings.oauth.modal.scopesHint': + 'list_trips 및 get_trip_summary는 항상 사용 가능합니다 — 권한 범위 불필요. AI가 다른 도구를 사용하는 데 필요한 여행 ID를 찾을 수 있습니다.', + 'settings.oauth.modal.selectAll': '전체 선택', + 'settings.oauth.modal.deselectAll': '전체 해제', + 'settings.oauth.modal.creating': '등록 중…', + 'settings.oauth.modal.create': '클라이언트 등록', + 'settings.oauth.modal.createdTitle': '클라이언트가 등록되었습니다', + 'settings.oauth.modal.createdWarning': + '클라이언트 시크릿은 한 번만 표시됩니다. 지금 복사하세요 — 다시 복구할 수 없습니다.', + 'settings.oauth.toast.createError': 'OAuth 클라이언트 등록 실패', + 'settings.oauth.toast.deleted': 'OAuth 클라이언트가 삭제되었습니다', + 'settings.oauth.toast.deleteError': 'OAuth 클라이언트 삭제 실패', + 'settings.oauth.toast.revoked': '세션이 취소되었습니다', + 'settings.oauth.toast.revokeError': '세션 취소 실패', + 'settings.oauth.toast.rotateError': '클라이언트 시크릿 교체 실패', + 'settings.account': '계정', + 'settings.about': '정보', + 'settings.about.reportBug': '버그 신고', + 'settings.about.reportBugHint': '문제를 발견하셨나요? 알려주세요', + 'settings.about.featureRequest': '기능 요청', + 'settings.about.featureRequestHint': '새로운 기능을 제안하세요', + 'settings.about.wikiHint': '문서 및 가이드', + 'settings.about.supporters.badge': '월간 후원자', + 'settings.about.supporters.title': 'TREK의 여행 동반자', + 'settings.about.supporters.subtitle': + '다음 여정을 계획하는 동안, 이분들이 TREK의 미래를 함께 만들어가고 있습니다. 월간 후원금은 개발과 실제 작업 시간에 직접 사용되어 TREK이 오픈 소스로 유지될 수 있게 합니다.', + 'settings.about.supporters.since': '{date}부터 후원자', + 'settings.about.supporters.tierEmpty': '첫 번째 후원자가 되어보세요', + 'settings.about.supporter.tier.noReturnTicket': '편도 티켓', + 'settings.about.supporter.tier.lostLuggageVip': '분실 수하물 VIP', + 'settings.about.supporter.tier.businessClassDreamer': + '비즈니스 클래스 꿈꾸기', + 'settings.about.supporter.tier.budgetTraveller': '알뜰 여행자', + 'settings.about.supporter.tier.hostelBunkmate': '호스텔 룸메이트', + 'settings.about.description': + 'TREK은 첫 아이디어부터 마지막 추억까지 여행을 체계적으로 관리하는 자체 호스팅 여행 플래너입니다. 일별 계획, 예산, 짐 목록, 사진 등 모든 것이 하나의 서버에 담겨 있습니다.', + 'settings.about.madeWith': '으로 만들어졌습니다', + 'settings.about.madeBy': 'Maurice와 성장하는 오픈 소스 커뮤니티가 함께', + 'settings.username': '사용자 이름', + 'settings.email': '이메일', + 'settings.role': '역할', + 'settings.roleAdmin': '관리자', + 'settings.oidcLinked': '연결됨', + 'settings.changePassword': '비밀번호 변경', + 'settings.currentPassword': '현재 비밀번호', + 'settings.currentPasswordRequired': '현재 비밀번호를 입력하세요', + 'settings.newPassword': '새 비밀번호', + 'settings.confirmPassword': '새 비밀번호 확인', + 'settings.updatePassword': '비밀번호 업데이트', + 'settings.passwordRequired': '현재 비밀번호와 새 비밀번호를 입력하세요', + 'settings.passwordTooShort': '비밀번호는 최소 8자 이상이어야 합니다', + 'settings.passwordMismatch': '비밀번호가 일치하지 않습니다', + 'settings.passwordWeak': + '비밀번호는 대문자, 소문자, 숫자, 특수문자를 포함해야 합니다', + 'settings.passwordChanged': '비밀번호가 성공적으로 변경되었습니다', + 'settings.mustChangePassword': + '계속하기 전에 비밀번호를 변경해야 합니다. 아래에서 새 비밀번호를 설정하세요.', + 'settings.deleteAccount': '계정 삭제', + 'settings.deleteAccountTitle': '계정을 삭제할까요?', + 'settings.deleteAccountWarning': + '계정과 모든 여행, 장소, 파일이 영구 삭제됩니다. 이 작업은 취소할 수 없습니다.', + 'settings.deleteAccountConfirm': '영구 삭제', + 'settings.deleteBlockedTitle': '삭제 불가', + 'settings.deleteBlockedMessage': + '유일한 관리자입니다. 계정을 삭제하기 전에 다른 사용자를 관리자로 승격하세요.', + 'settings.roleUser': '사용자', + 'settings.saveProfile': '프로필 저장', + 'settings.toast.mapSaved': '지도 설정이 저장되었습니다', + 'settings.toast.keysSaved': 'API 키가 저장되었습니다', + 'settings.toast.displaySaved': '화면 설정이 저장되었습니다', + 'settings.toast.profileSaved': '프로필이 저장되었습니다', + 'settings.uploadAvatar': '프로필 사진 업로드', + 'settings.removeAvatar': '프로필 사진 삭제', + 'settings.avatarUploaded': '프로필 사진이 업데이트되었습니다', + 'settings.avatarRemoved': '프로필 사진이 삭제되었습니다', + 'settings.avatarError': '업로드 실패', + 'settings.mfa.title': '2단계 인증 (2FA)', + 'settings.mfa.description': + '이메일 및 비밀번호로 로그인할 때 두 번째 단계를 추가합니다. 인증 앱 (Google Authenticator, Authy 등)을 사용하세요.', + 'settings.mfa.requiredByPolicy': + '관리자가 2단계 인증을 요구합니다. 앱을 계속 사용하려면 아래에서 인증 앱을 설정하세요.', + 'settings.mfa.backupTitle': '백업 코드', + 'settings.mfa.backupDescription': + '인증 앱에 접근할 수 없을 때 이 일회용 백업 코드를 사용하세요.', + 'settings.mfa.backupWarning': + '지금 이 코드를 저장하세요. 각 코드는 한 번만 사용할 수 있습니다.', + 'settings.mfa.backupCopy': '코드 복사', + 'settings.mfa.backupDownload': 'TXT 다운로드', + 'settings.mfa.backupPrint': '인쇄 / PDF', + 'settings.mfa.backupCopied': '백업 코드가 복사되었습니다', + 'settings.mfa.enabled': '계정에 2FA가 활성화되어 있습니다.', + 'settings.mfa.disabled': '2FA가 활성화되지 않았습니다.', + 'settings.mfa.setup': '인증 앱 설정', + 'settings.mfa.scanQr': + '앱으로 이 QR 코드를 스캔하거나 시크릿을 수동으로 입력하세요.', + 'settings.mfa.secretLabel': '시크릿 키 (수동 입력)', + 'settings.mfa.codePlaceholder': '6자리 코드', + 'settings.mfa.enable': '2FA 활성화', + 'settings.mfa.cancelSetup': '취소', + 'settings.mfa.disableTitle': '2FA 비활성화', + 'settings.mfa.disableHint': + '계정 비밀번호와 인증 앱의 현재 코드를 입력하세요.', + 'settings.mfa.disable': '2FA 비활성화', + 'settings.mfa.toastEnabled': '2단계 인증이 활성화되었습니다', + 'settings.mfa.toastDisabled': '2단계 인증이 비활성화되었습니다', + 'settings.mfa.demoBlocked': '데모 모드에서는 사용할 수 없습니다', + 'settings.oauth.modal.machineClient': '머신 클라이언트(브라우저 로그인 없음)', + 'settings.oauth.modal.machineClientHint': + 'client_credentials 권한 부여를 사용합니다 — 리디렉션 URI가 필요하지 않습니다. 토큰은 client_id + client_secret을 통해 직접 발급되며 선택한 범위 내에서 사용자로 작동합니다.', + 'settings.oauth.modal.machineClientUsage': + '토큰 받기: grant_type=client_credentials, client_id, client_secret으로 POST /oauth/token을 호출하세요. 브라우저도 새로 고침 토큰도 필요 없습니다.', + 'settings.oauth.badge.machine': '머신', +}; +export default settings; diff --git a/shared/src/i18n/ko/share.ts b/shared/src/i18n/ko/share.ts new file mode 100644 index 00000000..3ff30d2d --- /dev/null +++ b/shared/src/i18n/ko/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': '공개 링크', + 'share.linkHint': + '로그인 없이 이 여행을 볼 수 있는 링크를 만드세요. 읽기 전용 — 편집 불가.', + 'share.createLink': '링크 만들기', + 'share.deleteLink': '링크 삭제', + 'share.createError': '링크 생성 실패', + 'share.permMap': '지도 및 계획', + 'share.permBookings': '예약', + 'share.permPacking': '짐 목록', + 'share.permBudget': '예산', + 'share.permCollab': '채팅', +}; +export default share; diff --git a/shared/src/i18n/ko/shared.ts b/shared/src/i18n/ko/shared.ts new file mode 100644 index 00000000..dec8a195 --- /dev/null +++ b/shared/src/i18n/ko/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': '링크가 만료되었거나 유효하지 않습니다', + 'shared.expiredHint': '이 공유 여행 링크는 더 이상 유효하지 않습니다.', + 'shared.readOnly': '읽기 전용 공유 보기', + 'shared.tabPlan': '계획', + 'shared.tabBookings': '예약', + 'shared.tabPacking': '짐 목록', + 'shared.tabBudget': '예산', + 'shared.tabChat': '채팅', + 'shared.days': '일', + 'shared.places': '장소', + 'shared.other': '기타', + 'shared.totalBudget': '총 예산', + 'shared.messages': '메시지', + 'shared.sharedVia': '공유 경로', + 'shared.confirmed': '확정됨', + 'shared.pending': '대기 중', +}; +export default shared; diff --git a/shared/src/i18n/ko/stats.ts b/shared/src/i18n/ko/stats.ts new file mode 100644 index 00000000..68219709 --- /dev/null +++ b/shared/src/i18n/ko/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': '국가', + 'stats.cities': '도시', + 'stats.trips': '여행', + 'stats.places': '장소', + 'stats.worldProgress': '세계 진행도', + 'stats.visited': '방문함', + 'stats.remaining': '남음', + 'stats.visitedCountries': '방문한 나라', +}; +export default stats; diff --git a/shared/src/i18n/ko/system_notice.ts b/shared/src/i18n/ko/system_notice.ts new file mode 100644 index 00000000..bf85cda8 --- /dev/null +++ b/shared/src/i18n/ko/system_notice.ts @@ -0,0 +1,56 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.v3_photos.title': '3.0에서 사진이 이동했습니다', + 'system_notice.v3_photos.body': + '여행 플래너의 **사진** 기능이 제거되었습니다. 사진은 안전합니다 — TREK은 Immich 또는 Synology 라이브러리를 수정하지 않았습니다.\n\n사진은 이제 **Journey** 애드온에 있습니다. Journey는 선택 사항입니다 — 아직 사용할 수 없다면 관리자에게 관리자 → 애드온에서 활성화를 요청하세요.', + 'system_notice.v3_journey.title': 'Journey를 만나보세요 — 여행 일지', + 'system_notice.v3_journey.body': + '타임라인, 사진 갤러리, 인터랙티브 지도가 있는 풍부한 여행 이야기로 여행을 기록하세요.', + 'system_notice.v3_journey.cta_label': 'Journey 열기', + 'system_notice.v3_journey.highlight_timeline': '일별 타임라인 및 갤러리', + 'system_notice.v3_journey.highlight_photos': + 'Immich 또는 Synology에서 가져오기', + 'system_notice.v3_journey.highlight_share': '공개 공유 — 로그인 불필요', + 'system_notice.v3_journey.highlight_export': 'PDF 사진 책으로 내보내기', + 'system_notice.v3_features.title': '3.0의 더 많은 하이라이트', + 'system_notice.v3_features.body': '이번 릴리스에서 알아두면 좋은 몇 가지 더.', + 'system_notice.v3_features.highlight_dashboard': + '모바일 우선 대시보드 재설계', + 'system_notice.v3_features.highlight_offline': 'PWA로 완전한 오프라인 모드', + 'system_notice.v3_features.highlight_search': '실시간 장소 검색 자동완성', + 'system_notice.v3_features.highlight_import': + 'KMZ/KML 파일에서 장소 가져오기', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 업그레이드', + 'system_notice.v3_mcp.body': + 'MCP 통합이 완전히 개선되었습니다. OAuth 2.1이 이제 권장 인증 방법입니다. 기존 정적 토큰 (trek_…)은 더 이상 사용되지 않으며 향후 릴리스에서 제거될 예정입니다.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 권장 (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24개 세분화된 권한 범위', + 'system_notice.v3_mcp.highlight_deprecated': + '정적 trek_ 토큰 더 이상 사용 안 됨', + 'system_notice.v3_mcp.highlight_tools': '확장된 도구 모음 및 프롬프트', + 'system_notice.v3_thankyou.title': '개인적인 감사 인사', + 'system_notice.v3_thankyou.body': + '떠나시기 전에 잠깐 시간을 내주세요.\n\nTREK은 제 자신의 여행을 위해 만든 사이드 프로젝트로 시작했습니다. 4,000명이 넘는 분들이 모험을 계획하는 데 신뢰해 주실 줄은 상상도 못 했습니다. 모든 별, 모든 이슈, 모든 기능 요청 — 저는 다 읽고, 그것들이 풀타임 직장과 대학 사이의 늦은 밤을 버티게 해줍니다.\n\n알아주셨으면 합니다: TREK은 항상 오픈 소스이고, 항상 자체 호스팅이며, 항상 여러분의 것입니다. 추적 없음, 구독 없음, 조건 없음. 그저 여러분만큼 여행을 사랑하는 누군가가 만든 도구입니다.\n\n[jubnl](https://github.com/jubnl)에게 특별한 감사를. 당신은 훌륭한 협력자가 되었습니다. 3.0을 훌륭하게 만든 많은 부분에 당신의 손길이 담겨 있습니다. 거칠던 초기에 이 프로젝트를 믿어줘서 고맙습니다.\n\n그리고 버그를 제출하고, 문자열을 번역하고, TREK을 친구에게 공유하거나, 단순히 여행 계획에 사용해 주신 모든 분들께 — **감사합니다**. 여러분이 바로 이것이 존재하는 이유입니다.\n\n함께하는 더 많은 모험을 위해.\n\n— Maurice\n\n---\n\n[Discord 커뮤니티에 참여하세요](https://discord.gg/7Q6M6jDwzf)\n\nTREK이 여행을 더 즐겁게 만들어 준다면, [커피 한 잔](https://ko-fi.com/mauriceboe)으로 불을 켜두는 데 도움이 됩니다.', + 'system_notice.v3014_whitespace_collision.title': + '조치 필요: 사용자 계정 충돌', + 'system_notice.v3014_whitespace_collision.body': + '3.0.14 업그레이드 중 저장된 계정의 앞뒤 공백으로 인한 사용자 이름 또는 이메일 충돌이 감지되었습니다. 영향받은 계정은 자동으로 이름이 변경되었습니다. 검토가 필요한 계정을 확인하려면 **[migration] WHITESPACE COLLISION**으로 시작하는 줄의 서버 로그를 확인하세요.', + 'system_notice.welcome_v1.title': 'TREK에 오신 것을 환영합니다', + 'system_notice.welcome_v1.body': + '올인원 여행 플래너. 일정을 만들고, 친구들과 여행을 공유하고, 온라인 또는 오프라인으로 체계적으로 유지하세요.', + 'system_notice.welcome_v1.cta_label': '여행 계획', + 'system_notice.welcome_v1.hero_alt': + 'TREK 계획 UI 오버레이가 있는 아름다운 여행지', + 'system_notice.welcome_v1.highlight_plan': '모든 여행을 위한 일별 일정', + 'system_notice.welcome_v1.highlight_share': '여행 파트너와 협업', + 'system_notice.welcome_v1.highlight_offline': '모바일에서 오프라인으로 작동', + 'system_notice.dev_test_modal.title': '[Dev] 테스트 공지', + 'system_notice.dev_test_modal.body': '개발 전용 테스트 공지입니다.', + 'system_notice.pager.prev': '이전 공지', + 'system_notice.pager.next': '다음 공지', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': '{n}번 공지로 이동', + 'system_notice.pager.position': '공지 {current}/{total}', +}; +export default system_notice; diff --git a/shared/src/i18n/ko/todo.ts b/shared/src/i18n/ko/todo.ts new file mode 100644 index 00000000..fefcb183 --- /dev/null +++ b/shared/src/i18n/ko/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': '짐 목록', + 'todo.subtab.todo': '할 일', + 'todo.completed': '완료됨', + 'todo.filter.all': '전체', + 'todo.filter.open': '미완료', + 'todo.filter.done': '완료', + 'todo.uncategorized': '미분류', + 'todo.namePlaceholder': '작업 이름', + 'todo.descriptionPlaceholder': '설명 (선택)', + 'todo.unassigned': '미배정', + 'todo.noCategory': '카테고리 없음', + 'todo.hasDescription': '설명 있음', + 'todo.addItem': '새 작업 추가', + 'todo.sidebar.sortBy': '정렬 기준', + 'todo.priority': '우선순위', + 'todo.newCategoryLabel': '새로 만들기', + 'todo.newCategory': '카테고리 이름', + 'todo.addCategory': '카테고리 추가', + 'todo.newItem': '새 작업', + 'todo.empty': '아직 작업이 없습니다. 작업을 추가하여 시작하세요!', + 'todo.filter.my': '내 작업', + 'todo.filter.overdue': '기한 초과', + 'todo.sidebar.tasks': '작업', + 'todo.sidebar.categories': '카테고리', + 'todo.detail.title': '작업', + 'todo.detail.description': '설명', + 'todo.detail.category': '카테고리', + 'todo.detail.dueDate': '마감일', + 'todo.detail.assignedTo': '배정 대상', + 'todo.detail.delete': '삭제', + 'todo.detail.save': '변경 사항 저장', + 'todo.sortByPrio': '우선순위', + 'todo.detail.priority': '우선순위', + 'todo.detail.noPriority': '없음', + 'todo.detail.create': '작업 만들기', +}; +export default todo; diff --git a/shared/src/i18n/ko/transport.ts b/shared/src/i18n/ko/transport.ts new file mode 100644 index 00000000..62b7c847 --- /dev/null +++ b/shared/src/i18n/ko/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': '교통 추가', + 'transport.modalTitle.create': '교통 추가', + 'transport.modalTitle.edit': '교통 편집', + 'transport.title': '교통', + 'transport.addManual': '직접 교통 입력', +}; +export default transport; diff --git a/shared/src/i18n/ko/trip.ts b/shared/src/i18n/ko/trip.ts new file mode 100644 index 00000000..2c0000c0 --- /dev/null +++ b/shared/src/i18n/ko/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': '계획', + 'trip.tabs.transports': '교통', + 'trip.tabs.reservations': '예약', + 'trip.tabs.reservationsShort': '예약', + 'trip.tabs.packing': '짐 목록', + 'trip.tabs.packingShort': '짐', + 'trip.tabs.lists': '목록', + 'trip.tabs.listsShort': '목록', + 'trip.tabs.budget': '예산', + 'trip.tabs.files': '파일', + 'trip.loading': '여행 불러오는 중...', + 'trip.loadingPhotos': '장소 사진 불러오는 중...', + 'trip.mobilePlan': '계획', + 'trip.mobilePlaces': '장소', + 'trip.toast.placeUpdated': '장소가 업데이트되었습니다', + 'trip.toast.placeAdded': '장소가 추가되었습니다', + 'trip.toast.placeDeleted': '장소가 삭제되었습니다', + 'trip.toast.selectDay': '먼저 날을 선택하세요', + 'trip.toast.assignedToDay': '장소가 날에 배정되었습니다', + 'trip.toast.reorderError': '순서 변경 실패', + 'trip.toast.reservationUpdated': '예약이 업데이트되었습니다', + 'trip.toast.reservationAdded': '예약이 추가되었습니다', + 'trip.toast.deleted': '삭제됨', + 'trip.confirm.deletePlace': '이 장소를 삭제할까요?', + 'trip.confirm.deletePlaces': '{count}개 장소를 삭제할까요?', + 'trip.toast.placesDeleted': '{count}개 장소가 삭제되었습니다', +}; +export default trip; diff --git a/shared/src/i18n/ko/trips.ts b/shared/src/i18n/ko/trips.ts new file mode 100644 index 00000000..39fb4455 --- /dev/null +++ b/shared/src/i18n/ko/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} 제거됨', + 'trips.memberRemoveError': '제거 실패', + 'trips.memberAdded': '{username} 추가됨', + 'trips.memberAddError': '추가 실패', + 'trips.reminder': '리마인더', + 'trips.reminderNone': '없음', + 'trips.reminderDay': '일', + 'trips.reminderDays': '일', + 'trips.reminderCustom': '직접 설정', + 'trips.reminderDaysBefore': '일 전 출발', + 'trips.reminderDisabledHint': + '여행 리마인더가 비활성화되어 있습니다. 관리자 > 설정 > 알림에서 활성화하세요.', +}; +export default trips; diff --git a/shared/src/i18n/ko/undo.ts b/shared/src/i18n/ko/undo.ts new file mode 100644 index 00000000..73e7cb02 --- /dev/null +++ b/shared/src/i18n/ko/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': '실행 취소', + 'undo.tooltip': '실행 취소: {action}', + 'undo.assignPlace': '장소가 날에 배정되었습니다', + 'undo.removeAssignment': '장소가 날에서 제거되었습니다', + 'undo.reorder': '장소 순서가 변경되었습니다', + 'undo.optimize': '경로가 최적화되었습니다', + 'undo.deletePlace': '장소가 삭제되었습니다', + 'undo.deletePlaces': '장소들이 삭제되었습니다', + 'undo.moveDay': '장소가 다른 날로 이동되었습니다', + 'undo.lock': '장소 잠금이 변경되었습니다', + 'undo.importGpx': 'GPX 가져오기', + 'undo.importKeyholeMarkup': 'KMZ/KML 가져오기', + 'undo.importGoogleList': 'Google Maps 가져오기', + 'undo.importNaverList': '네이버 지도 가져오기', + 'undo.addPlace': '장소가 추가되었습니다', + 'undo.done': '실행 취소됨: {action}', +}; +export default undo; diff --git a/shared/src/i18n/ko/vacay.ts b/shared/src/i18n/ko/vacay.ts new file mode 100644 index 00000000..0ac03992 --- /dev/null +++ b/shared/src/i18n/ko/vacay.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': '휴가 일수를 계획하고 관리하세요', + 'vacay.settings': '설정', + 'vacay.year': '연도', + 'vacay.addYear': '다음 연도 추가', + 'vacay.addPrevYear': '이전 연도 추가', + 'vacay.removeYear': '연도 제거', + 'vacay.removeYearConfirm': '{year}을(를) 제거할까요?', + 'vacay.removeYearHint': + '이 연도의 모든 휴가 항목 및 회사 공휴일이 영구 삭제됩니다.', + 'vacay.remove': '제거', + 'vacay.persons': '인원', + 'vacay.noPersons': '추가된 인원이 없습니다', + 'vacay.addPerson': '인원 추가', + 'vacay.editPerson': '인원 편집', + 'vacay.removePerson': '인원 제거', + 'vacay.removePersonConfirm': '{name}을(를) 제거할까요?', + 'vacay.removePersonHint': '이 인원의 모든 휴가 항목이 영구 삭제됩니다.', + 'vacay.personName': '이름', + 'vacay.personNamePlaceholder': '이름 입력', + 'vacay.color': '색상', + 'vacay.add': '추가', + 'vacay.legend': '범례', + 'vacay.publicHoliday': '공휴일', + 'vacay.companyHoliday': '회사 휴일', + 'vacay.weekend': '주말', + 'vacay.modeVacation': '휴가', + 'vacay.modeCompany': '회사 휴일', + 'vacay.entitlement': '휴가 일수', + 'vacay.entitlementDays': '일', + 'vacay.used': '사용', + 'vacay.remaining': '남음', + 'vacay.carriedOver': '{year}에서 이월', + 'vacay.blockWeekends': '주말 차단', + 'vacay.blockWeekendsHint': '주말에 휴가 항목 추가를 방지합니다', + 'vacay.weekendDays': '주말 요일', + 'vacay.mon': '월', + 'vacay.tue': '화', + 'vacay.wed': '수', + 'vacay.thu': '목', + 'vacay.fri': '금', + 'vacay.sat': '토', + 'vacay.sun': '일', + 'vacay.publicHolidays': '공휴일', + 'vacay.publicHolidaysHint': '캘린더에 공휴일을 표시합니다', + 'vacay.selectCountry': '국가 선택', + 'vacay.selectRegion': '지역 선택 (선택)', + 'vacay.addCalendar': '캘린더 추가', + 'vacay.calendarLabel': '레이블 (선택)', + 'vacay.calendarColor': '색상', + 'vacay.noCalendars': '아직 공휴일 캘린더가 없습니다', + 'vacay.companyHolidays': '회사 휴일', + 'vacay.companyHolidaysHint': '회사 전체 휴일 표시를 허용합니다', + 'vacay.companyHolidaysNoDeduct': + '회사 휴일은 휴가 일수에서 차감되지 않습니다.', + 'vacay.weekStart': '주 시작 요일', + 'vacay.weekStartHint': '캘린더 주가 월요일 또는 일요일에 시작할지 선택하세요', + 'vacay.carryOver': '이월', + 'vacay.carryOverHint': '남은 휴가 일수를 다음 연도로 자동 이월합니다', + 'vacay.sharing': '공유', + 'vacay.sharingHint': '다른 TREK 사용자와 휴가 계획을 공유합니다', + 'vacay.owner': '소유자', + 'vacay.shareEmailPlaceholder': 'TREK 사용자 이메일', + 'vacay.shareSuccess': '계획이 성공적으로 공유되었습니다', + 'vacay.shareError': '계획 공유 실패', + 'vacay.dissolve': '퓨전 해제', + 'vacay.dissolveHint': '캘린더를 다시 분리합니다. 항목은 유지됩니다.', + 'vacay.dissolveAction': '해제', + 'vacay.dissolved': '캘린더가 분리되었습니다', + 'vacay.fusedWith': '퓨전됨', + 'vacay.you': '나', + 'vacay.noData': '데이터 없음', + 'vacay.changeColor': '색상 변경', + 'vacay.inviteUser': '사용자 초대', + 'vacay.inviteHint': + '다른 TREK 사용자를 초대하여 통합 휴가 캘린더를 공유하세요.', + 'vacay.selectUser': '사용자 선택', + 'vacay.sendInvite': '초대 전송', + 'vacay.inviteSent': '초대가 전송되었습니다', + 'vacay.inviteError': '초대 전송 실패', + 'vacay.pending': '대기 중', + 'vacay.noUsersAvailable': '사용 가능한 사용자가 없습니다', + 'vacay.accept': '수락', + 'vacay.decline': '거절', + 'vacay.acceptFusion': '수락 및 퓨전', + 'vacay.inviteTitle': '퓨전 요청', + 'vacay.inviteWantsToFuse': '귀하와 휴가 캘린더를 공유하고 싶어 합니다.', + 'vacay.fuseInfo1': + '두 사람 모두 하나의 공유 캘린더에서 모든 휴가 항목을 볼 수 있습니다.', + 'vacay.fuseInfo2': '양측 모두 서로의 항목을 만들고 편집할 수 있습니다.', + 'vacay.fuseInfo3': + '양측 모두 항목을 삭제하고 휴가 일수를 변경할 수 있습니다.', + 'vacay.fuseInfo4': '공휴일 및 회사 휴일 등의 설정이 공유됩니다.', + 'vacay.fuseInfo5': + '어느 쪽이든 언제든지 퓨전을 해제할 수 있습니다. 항목은 보존됩니다.', +}; +export default vacay; diff --git a/shared/src/i18n/languages.ts b/shared/src/i18n/languages.ts new file mode 100644 index 00000000..ff17ef3b --- /dev/null +++ b/shared/src/i18n/languages.ts @@ -0,0 +1,49 @@ +export const SUPPORTED_LANGUAGES = [ + { value: 'de', label: 'Deutsch', locale: 'de-DE' }, + { value: 'en', label: 'English', locale: 'en-US' }, + { value: 'es', label: 'Español', locale: 'es-ES' }, + { value: 'fr', label: 'Français', locale: 'fr-FR' }, + { value: 'hu', label: 'Magyar', locale: 'hu-HU' }, + { value: 'nl', label: 'Nederlands', locale: 'nl-NL' }, + { value: 'br', label: 'Português (Brasil)', locale: 'pt-BR' }, + { value: 'cs', label: 'Česky', locale: 'cs-CZ' }, + { value: 'pl', label: 'Polski', locale: 'pl-PL' }, + { value: 'ru', label: 'Русский', locale: 'ru-RU' }, + { value: 'zh', label: '简体中文', locale: 'zh-CN' }, + { value: 'zh-TW', label: '繁體中文', locale: 'zh-TW' }, + { value: 'it', label: 'Italiano', locale: 'it-IT' }, + { value: 'tr', label: 'Türkçe', locale: 'tr-TR' }, + { value: 'ar', label: 'العربية', locale: 'ar-SA' }, + { value: 'id', label: 'Bahasa Indonesia', locale: 'id-ID' }, + { value: 'ja', label: '日本語', locale: 'ja-JP' }, + { value: 'ko', label: '한국어', locale: 'ko-KR' }, + { value: 'uk', label: 'Українська', locale: 'uk-UA' }, +] as const; + +export type SupportedLanguageCode = + (typeof SUPPORTED_LANGUAGES)[number]['value']; + +export const SUPPORTED_LANGUAGE_CODES: string[] = SUPPORTED_LANGUAGES.map( + (l) => l.value, +); + +const LOCALES: Partial> = Object.fromEntries( + SUPPORTED_LANGUAGES.map((l) => [l.value, l.locale]), +); + +// Languages displayed right-to-left. +const RTL_LANGUAGES = new Set(['ar']); + +export function getLocaleForLanguage(language: string): string { + return LOCALES[language] ?? LOCALES['en'] ?? 'en-US'; +} + +// Returns a BCP-47 tag suitable for Intl APIs. +export function getIntlLanguage(language: string): string { + if (language === 'br') return 'pt-BR'; + return SUPPORTED_LANGUAGE_CODES.includes(language) ? language : 'en'; +} + +export function isRtlLanguage(language: string): boolean { + return RTL_LANGUAGES.has(language); +} diff --git a/shared/src/i18n/nl/admin.ts b/shared/src/i18n/nl/admin.ts new file mode 100644 index 00000000..4b63e430 --- /dev/null +++ b/shared/src/i18n/nl/admin.ts @@ -0,0 +1,364 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Meldingen', + 'admin.notifications.hint': + 'Kies een meldingskanaal. Er kan er slechts één tegelijk actief zijn.', + 'admin.notifications.none': 'Uitgeschakeld', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Meldingsinstellingen opslaan', + 'admin.notifications.saved': 'Meldingsinstellingen opgeslagen', + 'admin.notifications.testWebhook': 'Testwebhook verzenden', + 'admin.notifications.testWebhookSuccess': 'Testwebhook succesvol verzonden', + 'admin.notifications.testWebhookFailed': 'Testwebhook mislukt', + 'admin.smtp.title': 'E-mail en meldingen', + 'admin.smtp.hint': + 'SMTP-configuratie voor het verzenden van e-mailmeldingen.', + 'admin.smtp.testButton': 'Test-e-mail verzenden', + 'admin.webhook.hint': + 'Meldingen verzenden naar een externe webhook (Discord, Slack, enz.).', + 'admin.smtp.testSuccess': 'Test-e-mail succesvol verzonden', + 'admin.smtp.testFailed': 'Test-e-mail mislukt', + 'admin.title': 'Beheer', + 'admin.subtitle': 'Gebruikersbeheer en systeeminstellingen', + 'admin.tabs.users': 'Gebruikers', + 'admin.tabs.categories': 'Categorieën', + 'admin.tabs.backup': 'Back-up', + 'admin.tabs.audit': 'Audit', + 'admin.stats.users': 'Gebruikers', + 'admin.stats.trips': 'Reizen', + 'admin.stats.places': 'Plaatsen', + 'admin.stats.photos': "Foto's", + 'admin.stats.files': 'Bestanden', + 'admin.table.user': 'Gebruiker', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Rol', + 'admin.table.created': 'Aangemaakt', + 'admin.table.lastLogin': 'Laatste login', + 'admin.table.actions': 'Acties', + 'admin.you': '(Jij)', + 'admin.editUser': 'Gebruiker bewerken', + 'admin.newPassword': 'Nieuw wachtwoord', + 'admin.newPasswordHint': 'Laat leeg om het huidige wachtwoord te behouden', + 'admin.deleteUser': + 'Gebruiker "{name}" verwijderen? Alle reizen worden permanent verwijderd.', + 'admin.deleteUserTitle': 'Gebruiker verwijderen', + 'admin.newPasswordPlaceholder': 'Nieuw wachtwoord invoeren…', + 'admin.toast.loadError': 'Beheergegevens laden mislukt', + 'admin.toast.userUpdated': 'Gebruiker bijgewerkt', + 'admin.toast.updateError': 'Bijwerken mislukt', + 'admin.toast.userDeleted': 'Gebruiker verwijderd', + 'admin.toast.deleteError': 'Verwijderen mislukt', + 'admin.toast.cannotDeleteSelf': 'Je kunt je eigen account niet verwijderen', + 'admin.toast.userCreated': 'Gebruiker aangemaakt', + 'admin.toast.createError': 'Gebruiker aanmaken mislukt', + 'admin.toast.fieldsRequired': + 'Gebruikersnaam, e-mail en wachtwoord zijn verplicht', + 'admin.createUser': 'Gebruiker aanmaken', + 'admin.invite.title': 'Uitnodigingslinks', + 'admin.invite.subtitle': 'Eenmalige registratielinks aanmaken', + 'admin.invite.create': 'Link aanmaken', + 'admin.invite.createAndCopy': 'Aanmaken en kopiëren', + 'admin.invite.empty': 'Nog geen uitnodigingslinks aangemaakt', + 'admin.invite.maxUses': 'Max. gebruik', + 'admin.invite.expiry': 'Verloopt na', + 'admin.invite.uses': 'gebruikt', + 'admin.invite.expiresAt': 'verloopt op', + 'admin.invite.createdBy': 'door', + 'admin.invite.active': 'Actief', + 'admin.invite.expired': 'Verlopen', + 'admin.invite.usedUp': 'Opgebruikt', + 'admin.invite.copied': 'Uitnodigingslink gekopieerd', + 'admin.invite.copyLink': 'Link kopiëren', + 'admin.invite.deleted': 'Uitnodigingslink verwijderd', + 'admin.invite.createError': 'Fout bij aanmaken van link', + 'admin.invite.deleteError': 'Fout bij verwijderen van link', + 'admin.tabs.settings': 'Instellingen', + 'admin.allowRegistration': 'Registratie toestaan', + 'admin.allowRegistrationHint': + 'Nieuwe gebruikers kunnen zichzelf registreren', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Tweestapsverificatie (2FA) verplicht stellen', + 'admin.requireMfaHint': + 'Gebruikers zonder 2FA moeten de installatie in Instellingen voltooien voordat ze de app kunnen gebruiken.', + 'admin.apiKeys': 'API-sleutels', + 'admin.apiKeysHint': + "Optioneel. Schakelt uitgebreide plaatsgegevens in zoals foto's en weer.", + 'admin.mapsKey': 'Google Maps API-sleutel', + 'admin.mapsKeyHint': + 'Vereist voor het zoeken van plaatsen. Verkrijgbaar op console.cloud.google.com', + 'admin.mapsKeyHintLong': + "Zonder API-sleutel wordt OpenStreetMap gebruikt voor het zoeken van plaatsen. Met een Google API-sleutel kunnen ook foto's, beoordelingen en openingstijden worden geladen. Verkrijgbaar op console.cloud.google.com.", + 'admin.recommended': 'Aanbevolen', + 'admin.weatherKey': 'OpenWeatherMap API-sleutel', + 'admin.weatherKeyHint': 'Voor weergegevens. Gratis op openweathermap.org', + 'admin.validateKey': 'Testen', + 'admin.keyValid': 'Verbonden', + 'admin.keyInvalid': 'Ongeldig', + 'admin.keySaved': 'API-sleutels opgeslagen', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Sta inloggen toe via externe providers zoals Google, Apple, Authentik of Keycloak.', + 'admin.oidcDisplayName': 'Weergavenaam', + 'admin.oidcIssuer': 'Issuer-URL', + 'admin.oidcIssuerHint': + 'De OpenID Connect Issuer-URL van de provider. Bijv. https://accounts.google.com', + 'admin.oidcSaved': 'OIDC-configuratie opgeslagen', + 'admin.oidcOnlyMode': 'Wachtwoordauthenticatie uitschakelen', + 'admin.oidcOnlyModeHint': + 'Indien ingeschakeld, is alleen SSO-login toegestaan. Inloggen en registreren met wachtwoord worden geblokkeerd.', + 'admin.fileTypes': 'Toegestane bestandstypen', + 'admin.fileTypesHint': + 'Configureer welke bestandstypen gebruikers kunnen uploaden.', + 'admin.fileTypesFormat': + 'Kommagescheiden extensies (bijv. jpg,png,pdf,doc). Gebruik * om alle typen toe te staan.', + 'admin.fileTypesSaved': 'Bestandstype-instellingen opgeslagen', + 'admin.placesPhotos.title': "Plaatsfoto's", + 'admin.placesPhotos.subtitle': + "Haalt foto's op via de Google Places API. Schakel uit om API-quota te besparen. Wikimedia-foto's worden niet beïnvloed.", + 'admin.placesAutocomplete.title': 'Plaatsautocomplete', + 'admin.placesAutocomplete.subtitle': + 'Gebruikt de Google Places API voor zoeksuggesties. Schakel uit om API-quota te besparen.', + 'admin.placesDetails.title': 'Plaatsdetails', + 'admin.placesDetails.subtitle': + 'Haalt gedetailleerde plaatsinformatie (openingstijden, beoordeling, website) op via de Google Places API. Schakel uit om API-quota te besparen.', + 'admin.bagTracking.title': 'Bagagetracking', + 'admin.bagTracking.subtitle': + 'Gewicht en bagagetoewijzing inschakelen voor paklijstitems', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Realtime berichten voor reissamenwerking', + 'admin.collab.notes.title': 'Notities', + 'admin.collab.notes.subtitle': 'Gedeelde notities en documenten', + 'admin.collab.polls.title': 'Polls', + 'admin.collab.polls.subtitle': 'Groepspolls en stemmen', + 'admin.collab.whatsnext.title': 'Wat nu', + 'admin.collab.whatsnext.subtitle': + 'Activiteitssuggesties en volgende stappen', + 'admin.tabs.config': 'Personalisatie', + 'admin.tabs.defaults': 'Standaardinstellingen', + 'admin.defaultSettings.title': 'Standaard gebruikersinstellingen', + 'admin.defaultSettings.description': + 'Stel instantiebrede standaardwaarden in. Gebruikers die een instelling niet hebben gewijzigd, zien deze waarden. Hun eigen wijzigingen hebben altijd voorrang.', + 'admin.defaultSettings.saved': 'Standaard opgeslagen', + 'admin.defaultSettings.reset': 'Terugzetten naar ingebouwde standaard', + 'admin.defaultSettings.resetToBuiltIn': 'terugzetten', + 'admin.tabs.templates': 'Paksjablonen', + 'admin.packingTemplates.title': 'Paksjablonen', + 'admin.packingTemplates.subtitle': + 'Herbruikbare paklijsten maken voor je reizen', + 'admin.packingTemplates.create': 'Nieuw sjabloon', + 'admin.packingTemplates.namePlaceholder': + 'Sjabloonnaam (bijv. Strandvakantie)', + 'admin.packingTemplates.empty': 'Nog geen sjablonen aangemaakt', + 'admin.packingTemplates.items': 'items', + 'admin.packingTemplates.categories': 'categorieën', + 'admin.packingTemplates.itemName': 'Itemnaam', + 'admin.packingTemplates.itemCategory': 'Categorie', + 'admin.packingTemplates.categoryName': 'Categorienaam (bijv. Kleding)', + 'admin.packingTemplates.addCategory': 'Categorie toevoegen', + 'admin.packingTemplates.created': 'Sjabloon aangemaakt', + 'admin.packingTemplates.deleted': 'Sjabloon verwijderd', + 'admin.packingTemplates.loadError': 'Fout bij laden van sjablonen', + 'admin.packingTemplates.createError': 'Fout bij aanmaken van sjabloon', + 'admin.packingTemplates.deleteError': 'Fout bij verwijderen van sjabloon', + 'admin.packingTemplates.saveError': 'Fout bij opslaan', + 'admin.tabs.addons': 'Add-ons', + 'admin.addons.title': 'Add-ons', + 'admin.addons.subtitle': + 'Schakel functies in of uit om je TREK-ervaring aan te passen.', + 'admin.addons.catalog.memories.name': "Foto's (Immich)", + 'admin.addons.catalog.memories.description': + "Deel reisfoto's via je Immich-instantie", + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol voor AI-assistent integratie', + 'admin.addons.catalog.packing.name': 'Lijsten', + 'admin.addons.catalog.packing.description': + 'Paklijsten en to-dotaken voor je reizen', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Houd uitgaven bij en plan je reisbudget', + 'admin.addons.catalog.documents.name': 'Documenten', + 'admin.addons.catalog.documents.description': + 'Bewaar en beheer reisdocumenten', + 'admin.addons.catalog.vacay.name': 'Vakantie', + 'admin.addons.catalog.vacay.description': + 'Persoonlijke vakantieplanner met kalenderweergave', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Wereldkaart met bezochte landen en reisstatistieken', + 'admin.addons.catalog.collab.name': 'Samenwerking', + 'admin.addons.catalog.collab.description': + 'Realtime notities, polls en chat voor het plannen van reizen', + 'admin.addons.subtitleBefore': 'Schakel functies in of uit om je ', + 'admin.addons.subtitleAfter': '-ervaring aan te passen.', + 'admin.addons.enabled': 'Ingeschakeld', + 'admin.addons.disabled': 'Uitgeschakeld', + 'admin.addons.type.trip': 'Reis', + 'admin.addons.type.global': 'Globaal', + 'admin.addons.type.integration': 'Integratie', + 'admin.addons.tripHint': 'Beschikbaar als tabblad binnen elke reis', + 'admin.addons.globalHint': + 'Beschikbaar als zelfstandig onderdeel in de hoofdnavigatie', + 'admin.addons.integrationHint': + 'Backenddiensten en API-integraties zonder eigen pagina', + 'admin.addons.toast.updated': 'Add-on bijgewerkt', + 'admin.addons.toast.error': 'Add-on bijwerken mislukt', + 'admin.addons.noAddons': 'Geen add-ons beschikbaar', + 'admin.weather.title': 'Weergegevens', + 'admin.weather.badge': 'Sinds 24 maart 2026', + 'admin.weather.description': + 'TREK gebruikt Open-Meteo als weerbron. Open-Meteo is een gratis, open-source weerdienst — geen API-sleutel vereist.', + 'admin.weather.forecast': '16-daagse voorspelling', + 'admin.weather.forecastDesc': 'Voorheen 5 dagen (OpenWeatherMap)', + 'admin.weather.climate': 'Historische klimaatgegevens', + 'admin.weather.climateDesc': + 'Gemiddelden over de afgelopen 85 jaar voor dagen buiten de 16-daagse voorspelling', + 'admin.weather.requests': '10.000 verzoeken / dag', + 'admin.weather.requestsDesc': 'Gratis, geen API-sleutel vereist', + 'admin.weather.locationHint': + 'Het weer is gebaseerd op de eerste plaats met coördinaten op elke dag. Als er geen plaats aan een dag is toegewezen, wordt een plaats uit de lijst als referentie gebruikt.', + 'admin.tabs.mcpTokens': 'MCP-toegang', + 'admin.mcpTokens.title': 'MCP-toegang', + 'admin.mcpTokens.subtitle': + 'OAuth-sessies en API-tokens van alle gebruikers beheren', + 'admin.mcpTokens.sectionTitle': 'API-tokens', + 'admin.mcpTokens.owner': 'Eigenaar', + 'admin.mcpTokens.tokenName': 'Tokennaam', + 'admin.mcpTokens.created': 'Aangemaakt', + 'admin.mcpTokens.lastUsed': 'Laatst gebruikt', + 'admin.mcpTokens.never': 'Nooit', + 'admin.mcpTokens.empty': 'Er zijn nog geen MCP-tokens aangemaakt', + 'admin.mcpTokens.deleteTitle': 'Token verwijderen', + 'admin.mcpTokens.deleteMessage': + 'Dit token wordt onmiddellijk ingetrokken. De gebruiker verliest MCP-toegang via dit token.', + 'admin.mcpTokens.deleteSuccess': 'Token verwijderd', + 'admin.mcpTokens.deleteError': 'Token kon niet worden verwijderd', + 'admin.mcpTokens.loadError': 'Tokens konden niet worden geladen', + 'admin.oauthSessions.sectionTitle': 'OAuth-sessies', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Eigenaar', + 'admin.oauthSessions.scopes': 'Rechten', + 'admin.oauthSessions.created': 'Aangemaakt', + 'admin.oauthSessions.empty': 'Geen actieve OAuth-sessies', + 'admin.oauthSessions.revokeTitle': 'Sessie intrekken', + 'admin.oauthSessions.revokeMessage': + 'Deze OAuth-sessie wordt onmiddellijk ingetrokken. De client verliest MCP-toegang.', + 'admin.oauthSessions.revokeSuccess': 'Sessie ingetrokken', + 'admin.oauthSessions.revokeError': 'Sessie kon niet worden ingetrokken', + 'admin.oauthSessions.loadError': 'OAuth-sessies konden niet worden geladen', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Beveiligingsgevoelige en beheerdersgebeurtenissen (back-ups, gebruikers, MFA, instellingen).', + 'admin.audit.empty': 'Nog geen auditregistraties.', + 'admin.audit.refresh': 'Vernieuwen', + 'admin.audit.loadMore': 'Meer laden', + 'admin.audit.showing': '{count} geladen · {total} totaal', + 'admin.audit.col.time': 'Tijd', + 'admin.audit.col.user': 'Gebruiker', + 'admin.audit.col.action': 'Actie', + 'admin.audit.col.resource': 'Bron', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Details', + 'admin.github.title': 'Release-geschiedenis', + 'admin.github.subtitle': 'Laatste updates van {repo}', + 'admin.github.latest': 'Nieuwste', + 'admin.github.prerelease': 'Pre-release', + 'admin.github.showDetails': 'Details tonen', + 'admin.github.hideDetails': 'Details verbergen', + 'admin.github.loadMore': 'Meer laden', + 'admin.github.loading': 'Laden...', + 'admin.github.support': 'Helpt mij TREK verder te ontwikkelen', + 'admin.github.error': 'Releases laden mislukt', + 'admin.github.by': 'door', + 'admin.update.available': 'Update beschikbaar', + 'admin.update.text': 'TREK {version} is beschikbaar. Je draait {current}.', + 'admin.update.button': 'Bekijk op GitHub', + 'admin.update.install': 'Update installeren', + 'admin.update.confirmTitle': 'Update installeren?', + 'admin.update.confirmText': + 'TREK wordt bijgewerkt van {current} naar {version}. De server herstart automatisch.', + 'admin.update.dataInfo': + 'Al je gegevens (reizen, gebruikers, API-sleutels, uploads, Vacay, Atlas, budgetten) worden bewaard.', + 'admin.update.warning': + 'De app is kort niet beschikbaar tijdens het herstarten.', + 'admin.update.confirm': 'Nu bijwerken', + 'admin.update.installing': 'Bijwerken…', + 'admin.update.success': 'Update geïnstalleerd! Server herstart…', + 'admin.update.failed': 'Update mislukt', + 'admin.update.backupHint': + 'We raden aan een back-up te maken voordat je bijwerkt.', + 'admin.update.backupLink': 'Naar back-up', + 'admin.update.howTo': 'Hoe bij te werken', + 'admin.update.dockerText': + "Je TREK-instantie draait in Docker. Om bij te werken naar {version}, voer de volgende commando's uit op je server:", + 'admin.update.reloadHint': 'Herlaad de pagina over een paar seconden.', + 'admin.tabs.permissions': 'Rechten', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'In-app-meldingen zijn altijd actief en kunnen niet globaal worden uitgeschakeld.', + 'admin.notifications.adminWebhookPanel.title': 'Admin-webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'Deze webhook wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Hij staat los van gebruikerswebhooks en verstuurt automatisch als er een URL is ingesteld.', + 'admin.notifications.adminWebhookPanel.saved': 'Admin-webhook-URL opgeslagen', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Test-webhook succesvol verzonden', + 'admin.notifications.adminWebhookPanel.testFailed': 'Test-webhook mislukt', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin-webhook verstuurt automatisch als er een URL is ingesteld', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Hiermee kunnen gebruikers hun eigen ntfy-onderwerpen instellen voor pushmeldingen. Stel de standaardserver hieronder in om de gebruikersinstellingen vooraf in te vullen.', + 'admin.notifications.testNtfy': 'Test-Ntfy verzenden', + 'admin.notifications.testNtfySuccess': 'Test-Ntfy succesvol verzonden', + 'admin.notifications.testNtfyFailed': 'Test-Ntfy mislukt', + 'admin.notifications.adminNtfyPanel.title': 'Admin-Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Dit Ntfy-onderwerp wordt uitsluitend gebruikt voor admin-meldingen (bijv. versie-updates). Het staat los van onderwerpen per gebruiker en verstuurt altijd wanneer het geconfigureerd is.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy-server-URL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Wordt ook gebruikt als standaardserver voor ntfy-meldingen van gebruikers. Laat leeg om ntfy.sh te gebruiken. Gebruikers kunnen dit aanpassen in hun eigen instellingen.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin-onderwerp', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Toegangstoken (optioneel)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Admin-toegangstoken gewist', + 'admin.notifications.adminNtfyPanel.saved': + 'Admin-Ntfy-instellingen opgeslagen', + 'admin.notifications.adminNtfyPanel.test': 'Test-Ntfy verzenden', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Test-Ntfy succesvol verzonden', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test-Ntfy mislukt', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin-Ntfy verstuurt altijd wanneer een onderwerp is geconfigureerd', + 'admin.notifications.adminNotificationsHint': + 'Stel in via welke kanalen admin-meldingen worden bezorgd (bijv. versie-updates). De webhook verstuurt automatisch als er een admin-webhook-URL is ingesteld.', + 'admin.notifications.tripReminders.title': 'Reisherinneringen', + 'admin.notifications.tripReminders.hint': + 'Stuurt een herinneringsmelding voor de start van een reis (vereist ingestelde herinneringsdagen bij de reis).', + 'admin.notifications.tripReminders.enabled': 'Reisherinneringen ingeschakeld', + 'admin.notifications.tripReminders.disabled': + 'Reisherinneringen uitgeschakeld', + 'admin.tabs.notifications': 'Meldingen', + 'admin.addons.catalog.journey.name': 'Reisverslag', + 'admin.addons.catalog.journey.description': + "Reistracking & reisdagboek met check-ins, foto's en dagelijkse verhalen", +}; +export default admin; diff --git a/shared/src/i18n/nl/airport.ts b/shared/src/i18n/nl/airport.ts new file mode 100644 index 00000000..84912b32 --- /dev/null +++ b/shared/src/i18n/nl/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Luchthavencode of stad (bijv. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/nl/atlas.ts b/shared/src/i18n/nl/atlas.ts new file mode 100644 index 00000000..49148f31 --- /dev/null +++ b/shared/src/i18n/nl/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Je reisvoetafdruk over de wereld', + 'atlas.countries': 'Landen', + 'atlas.trips': 'Reizen', + 'atlas.places': 'Plaatsen', + 'atlas.days': 'Dagen', + 'atlas.visitedCountries': 'Bezochte landen', + 'atlas.cities': 'Steden', + 'atlas.noData': 'Nog geen reisgegevens', + 'atlas.noDataHint': + 'Maak een reis aan en voeg plaatsen toe om je wereldkaart te zien', + 'atlas.lastTrip': 'Laatste reis', + 'atlas.nextTrip': 'Volgende reis', + 'atlas.daysLeft': 'dagen te gaan', + 'atlas.streak': 'Reeks', + 'atlas.year': 'jaar', + 'atlas.years': 'jaar', + 'atlas.yearInRow': 'jaar op rij', + 'atlas.yearsInRow': 'jaar op rij', + 'atlas.tripIn': 'reis in', + 'atlas.tripsIn': 'reizen in', + 'atlas.since': 'sinds', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Azië', + 'atlas.northAmerica': 'N.-Amerika', + 'atlas.southAmerica': 'Z.-Amerika', + 'atlas.africa': 'Afrika', + 'atlas.oceania': 'Oceanië', + 'atlas.other': 'Overig', + 'atlas.firstVisit': 'Eerste reis', + 'atlas.lastVisitLabel': 'Laatste reis', + 'atlas.tripSingular': 'Reis', + 'atlas.tripPlural': 'Reizen', + 'atlas.placeVisited': 'Bezochte plaats', + 'atlas.placesVisited': 'Bezochte plaatsen', + 'atlas.statsTab': 'Statistieken', + 'atlas.bucketTab': 'Bucketlist', + 'atlas.addBucket': 'Toevoegen aan bucket list', + 'atlas.bucketNamePlaceholder': 'Plaats of bestemming...', + 'atlas.bucketNotesPlaceholder': 'Notities (optioneel)', + 'atlas.bucketEmpty': 'Je bucket list is leeg', + 'atlas.bucketEmptyHint': 'Voeg plekken toe die je wilt bezoeken', + 'atlas.unmark': 'Verwijderen', + 'atlas.confirmMark': 'Dit land als bezocht markeren?', + 'atlas.confirmUnmark': 'Dit land van je bezochte lijst verwijderen?', + 'atlas.confirmUnmarkRegion': 'Deze regio van je bezochte lijst verwijderen?', + 'atlas.markVisited': 'Markeren als bezocht', + 'atlas.markVisitedHint': 'Dit land toevoegen aan je bezochte lijst', + 'atlas.markRegionVisitedHint': 'Deze regio toevoegen aan je bezochte lijst', + 'atlas.addToBucket': 'Aan bucket list toevoegen', + 'atlas.addPoi': 'Plaats toevoegen', + 'atlas.searchCountry': 'Zoek een land...', + 'atlas.month': 'Maand', + 'atlas.addToBucketHint': 'Opslaan als plek die je wilt bezoeken', + 'atlas.bucketWhen': 'Wanneer ben je van plan te gaan?', +}; +export default atlas; diff --git a/shared/src/i18n/nl/backup.ts b/shared/src/i18n/nl/backup.ts new file mode 100644 index 00000000..3657df02 --- /dev/null +++ b/shared/src/i18n/nl/backup.ts @@ -0,0 +1,78 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Gegevensback-up', + 'backup.subtitle': 'Database en alle geüploade bestanden', + 'backup.refresh': 'Vernieuwen', + 'backup.upload': 'Back-up uploaden', + 'backup.uploading': 'Uploaden…', + 'backup.create': 'Back-up aanmaken', + 'backup.creating': 'Aanmaken…', + 'backup.empty': 'Nog geen back-ups', + 'backup.createFirst': 'Eerste back-up aanmaken', + 'backup.download': 'Downloaden', + 'backup.restore': 'Herstellen', + 'backup.confirm.restore': + 'Back-up "{name}" herstellen?\n\nAlle huidige gegevens worden vervangen door de back-up.', + 'backup.confirm.uploadRestore': + 'Back-upbestand "{name}" uploaden en herstellen?\n\nAlle huidige gegevens worden overschreven.', + 'backup.confirm.delete': 'Back-up "{name}" verwijderen?', + 'backup.toast.loadError': 'Back-ups laden mislukt', + 'backup.toast.created': 'Back-up succesvol aangemaakt', + 'backup.toast.createError': 'Back-up aanmaken mislukt', + 'backup.toast.restored': 'Back-up hersteld. Pagina wordt herladen…', + 'backup.toast.restoreError': 'Herstellen mislukt', + 'backup.toast.uploadError': 'Uploaden mislukt', + 'backup.toast.deleted': 'Back-up verwijderd', + 'backup.toast.deleteError': 'Verwijderen mislukt', + 'backup.toast.downloadError': 'Downloaden mislukt', + 'backup.toast.settingsSaved': 'Auto-back-up-instellingen opgeslagen', + 'backup.toast.settingsError': 'Instellingen opslaan mislukt', + 'backup.auto.title': 'Auto-back-up', + 'backup.auto.subtitle': 'Automatische back-up volgens schema', + 'backup.auto.enable': 'Auto-back-up inschakelen', + 'backup.auto.enableHint': + 'Back-ups worden automatisch aangemaakt volgens het gekozen schema', + 'backup.auto.interval': 'Interval', + 'backup.auto.hour': 'Uitvoeren om', + 'backup.auto.hourHint': 'Lokale servertijd ({format}-notatie)', + 'backup.auto.dayOfWeek': 'Dag van de week', + 'backup.auto.dayOfMonth': 'Dag van de maand', + 'backup.auto.dayOfMonthHint': + 'Beperkt tot 1–28 voor compatibiliteit met alle maanden', + 'backup.auto.scheduleSummary': 'Planning', + 'backup.auto.summaryDaily': 'Elke dag om {hour}:00', + 'backup.auto.summaryWeekly': 'Elke {day} om {hour}:00', + 'backup.auto.summaryMonthly': 'Dag {day} van elke maand om {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Auto-back-up is geconfigureerd via Docker-omgevingsvariabelen. Pas je docker-compose.yml aan en herstart de container om deze instellingen te wijzigen.', + 'backup.auto.copyEnv': 'Docker-omgevingsvariabelen kopiëren', + 'backup.auto.envCopied': + 'Docker-omgevingsvariabelen gekopieerd naar klembord', + 'backup.auto.keepLabel': 'Oude back-ups verwijderen na', + 'backup.dow.sunday': 'Zo', + 'backup.dow.monday': 'Ma', + 'backup.dow.tuesday': 'Di', + 'backup.dow.wednesday': 'Wo', + 'backup.dow.thursday': 'Do', + 'backup.dow.friday': 'Vr', + 'backup.dow.saturday': 'Za', + 'backup.interval.hourly': 'Elk uur', + 'backup.interval.daily': 'Dagelijks', + 'backup.interval.weekly': 'Wekelijks', + 'backup.interval.monthly': 'Maandelijks', + 'backup.keep.1day': '1 dag', + 'backup.keep.3days': '3 dagen', + 'backup.keep.7days': '7 dagen', + 'backup.keep.14days': '14 dagen', + 'backup.keep.30days': '30 dagen', + 'backup.keep.forever': 'Voor altijd bewaren', + 'backup.restoreConfirmTitle': 'Back-up herstellen?', + 'backup.restoreWarning': + 'Alle huidige gegevens (reizen, plaatsen, gebruikers, uploads) worden permanent vervangen door de back-up. Deze actie kan niet ongedaan worden gemaakt.', + 'backup.restoreTip': + 'Tip: Maak een back-up van de huidige status voordat je herstelt.', + 'backup.restoreConfirm': 'Ja, herstellen', +}; +export default backup; diff --git a/shared/src/i18n/nl/budget.ts b/shared/src/i18n/nl/budget.ts new file mode 100644 index 00000000..d8b47c8d --- /dev/null +++ b/shared/src/i18n/nl/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'CSV exporteren', + 'budget.emptyTitle': 'Nog geen budget aangemaakt', + 'budget.emptyText': + 'Maak categorieën en invoeren aan om je reisbudget te plannen', + 'budget.emptyPlaceholder': 'Categorienaam invoeren...', + 'budget.createCategory': 'Categorie aanmaken', + 'budget.category': 'Categorie', + 'budget.categoryName': 'Categorienaam', + 'budget.table.name': 'Naam', + 'budget.table.total': 'Totaal', + 'budget.table.persons': 'Personen', + 'budget.table.days': 'Dagen', + 'budget.table.perPerson': 'Per persoon', + 'budget.table.perDay': 'Per dag', + 'budget.table.perPersonDay': 'P. p. / dag', + 'budget.table.note': 'Notitie', + 'budget.table.date': 'Datum', + 'budget.newEntry': 'Nieuwe invoer', + 'budget.defaultEntry': 'Nieuwe invoer', + 'budget.defaultCategory': 'Nieuwe categorie', + 'budget.total': 'Totaal', + 'budget.totalBudget': 'Totaal budget', + 'budget.byCategory': 'Per categorie', + 'budget.editTooltip': 'Klik om te bewerken', + 'budget.linkedToReservation': + 'Gekoppeld aan een reservering — bewerk de naam daar', + 'budget.confirm.deleteCategory': + 'Weet je zeker dat je de categorie "{name}" met {count} invoeren wilt verwijderen?', + 'budget.deleteCategory': 'Categorie verwijderen', + 'budget.perPerson': 'Per persoon', + 'budget.paid': 'Betaald', + 'budget.open': 'Openstaand', + 'budget.noMembers': 'Geen leden toegewezen', + 'budget.settlement': 'Afrekening', + 'budget.settlementInfo': + 'Klik op de avatar van een lid bij een budgetpost om deze groen te markeren — dit betekent dat diegene heeft betaald. De afrekening toont vervolgens wie wie hoeveel verschuldigd is.', + 'budget.netBalances': 'Nettosaldi', + 'budget.categoriesLabel': 'categorieën', +}; +export default budget; diff --git a/shared/src/i18n/nl/categories.ts b/shared/src/i18n/nl/categories.ts new file mode 100644 index 00000000..37b17eb0 --- /dev/null +++ b/shared/src/i18n/nl/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categorieën', + 'categories.subtitle': 'Beheer categorieën voor plaatsen', + 'categories.new': 'Nieuwe categorie', + 'categories.empty': 'Nog geen categorieën', + 'categories.namePlaceholder': 'Categorienaam', + 'categories.icon': 'Pictogram', + 'categories.color': 'Kleur', + 'categories.customColor': 'Kies een aangepaste kleur', + 'categories.preview': 'Voorbeeld', + 'categories.defaultName': 'Categorie', + 'categories.update': 'Bijwerken', + 'categories.create': 'Aanmaken', + 'categories.confirm.delete': + 'Categorie verwijderen? Plaatsen in deze categorie worden niet verwijderd.', + 'categories.toast.loadError': 'Categorieën laden mislukt', + 'categories.toast.nameRequired': 'Voer een naam in', + 'categories.toast.updated': 'Categorie bijgewerkt', + 'categories.toast.created': 'Categorie aangemaakt', + 'categories.toast.saveError': 'Opslaan mislukt', + 'categories.toast.deleted': 'Categorie verwijderd', + 'categories.toast.deleteError': 'Verwijderen mislukt', +}; +export default categories; diff --git a/shared/src/i18n/nl/collab.ts b/shared/src/i18n/nl/collab.ts new file mode 100644 index 00000000..61247230 --- /dev/null +++ b/shared/src/i18n/nl/collab.ts @@ -0,0 +1,73 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Notities', + 'collab.tabs.polls': 'Polls', + 'collab.whatsNext.title': 'Wat komt er', + 'collab.whatsNext.today': 'Vandaag', + 'collab.whatsNext.tomorrow': 'Morgen', + 'collab.whatsNext.empty': 'Geen komende activiteiten', + 'collab.whatsNext.until': 'tot', + 'collab.whatsNext.emptyHint': 'Activiteiten met tijden verschijnen hier', + 'collab.chat.send': 'Verzenden', + 'collab.chat.placeholder': 'Typ een bericht...', + 'collab.chat.empty': 'Start het gesprek', + 'collab.chat.emptyHint': 'Berichten worden gedeeld met alle reisleden', + 'collab.chat.emptyDesc': 'Deel ideeën, plannen en updates met je reisgroep', + 'collab.chat.today': 'Vandaag', + 'collab.chat.yesterday': 'Gisteren', + 'collab.chat.deletedMessage': 'heeft een bericht verwijderd', + 'collab.chat.reply': 'Beantwoorden', + 'collab.chat.loadMore': 'Oudere berichten laden', + 'collab.chat.justNow': 'zojuist', + 'collab.chat.minutesAgo': '{n} min. geleden', + 'collab.chat.hoursAgo': '{n} uur geleden', + 'collab.notes.title': 'Notities', + 'collab.notes.new': 'Nieuwe notitie', + 'collab.notes.empty': 'Nog geen notities', + 'collab.notes.emptyHint': 'Begin met het vastleggen van ideeën en plannen', + 'collab.notes.all': 'Alle', + 'collab.notes.titlePlaceholder': 'Notitietitel', + 'collab.notes.contentPlaceholder': 'Schrijf iets...', + 'collab.notes.categoryPlaceholder': 'Categorie', + 'collab.notes.newCategory': 'Nieuwe categorie...', + 'collab.notes.category': 'Categorie', + 'collab.notes.noCategory': 'Geen categorie', + 'collab.notes.color': 'Kleur', + 'collab.notes.save': 'Opslaan', + 'collab.notes.cancel': 'Annuleren', + 'collab.notes.edit': 'Bewerken', + 'collab.notes.delete': 'Verwijderen', + 'collab.notes.pin': 'Vastpinnen', + 'collab.notes.unpin': 'Losmaken', + 'collab.notes.daysAgo': '{n}d geleden', + 'collab.notes.categorySettings': 'Categorieën beheren', + 'collab.notes.create': 'Aanmaken', + 'collab.notes.website': 'Website', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Bestanden bijvoegen', + 'collab.notes.noCategoriesYet': 'Nog geen categorieën', + 'collab.notes.emptyDesc': 'Maak een notitie om te beginnen', + 'collab.polls.title': 'Polls', + 'collab.polls.new': 'Nieuwe poll', + 'collab.polls.empty': 'Nog geen polls', + 'collab.polls.emptyHint': 'Stel de groep een vraag en stem samen', + 'collab.polls.question': 'Vraag', + 'collab.polls.questionPlaceholder': 'Wat zullen we doen?', + 'collab.polls.addOption': '+ Optie toevoegen', + 'collab.polls.optionPlaceholder': 'Optie {n}', + 'collab.polls.create': 'Poll aanmaken', + 'collab.polls.close': 'Sluiten', + 'collab.polls.closed': 'Gesloten', + 'collab.polls.votes': '{n} stemmen', + 'collab.polls.vote': '{n} stem', + 'collab.polls.multipleChoice': 'Meerkeuze', + 'collab.polls.multiChoice': 'Meerkeuze', + 'collab.polls.deadline': 'Deadline', + 'collab.polls.option': 'Optie', + 'collab.polls.options': 'Opties', + 'collab.polls.delete': 'Verwijderen', + 'collab.polls.closedSection': 'Gesloten', +}; +export default collab; diff --git a/shared/src/i18n/nl/common.ts b/shared/src/i18n/nl/common.ts new file mode 100644 index 00000000..c7aa8173 --- /dev/null +++ b/shared/src/i18n/nl/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Opslaan', + 'common.showMore': 'Meer tonen', + 'common.showLess': 'Minder tonen', + 'common.cancel': 'Annuleren', + 'common.clear': 'Wissen', + 'common.delete': 'Verwijderen', + 'common.edit': 'Bewerken', + 'common.add': 'Toevoegen', + 'common.loading': 'Laden...', + 'common.import': 'Importeren', + 'common.select': 'Selecteren', + 'common.selectAll': 'Alles selecteren', + 'common.deselectAll': 'Alles deselecteren', + 'common.error': 'Fout', + 'common.unknownError': 'Onbekende fout', + 'common.tooManyAttempts': 'Te veel pogingen. Probeer het later opnieuw.', + 'common.back': 'Terug', + 'common.all': 'Alles', + 'common.close': 'Sluiten', + 'common.open': 'Openen', + 'common.upload': 'Uploaden', + 'common.search': 'Zoeken', + 'common.confirm': 'Bevestigen', + 'common.ok': 'OK', + 'common.yes': 'Ja', + 'common.no': 'Nee', + 'common.or': 'of', + 'common.none': 'Geen', + 'common.date': 'Datum', + 'common.rename': 'Hernoemen', + 'common.discardChanges': 'Wijzigingen verwerpen', + 'common.discard': 'Verwerpen', + 'common.name': 'Naam', + 'common.email': 'E-mail', + 'common.password': 'Wachtwoord', + 'common.saving': 'Opslaan...', + 'common.saved': 'Opgeslagen', + 'common.expand': 'Uitvouwen', + 'common.collapse': 'Inklappen', + 'common.update': 'Bijwerken', + 'common.change': 'Wijzigen', + 'common.uploading': 'Uploaden…', + 'common.backToPlanning': 'Terug naar planning', + 'common.reset': 'Resetten', + 'common.copy': 'Kopiëren', + 'common.copied': 'Gekopieerd', + 'common.justNow': 'zojuist', + 'common.hoursAgo': '{count}u geleden', + 'common.daysAgo': '{count}d geleden', +}; +export default common; diff --git a/shared/src/i18n/nl/dashboard.ts b/shared/src/i18n/nl/dashboard.ts new file mode 100644 index 00000000..911573d7 --- /dev/null +++ b/shared/src/i18n/nl/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Mijn reizen', + 'dashboard.subtitle.loading': 'Reizen laden...', + 'dashboard.subtitle.trips': '{count} reizen ({archived} gearchiveerd)', + 'dashboard.subtitle.empty': 'Begin je eerste reis', + 'dashboard.subtitle.activeOne': '{count} actieve reis', + 'dashboard.subtitle.activeMany': '{count} actieve reizen', + 'dashboard.subtitle.archivedSuffix': ' · {count} gearchiveerd', + 'dashboard.newTrip': 'Nieuwe reis', + 'dashboard.gridView': 'Rasterweergave', + 'dashboard.listView': 'Lijstweergave', + 'dashboard.currency': 'Valuta', + 'dashboard.timezone': 'Tijdzones', + 'dashboard.localTime': 'Lokaal', + 'dashboard.timezoneCustomTitle': 'Aangepaste tijdzone', + 'dashboard.timezoneCustomLabelPlaceholder': 'Label (optioneel)', + 'dashboard.timezoneCustomTzPlaceholder': 'bijv. America/New_York', + 'dashboard.timezoneCustomAdd': 'Toevoegen', + 'dashboard.timezoneCustomErrorEmpty': 'Voer een tijdzone-identificatie in', + 'dashboard.timezoneCustomErrorInvalid': + 'Ongeldige tijdzone. Gebruik een formaat zoals Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Al toegevoegd', + 'dashboard.emptyTitle': 'Nog geen reizen', + 'dashboard.emptyText': 'Maak je eerste reis aan en begin met plannen!', + 'dashboard.emptyButton': 'Eerste reis aanmaken', + 'dashboard.nextTrip': 'Volgende reis', + 'dashboard.shared': 'Gedeeld', + 'dashboard.sharedBy': 'Gedeeld door {name}', + 'dashboard.days': 'Dagen', + 'dashboard.places': 'Plaatsen', + 'dashboard.members': 'Reisgenoten', + 'dashboard.archive': 'Archiveren', + 'dashboard.copyTrip': 'Kopiëren', + 'dashboard.copySuffix': 'kopie', + 'dashboard.restore': 'Herstellen', + 'dashboard.archived': 'Gearchiveerd', + 'dashboard.status.ongoing': 'Lopend', + 'dashboard.status.today': 'Vandaag', + 'dashboard.status.tomorrow': 'Morgen', + 'dashboard.status.past': 'Afgelopen', + 'dashboard.status.daysLeft': 'nog {count} dagen', + 'dashboard.toast.loadError': 'Reizen laden mislukt', + 'dashboard.toast.created': 'Reis aangemaakt!', + 'dashboard.toast.createError': 'Reis aanmaken mislukt', + 'dashboard.toast.updated': 'Reis bijgewerkt!', + 'dashboard.toast.updateError': 'Reis bijwerken mislukt', + 'dashboard.toast.deleted': 'Reis verwijderd', + 'dashboard.toast.deleteError': 'Reis verwijderen mislukt', + 'dashboard.toast.archived': 'Reis gearchiveerd', + 'dashboard.toast.archiveError': 'Reis archiveren mislukt', + 'dashboard.toast.restored': 'Reis hersteld', + 'dashboard.toast.restoreError': 'Reis herstellen mislukt', + 'dashboard.toast.copied': 'Reis gekopieerd!', + 'dashboard.toast.copyError': 'Reis kopiëren mislukt', + 'dashboard.confirm.delete': + 'Reis "{title}" verwijderen? Alle plaatsen en plannen worden permanent verwijderd.', + 'dashboard.editTrip': 'Reis bewerken', + 'dashboard.createTrip': 'Nieuwe reis aanmaken', + 'dashboard.tripTitle': 'Titel', + 'dashboard.tripTitlePlaceholder': 'bijv. Zomer in Japan', + 'dashboard.tripDescription': 'Beschrijving', + 'dashboard.tripDescriptionPlaceholder': 'Waar gaat deze reis over?', + 'dashboard.startDate': 'Startdatum', + 'dashboard.endDate': 'Einddatum', + 'dashboard.dayCount': 'Aantal dagen', + 'dashboard.dayCountHint': + 'Hoeveel dagen te plannen wanneer er geen reisdata zijn ingesteld.', + 'dashboard.noDateHint': + 'Geen datum ingesteld — er worden standaard 7 dagen aangemaakt. Je kunt dit altijd wijzigen.', + 'dashboard.coverImage': 'Omslagafbeelding', + 'dashboard.addCoverImage': 'Omslagafbeelding toevoegen', + 'dashboard.addMembers': 'Reisgenoten', + 'dashboard.addMember': 'Lid toevoegen', + 'dashboard.coverSaved': 'Omslagafbeelding opgeslagen', + 'dashboard.coverUploadError': 'Uploaden mislukt', + 'dashboard.coverRemoveError': 'Verwijderen mislukt', + 'dashboard.titleRequired': 'Titel is verplicht', + 'dashboard.endDateError': 'Einddatum moet na de startdatum liggen', + 'dashboard.greeting.morning': 'Goedemorgen,', + 'dashboard.greeting.afternoon': 'Goedemiddag,', + 'dashboard.greeting.evening': 'Goedenavond,', + 'dashboard.mobile.liveNow': 'Nu live', + 'dashboard.mobile.tripProgress': 'Reisvoortgang', + 'dashboard.mobile.daysLeft': '{count} dagen over', + 'dashboard.mobile.places': 'Plaatsen', + 'dashboard.mobile.buddies': 'Reisgenoten', + 'dashboard.mobile.newTrip': 'Nieuwe reis', + 'dashboard.mobile.currency': 'Valuta', + 'dashboard.mobile.timezone': 'Tijdzone', + 'dashboard.mobile.upcomingTrips': 'Aankomende reizen', + 'dashboard.mobile.yourTrips': 'Jouw reizen', + 'dashboard.mobile.trips': 'reizen', + 'dashboard.mobile.starts': 'Begint', + 'dashboard.mobile.duration': 'Duur', + 'dashboard.mobile.day': 'dag', + 'dashboard.mobile.days': 'dagen', + 'dashboard.mobile.ongoing': 'Bezig', + 'dashboard.mobile.startsToday': 'Begint vandaag', + 'dashboard.mobile.tomorrow': 'Morgen', + 'dashboard.mobile.inDays': 'Over {count} dagen', + 'dashboard.mobile.inMonths': 'Over {count} maanden', + 'dashboard.mobile.completed': 'Voltooid', + 'dashboard.mobile.currencyConverter': 'Valutaomrekener', +}; +export default dashboard; diff --git a/shared/src/i18n/nl/day.ts b/shared/src/i18n/nl/day.ts new file mode 100644 index 00000000..055faae1 --- /dev/null +++ b/shared/src/i18n/nl/day.ts @@ -0,0 +1,27 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Regenkans', + 'day.precipitation': 'Neerslag', + 'day.wind': 'Wind', + 'day.sunrise': 'Zonsopgang', + 'day.sunset': 'Zonsondergang', + 'day.hourlyForecast': 'Uurlijkse voorspelling', + 'day.climateHint': + 'Historische gemiddelden — echte voorspelling beschikbaar binnen 16 dagen voor deze datum.', + 'day.noWeather': + 'Geen weergegevens beschikbaar. Voeg een plaats met coördinaten toe.', + 'day.overview': 'Dagoverzicht', + 'day.accommodation': 'Accommodatie', + 'day.addAccommodation': 'Accommodatie toevoegen', + 'day.hotelDayRange': 'Toepassen op dagen', + 'day.noPlacesForHotel': 'Voeg eerst plaatsen toe aan je reis', + 'day.allDays': 'Alle', + 'day.checkIn': 'Inchecken', + 'day.checkInUntil': 'Tot', + 'day.checkOut': 'Uitchecken', + 'day.confirmation': 'Bevestiging', + 'day.editAccommodation': 'Accommodatie bewerken', + 'day.reservations': 'Reserveringen', +}; +export default day; diff --git a/shared/src/i18n/nl/dayplan.ts b/shared/src/i18n/nl/dayplan.ts new file mode 100644 index 00000000..e21bfcd2 --- /dev/null +++ b/shared/src/i18n/nl/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Kalender exporteren (ICS)', + 'dayplan.emptyDay': 'Geen plaatsen gepland voor deze dag', + 'dayplan.addNote': 'Notitie toevoegen', + 'dayplan.editNote': 'Notitie bewerken', + 'dayplan.noteAdd': 'Notitie toevoegen', + 'dayplan.noteEdit': 'Notitie bewerken', + 'dayplan.noteTitle': 'Notitie', + 'dayplan.noteSubtitle': 'Dagnotitie', + 'dayplan.totalCost': 'Totale kosten', + 'dayplan.days': 'Dagen', + 'dayplan.dayN': 'Dag {n}', + 'dayplan.calculating': 'Berekenen...', + 'dayplan.route': 'Route', + 'dayplan.optimize': 'Optimaliseren', + 'dayplan.optimized': 'Route geoptimaliseerd', + 'dayplan.routeError': 'Route berekenen mislukt', + 'dayplan.toast.needTwoPlaces': + 'Minimaal twee plaatsen nodig voor route-optimalisatie', + 'dayplan.toast.routeOptimized': 'Route geoptimaliseerd', + 'dayplan.toast.noGeoPlaces': + 'Geen plaatsen met coördinaten gevonden voor routeberekening', + 'dayplan.confirmed': 'Bevestigd', + 'dayplan.pendingRes': 'In behandeling', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Dagplan exporteren als PDF', + 'dayplan.pdfError': 'PDF-export mislukt', + 'dayplan.cannotReorderTransport': + 'Boekingen met een vast tijdstip kunnen niet worden verplaatst', + 'dayplan.confirmRemoveTimeTitle': 'Tijd verwijderen?', + 'dayplan.confirmRemoveTimeBody': + 'Deze plek heeft een vast tijdstip ({time}). Verplaatsen verwijdert het tijdstip en maakt vrije sortering mogelijk.', + 'dayplan.confirmRemoveTimeAction': 'Tijd verwijderen en verplaatsen', + 'dayplan.cannotDropOnTimed': + 'Items kunnen niet tussen tijdgebonden items worden geplaatst', + 'dayplan.cannotBreakChronology': + 'Dit zou de chronologische volgorde van geplande items en boekingen doorbreken', + 'dayplan.mobile.addPlace': 'Plaats toevoegen', + 'dayplan.mobile.searchPlaces': 'Plaatsen zoeken...', + 'dayplan.mobile.allAssigned': 'Alle plaatsen toegewezen', + 'dayplan.mobile.noMatch': 'Geen resultaat', + 'dayplan.mobile.createNew': 'Nieuwe plaats aanmaken', +}; +export default dayplan; diff --git a/shared/src/i18n/nl/externalNotifications.ts b/shared/src/i18n/nl/externalNotifications.ts new file mode 100644 index 00000000..5b9af4a1 --- /dev/null +++ b/shared/src/i18n/nl/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const nl: NotificationLocale = { + email: { + footer: 'Je ontvangt dit omdat je meldingen hebt ingeschakeld in TREK.', + manage: 'Voorkeuren beheren', + madeWith: 'Made with', + openTrek: 'TREK openen', + }, + events: { + trip_invite: (p) => ({ + title: `Uitnodiging voor "${p.trip}"`, + body: `${p.actor} heeft ${p.invitee || 'een lid'} uitgenodigd voor de reis "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nieuwe boeking: ${p.booking}`, + body: `${p.actor} heeft een boeking "${p.booking}" (${p.type}) toegevoegd aan "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Reisherinnering: ${p.trip}`, + body: `Je reis "${p.trip}" komt eraan!`, + }), + todo_due: (p) => ({ + title: `Taak verloopt: ${p.todo}`, + body: `"${p.todo}" in "${p.trip}" verloopt op ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion uitnodiging', + body: `${p.actor} nodigt je uit om vakantieplannen te fuseren. Open TREK om te accepteren of af te wijzen.`, + }), + photos_shared: (p) => ({ + title: `${p.count} foto's gedeeld`, + body: `${p.actor} heeft ${p.count} foto('s) gedeeld in "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nieuw bericht in "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Paklijst: ${p.category}`, + body: `${p.actor} heeft je toegewezen aan de categorie "${p.category}" in "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nieuwe TREK-versie beschikbaar', + body: `TREK ${p.version} is nu beschikbaar. Bezoek het beheerderspaneel om bij te werken.`, + }), + synology_session_cleared: () => ({ + title: 'Synology-sessie gewist', + body: 'Je Synology-account of URL is gewijzigd. Je bent uitgelogd bij Synology Photos.', + }), + }, + passwordReset: { + subject: 'Reset je wachtwoord', + greeting: 'Hallo', + body: 'We hebben een verzoek ontvangen om het wachtwoord voor je TREK-account te resetten. Klik op de knop hieronder om een nieuw wachtwoord in te stellen.', + ctaIntro: 'Wachtwoord resetten', + expiry: 'Deze link verloopt over 60 minuten.', + ignore: + 'Als jij dit niet hebt aangevraagd, kun je deze e-mail negeren — je wachtwoord blijft ongewijzigd.', + }, +}; + +export default nl; diff --git a/shared/src/i18n/nl/files.ts b/shared/src/i18n/nl/files.ts new file mode 100644 index 00000000..be63097e --- /dev/null +++ b/shared/src/i18n/nl/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Bestanden', + 'files.pageTitle': 'Bestanden en documenten', + 'files.subtitle': '{count} bestanden voor {trip}', + 'files.download': 'Downloaden', + 'files.openError': 'Bestand kon niet worden geopend', + 'files.downloadPdf': 'PDF downloaden', + 'files.count': '{count} bestanden', + 'files.countSingular': '1 bestand', + 'files.uploaded': '{count} geüpload', + 'files.uploadError': 'Uploaden mislukt', + 'files.dropzone': 'Sleep bestanden hierheen', + 'files.dropzoneHint': 'of klik om te bladeren', + 'files.allowedTypes': + 'Afbeeldingen, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Uploaden...', + 'files.filterAll': 'Alle', + 'files.filterPdf': "PDF's", + 'files.filterImages': 'Afbeeldingen', + 'files.filterDocs': 'Documenten', + 'files.filterCollab': 'Collab-notities', + 'files.sourceCollab': 'Uit Collab-notities', + 'files.empty': 'Nog geen bestanden', + 'files.emptyHint': 'Upload bestanden om ze aan je reis toe te voegen', + 'files.openTab': 'Openen in nieuw tabblad', + 'files.confirm.delete': 'Weet je zeker dat je dit bestand wilt verwijderen?', + 'files.toast.deleted': 'Bestand verwijderd', + 'files.toast.deleteError': 'Bestand verwijderen mislukt', + 'files.sourcePlan': 'Dagplan', + 'files.sourceBooking': 'Boeking', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Bijvoegen', + 'files.pasteHint': + 'Je kunt ook afbeeldingen plakken vanuit het klembord (Ctrl+V)', + 'files.trash': 'Prullenbak', + 'files.trashEmpty': 'Prullenbak is leeg', + 'files.emptyTrash': 'Prullenbak legen', + 'files.restore': 'Herstellen', + 'files.star': 'Ster', + 'files.unstar': 'Ster verwijderen', + 'files.assign': 'Toewijzen', + 'files.assignTitle': 'Bestand toewijzen', + 'files.assignPlace': 'Plaats', + 'files.assignBooking': 'Boeking', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Niet toegewezen', + 'files.unlink': 'Koppeling verwijderen', + 'files.toast.trashed': 'Naar prullenbak verplaatst', + 'files.toast.restored': 'Bestand hersteld', + 'files.toast.trashEmptied': 'Prullenbak geleegd', + 'files.toast.assigned': 'Bestand toegewezen', + 'files.toast.assignError': 'Toewijzing mislukt', + 'files.toast.restoreError': 'Herstellen mislukt', + 'files.confirm.permanentDelete': + 'Dit bestand permanent verwijderen? Dit kan niet ongedaan worden gemaakt.', + 'files.confirm.emptyTrash': + 'Alle bestanden in de prullenbak permanent verwijderen? Dit kan niet ongedaan worden gemaakt.', + 'files.noteLabel': 'Notitie', + 'files.notePlaceholder': 'Notitie toevoegen...', +}; +export default files; diff --git a/shared/src/i18n/nl/index.ts b/shared/src/i18n/nl/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/nl/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/nl/inspector.ts b/shared/src/i18n/nl/inspector.ts new file mode 100644 index 00000000..a7727eec --- /dev/null +++ b/shared/src/i18n/nl/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Openingstijden', + 'inspector.closed': 'Gesloten', + 'inspector.openingHours': 'Openingstijden', + 'inspector.showHours': 'Openingstijden tonen', + 'inspector.files': 'Bestanden', + 'inspector.filesCount': '{count} bestanden', + 'inspector.removeFromDay': 'Verwijderen van dag', + 'inspector.remove': 'Verwijderen', + 'inspector.addToDay': 'Toevoegen aan dag', + 'inspector.confirmedRes': 'Bevestigde reservering', + 'inspector.pendingRes': 'Reservering in behandeling', + 'inspector.google': 'Openen in Google Maps', + 'inspector.website': 'Website openen', + 'inspector.addRes': 'Reservering', + 'inspector.editRes': 'Reservering bewerken', + 'inspector.participants': 'Deelnemers', + 'inspector.trackStats': 'Routegegevens', +}; +export default inspector; diff --git a/shared/src/i18n/nl/journey.ts b/shared/src/i18n/nl/journey.ts new file mode 100644 index 00000000..82151595 --- /dev/null +++ b/shared/src/i18n/nl/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Reizen zoeken…', + 'journey.search.noResults': 'Geen reizen komen overeen met "{query}"', + 'journey.title': 'Reisverslag', + 'journey.subtitle': 'Leg je reizen vast terwijl je onderweg bent', + 'journey.new': 'Nieuw reisverslag', + 'journey.create': 'Aanmaken', + 'journey.titlePlaceholder': 'Waar ga je naartoe?', + 'journey.empty': 'Nog geen reisverslagen', + 'journey.emptyHint': 'Begin met het vastleggen van je volgende reis', + 'journey.deleted': 'Reisverslag verwijderd', + 'journey.createError': 'Kon reisverslag niet aanmaken', + 'journey.deleteError': 'Kon reisverslag niet verwijderen', + 'journey.deleteConfirmTitle': 'Verwijderen', + 'journey.deleteConfirmMessage': + '"{title}" verwijderen? Dit kan niet ongedaan worden gemaakt.', + 'journey.deleteConfirmGeneric': 'Weet je zeker dat je dit wilt verwijderen?', + 'journey.notFound': 'Reisverslag niet gevonden', + 'journey.photos': "Foto's", + 'journey.timelineEmpty': 'Nog geen stops', + 'journey.timelineEmptyHint': + 'Voeg een check-in toe of schrijf een dagboekvermelding om te beginnen', + 'journey.status.draft': 'Concept', + 'journey.status.active': 'Actief', + 'journey.status.completed': 'Voltooid', + 'journey.status.upcoming': 'Gepland', + 'journey.status.archived': 'Gearchiveerd', + 'journey.checkin.add': 'Inchecken', + 'journey.checkin.namePlaceholder': 'Locatienaam', + 'journey.checkin.notesPlaceholder': 'Notities (optioneel)', + 'journey.checkin.save': 'Opslaan', + 'journey.checkin.error': 'Kon check-in niet opslaan', + 'journey.entry.add': 'Dagboek', + 'journey.entry.edit': 'Vermelding bewerken', + 'journey.entry.titlePlaceholder': 'Titel (optioneel)', + 'journey.entry.bodyPlaceholder': 'Wat is er vandaag gebeurd?', + 'journey.entry.save': 'Opslaan', + 'journey.entry.error': 'Kon vermelding niet opslaan', + 'journey.photo.add': 'Foto', + 'journey.photo.uploadError': 'Uploaden mislukt', + 'journey.share.share': 'Delen', + 'journey.share.public': 'Openbaar', + 'journey.share.linkCopied': 'Openbare link gekopieerd', + 'journey.share.disabled': 'Openbaar delen uitgeschakeld', + 'journey.editor.titlePlaceholder': 'Geef dit moment een naam...', + 'journey.editor.bodyPlaceholder': 'Vertel het verhaal van deze dag...', + 'journey.editor.placePlaceholder': 'Locatie (optioneel)', + 'journey.editor.tagsPlaceholder': + 'Tags: verborgen parel, beste maaltijd, moet terugkomen...', + 'journey.visibility.private': 'Privé', + 'journey.visibility.shared': 'Gedeeld', + 'journey.visibility.public': 'Openbaar', + 'journey.emptyState.title': 'Je verhaal begint hier', + 'journey.emptyState.subtitle': + 'Check in op een plek of schrijf je eerste dagboekvermelding', + 'journey.frontpage.subtitle': + 'Maak van je reizen verhalen die je nooit vergeet', + 'journey.frontpage.createJourney': 'Reisverslag aanmaken', + 'journey.frontpage.activeJourney': 'Actief reisverslag', + 'journey.frontpage.allJourneys': 'Alle reisverslagen', + 'journey.frontpage.journeys': 'reisverslagen', + 'journey.frontpage.createNew': 'Nieuw reisverslag aanmaken', + 'journey.frontpage.createNewSub': + 'Kies reizen, schrijf verhalen, deel je avonturen', + 'journey.frontpage.live': 'Live', + 'journey.frontpage.synced': 'Gesynchroniseerd', + 'journey.frontpage.continueWriting': 'Verder schrijven', + 'journey.frontpage.updated': 'Bijgewerkt {time}', + 'journey.frontpage.suggestionLabel': 'Reis net afgelopen', + 'journey.frontpage.suggestionText': + 'Maak van {title} een reisverslag', + 'journey.frontpage.dismiss': 'Sluiten', + 'journey.frontpage.journeyName': 'Naam reisverslag', + 'journey.frontpage.namePlaceholder': 'bijv. Zuidoost-Azië 2026', + 'journey.frontpage.selectTrips': 'Selecteer reizen', + 'journey.frontpage.tripsSelected': 'reizen geselecteerd', + 'journey.frontpage.trips': 'reizen', + 'journey.frontpage.placesImported': 'plaatsen worden geïmporteerd', + 'journey.frontpage.places': 'plaatsen', + 'journey.detail.backToJourney': 'Terug naar reisverslag', + 'journey.detail.syncedWithTrips': 'Gesynchroniseerd met reizen', + 'journey.detail.addEntry': 'Vermelding toevoegen', + 'journey.detail.newEntry': 'Nieuwe vermelding', + 'journey.detail.editEntry': 'Vermelding bewerken', + 'journey.detail.noEntries': 'Nog geen vermeldingen', + 'journey.detail.noEntriesHint': + 'Voeg een reis toe om te beginnen met skeletvermeldingen', + 'journey.detail.noPhotos': "Nog geen foto's", + 'journey.detail.noPhotosHint': + "Upload foto's naar vermeldingen of blader door je Immich/Synology-bibliotheek", + 'journey.detail.journeyStats': 'Reisstatistieken', + 'journey.detail.syncedTrips': 'Gesynchroniseerde reizen', + 'journey.detail.noTripsLinked': 'Nog geen reizen gekoppeld', + 'journey.detail.contributors': 'Bijdragers', + 'journey.detail.readMore': 'Lees meer', + 'journey.detail.prosCons': 'Voor- & nadelen', + 'journey.detail.photos': "foto's", + 'journey.detail.day': 'Dag {number}', + 'journey.detail.places': 'plaatsen', + 'journey.stats.days': 'Dagen', + 'journey.stats.cities': 'Steden', + 'journey.stats.entries': 'Vermeldingen', + 'journey.stats.photos': "Foto's", + 'journey.stats.places': 'Plaatsen', + 'journey.skeletons.show': 'Suggesties tonen', + 'journey.skeletons.hide': 'Suggesties verbergen', + 'journey.verdict.lovedIt': 'Geweldig', + 'journey.verdict.couldBeBetter': 'Kan beter', + 'journey.synced.places': 'plaatsen', + 'journey.synced.synced': 'gesynchroniseerd', + 'journey.editor.discardChangesConfirm': + 'Je hebt niet-opgeslagen wijzigingen. Verwerpen?', + 'journey.editor.uploadFailed': 'Foto uploaden mislukt', + 'journey.editor.uploadPhotos': "Foto's uploaden", + 'journey.editor.uploading': 'Uploaden...', + 'journey.editor.uploadingProgress': 'Uploaden {done}/{total}…', + 'journey.editor.uploadPartialFailed': + "{failed} van {total} foto's mislukt — sla opnieuw op om het opnieuw te proberen", + 'journey.editor.fromGallery': 'Uit galerij', + 'journey.editor.allPhotosAdded': "Alle foto's al toegevoegd", + 'journey.editor.writeStory': 'Schrijf je verhaal...', + 'journey.editor.prosCons': 'Voor- & nadelen', + 'journey.editor.pros': 'Voordelen', + 'journey.editor.cons': 'Nadelen', + 'journey.editor.proPlaceholder': 'Iets geweldigs...', + 'journey.editor.conPlaceholder': 'Niet zo geweldig...', + 'journey.editor.addAnother': 'Nog een toevoegen', + 'journey.editor.date': 'Datum', + 'journey.editor.location': 'Locatie', + 'journey.editor.searchLocation': 'Locatie zoeken...', + 'journey.editor.mood': 'Stemming', + 'journey.editor.weather': 'Weer', + 'journey.editor.photoFirst': '1e', + 'journey.editor.makeFirst': 'Maak 1e', + 'journey.editor.searching': 'Zoeken...', + 'journey.mood.amazing': 'Fantastisch', + 'journey.mood.good': 'Goed', + 'journey.mood.neutral': 'Neutraal', + 'journey.mood.rough': 'Zwaar', + 'journey.weather.sunny': 'Zonnig', + 'journey.weather.partly': 'Halfbewolkt', + 'journey.weather.cloudy': 'Bewolkt', + 'journey.weather.rainy': 'Regenachtig', + 'journey.weather.stormy': 'Stormachtig', + 'journey.weather.cold': 'Sneeuw', + 'journey.trips.linkTrip': 'Reis koppelen', + 'journey.trips.searchTrip': 'Reis zoeken', + 'journey.trips.searchPlaceholder': 'Reisnaam of bestemming...', + 'journey.trips.noTripsAvailable': 'Geen reizen beschikbaar', + 'journey.trips.link': 'Koppelen', + 'journey.trips.tripLinked': 'Reis gekoppeld', + 'journey.trips.linkFailed': 'Koppelen van reis mislukt', + 'journey.trips.addTrip': 'Reis toevoegen', + 'journey.trips.unlinkTrip': 'Reis ontkoppelen', + 'journey.trips.unlinkMessage': + '"{title}" ontkoppelen? Alle gesynchroniseerde vermeldingen en foto\'s van deze reis worden permanent verwijderd. Dit kan niet ongedaan worden gemaakt.', + 'journey.trips.unlink': 'Ontkoppelen', + 'journey.trips.tripUnlinked': 'Reis ontkoppeld', + 'journey.trips.unlinkFailed': 'Ontkoppelen van reis mislukt', + 'journey.trips.noTripsLinkedSettings': 'Geen reizen gekoppeld', + 'journey.contributors.invite': 'Bijdrager uitnodigen', + 'journey.contributors.searchUser': 'Gebruiker zoeken', + 'journey.contributors.searchPlaceholder': 'Gebruikersnaam of e-mail...', + 'journey.contributors.noUsers': 'Geen gebruikers gevonden', + 'journey.contributors.role': 'Rol', + 'journey.contributors.added': 'Bijdrager toegevoegd', + 'journey.contributors.addFailed': 'Toevoegen van bijdrager mislukt', + 'journey.share.publicShare': 'Openbaar delen', + 'journey.share.createLink': 'Deellink aanmaken', + 'journey.share.linkCreated': 'Deellink aangemaakt', + 'journey.share.createFailed': 'Aanmaken van link mislukt', + 'journey.share.copy': 'Kopiëren', + 'journey.share.copied': 'Gekopieerd!', + 'journey.share.timeline': 'Tijdlijn', + 'journey.share.gallery': 'Galerij', + 'journey.share.map': 'Kaart', + 'journey.share.removeLink': 'Deellink verwijderen', + 'journey.share.linkDeleted': 'Deellink verwijderd', + 'journey.share.deleteFailed': 'Verwijderen mislukt', + 'journey.share.updateFailed': 'Bijwerken mislukt', + 'journey.invite.role': 'Rol', + 'journey.invite.viewer': 'Kijker', + 'journey.invite.editor': 'Bewerker', + 'journey.invite.invite': 'Uitnodigen', + 'journey.invite.inviting': 'Uitnodigen...', + 'journey.settings.title': 'Reisverslaginstellingen', + 'journey.settings.coverImage': 'Omslagfoto', + 'journey.settings.changeCover': 'Omslag wijzigen', + 'journey.settings.addCover': 'Omslagfoto toevoegen', + 'journey.settings.name': 'Naam', + 'journey.settings.subtitle': 'Ondertitel', + 'journey.settings.subtitlePlaceholder': 'bijv. Thailand, Vietnam & Cambodja', + 'journey.settings.endJourney': 'Reis archiveren', + 'journey.settings.reopenJourney': 'Reis herstellen', + 'journey.settings.archived': 'Reis gearchiveerd', + 'journey.settings.reopened': 'Reis heropend', + 'journey.settings.endDescription': + 'Verbergt het Live-badge. Je kunt het altijd heropenen.', + 'journey.settings.delete': 'Verwijderen', + 'journey.settings.deleteJourney': 'Reisverslag verwijderen', + 'journey.settings.deleteMessage': + '"{title}" verwijderen? Alle vermeldingen en foto\'s gaan verloren.', + 'journey.settings.saved': 'Instellingen opgeslagen', + 'journey.settings.saveFailed': 'Opslaan mislukt', + 'journey.settings.coverUpdated': 'Omslag bijgewerkt', + 'journey.settings.coverFailed': 'Uploaden mislukt', + 'journey.settings.failedToDelete': 'Verwijderen mislukt', + 'journey.entries.deleteTitle': 'Vermelding verwijderen', + 'journey.photosUploaded': "{count} foto's geüpload", + 'journey.photosUploadFailed': "Sommige foto's konden niet worden geüpload", + 'journey.photosAdded': "{count} foto's toegevoegd", + 'journey.public.notFound': 'Niet gevonden', + 'journey.public.notFoundMessage': + 'Dit reisverslag bestaat niet of de link is verlopen.', + 'journey.public.readOnly': 'Alleen-lezen · Openbaar reisverslag', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Gedeeld via', + 'journey.public.madeWith': 'Gemaakt met', + 'journey.pdf.journeyBook': 'Reisboek', + 'journey.pdf.madeWith': 'Gemaakt met TREK', + 'journey.pdf.day': 'Dag', + 'journey.pdf.theEnd': 'Einde', + 'journey.pdf.saveAsPdf': 'Opslaan als PDF', + 'journey.pdf.pages': "pagina's", + 'journey.picker.tripPeriod': 'Reisperiode', + 'journey.picker.dateRange': 'Datumbereik', + 'journey.picker.allPhotos': "Alle foto's", + 'journey.picker.albums': 'Albums', + 'journey.picker.selected': 'geselecteerd', + 'journey.picker.addTo': 'Toevoegen aan', + 'journey.picker.newGallery': 'Nieuwe galerij', + 'journey.picker.selectAll': 'Alles selecteren', + 'journey.picker.deselectAll': 'Alles deselecteren', + 'journey.picker.noAlbums': 'Geen albums gevonden', + 'journey.picker.selectDate': 'Selecteer datum', + 'journey.picker.search': 'Zoeken', +}; +export default journey; diff --git a/shared/src/i18n/nl/login.ts b/shared/src/i18n/nl/login.ts new file mode 100644 index 00000000..b931b2a6 --- /dev/null +++ b/shared/src/i18n/nl/login.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Inloggen mislukt. Controleer je inloggegevens.', + 'login.tagline': 'Jouw reizen.\nJouw plan.', + 'login.description': + 'Plan reizen samen met interactieve kaarten, budgetten en realtime synchronisatie.', + 'login.features.maps': 'Interactieve kaarten', + 'login.features.mapsDesc': 'Google Places, routes en clustering', + 'login.features.realtime': 'Realtime synchronisatie', + 'login.features.realtimeDesc': 'Plan samen via WebSocket', + 'login.features.budget': 'Budgetbeheer', + 'login.features.budgetDesc': 'Categorieën, grafieken en kosten per persoon', + 'login.features.collab': 'Samenwerking', + 'login.features.collabDesc': 'Meerdere gebruikers met gedeelde reizen', + 'login.features.packing': 'Paklijsten', + 'login.features.packingDesc': 'Categorieën, voortgang en suggesties', + 'login.features.bookings': 'Reserveringen', + 'login.features.bookingsDesc': 'Vluchten, hotels, restaurants en meer', + 'login.features.files': 'Documenten', + 'login.features.filesDesc': 'Upload en beheer documenten', + 'login.features.routes': 'Slimme routes', + 'login.features.routesDesc': + 'Automatisch optimaliseren en exporteren naar Google Maps', + 'login.selfHosted': + 'Zelf gehost · Open Source · Jouw gegevens blijven van jou', + 'login.title': 'Inloggen', + 'login.subtitle': 'Welkom terug', + 'login.signingIn': 'Inloggen…', + 'login.signIn': 'Inloggen', + 'login.createAdmin': 'Beheerdersaccount aanmaken', + 'login.createAdminHint': 'Stel het eerste beheerdersaccount in voor TREK.', + 'login.setNewPassword': 'Nieuw wachtwoord instellen', + 'login.setNewPasswordHint': + 'U moet uw wachtwoord wijzigen voordat u verder kunt gaan.', + 'login.createAccount': 'Account aanmaken', + 'login.createAccountHint': 'Registreer een nieuw account.', + 'login.creating': 'Aanmaken…', + 'login.noAccount': 'Nog geen account?', + 'login.hasAccount': 'Heb je al een account?', + 'login.register': 'Registreren', + 'login.emailPlaceholder': 'jouw@email.com', + 'login.username': 'Gebruikersnaam', + 'login.oidc.registrationDisabled': + 'Registratie is uitgeschakeld. Neem contact op met je beheerder.', + 'login.oidc.noEmail': 'Geen e-mailadres ontvangen van de provider.', + 'login.mfaTitle': 'Tweefactorauthenticatie', + 'login.mfaSubtitle': 'Voer de 6-cijferige code van je authenticator-app in.', + 'login.mfaCodeLabel': 'Verificatiecode', + 'login.mfaCodeRequired': 'Voer de code van je authenticator-app in.', + 'login.mfaHint': 'Open Google Authenticator, Authy of een andere TOTP-app.', + 'login.mfaBack': '← Terug naar inloggen', + 'login.mfaVerify': 'Verifiëren', + 'login.invalidInviteLink': 'Ongeldige of verlopen uitnodigingslink', + 'login.oidcFailed': 'OIDC-aanmelding mislukt', + 'login.usernameRequired': 'Gebruikersnaam is vereist', + 'login.passwordMinLength': 'Wachtwoord moet minimaal 8 tekens bevatten', + 'login.forgotPassword': 'Wachtwoord vergeten?', + 'login.forgotPasswordTitle': 'Wachtwoord resetten', + 'login.forgotPasswordBody': + 'Voer het e-mailadres van je account in. Als er een account bestaat, sturen we een resetlink.', + 'login.forgotPasswordSubmit': 'Resetlink verzenden', + 'login.forgotPasswordSentTitle': 'Controleer je e-mail', + 'login.forgotPasswordSentBody': + 'Als er een account bestaat met dit adres, is de resetlink onderweg. Hij verloopt over 60 minuten.', + 'login.forgotPasswordSmtpHintOff': + 'Let op: de beheerder heeft SMTP niet ingesteld. De resetlink wordt naar de serverconsole geschreven in plaats van via e-mail verzonden.', + 'login.backToLogin': 'Terug naar inloggen', + 'login.newPassword': 'Nieuw wachtwoord', + 'login.confirmPassword': 'Nieuw wachtwoord bevestigen', + 'login.passwordsDontMatch': 'Wachtwoorden komen niet overeen', + 'login.mfaCode': '2FA-code', + 'login.resetPasswordTitle': 'Nieuw wachtwoord instellen', + 'login.resetPasswordBody': + 'Kies een sterk wachtwoord dat je hier nog niet hebt gebruikt. Minimaal 8 tekens.', + 'login.resetPasswordMfaBody': + 'Voer je 2FA-code of een back-upcode in om de reset te voltooien.', + 'login.resetPasswordSubmit': 'Wachtwoord resetten', + 'login.resetPasswordVerify': 'Verifiëren en resetten', + 'login.resetPasswordSuccessTitle': 'Wachtwoord bijgewerkt', + 'login.resetPasswordSuccessBody': + 'Je kunt nu inloggen met je nieuwe wachtwoord.', + 'login.resetPasswordInvalidLink': 'Ongeldige resetlink', + 'login.resetPasswordInvalidLinkBody': + 'Deze link ontbreekt of is ongeldig. Vraag een nieuwe aan om door te gaan.', + 'login.resetPasswordFailed': + 'Resetten mislukt. De link is mogelijk verlopen.', + 'login.oidc.tokenFailed': 'Authenticatie mislukt.', + 'login.oidc.invalidState': 'Ongeldige sessie. Probeer het opnieuw.', + 'login.demoFailed': 'Demo-login mislukt', + 'login.oidcSignIn': 'Inloggen met {name}', + 'login.oidcOnly': + 'Wachtwoordauthenticatie is uitgeschakeld. Log in via je SSO-provider.', + 'login.oidcLoggedOut': + 'Je bent uitgelogd. Log opnieuw in via je SSO-provider.', + 'login.demoHint': 'Probeer de demo — geen registratie nodig', +}; +export default login; diff --git a/shared/src/i18n/nl/map.ts b/shared/src/i18n/nl/map.ts new file mode 100644 index 00000000..dcc4cdac --- /dev/null +++ b/shared/src/i18n/nl/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Verbindingen', + 'map.showConnections': 'Boekingsroutes tonen', + 'map.hideConnections': 'Boekingsroutes verbergen', +}; +export default map; diff --git a/shared/src/i18n/nl/members.ts b/shared/src/i18n/nl/members.ts new file mode 100644 index 00000000..074c3b10 --- /dev/null +++ b/shared/src/i18n/nl/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Reis delen', + 'members.inviteUser': 'Gebruiker uitnodigen', + 'members.selectUser': 'Selecteer gebruiker…', + 'members.invite': 'Uitnodigen', + 'members.allHaveAccess': 'Alle gebruikers hebben al toegang.', + 'members.access': 'Toegang', + 'members.person': 'persoon', + 'members.persons': 'personen', + 'members.you': 'jij', + 'members.owner': 'Eigenaar', + 'members.leaveTrip': 'Reis verlaten', + 'members.removeAccess': 'Toegang verwijderen', + 'members.confirmLeave': 'Reis verlaten? Je verliest de toegang.', + 'members.confirmRemove': 'Toegang voor deze gebruiker verwijderen?', + 'members.loadError': 'Leden laden mislukt', + 'members.added': 'toegevoegd', + 'members.addError': 'Toevoegen mislukt', + 'members.removed': 'Lid verwijderd', + 'members.removeError': 'Verwijderen mislukt', +}; +export default members; diff --git a/shared/src/i18n/nl/memories.ts b/shared/src/i18n/nl/memories.ts new file mode 100644 index 00000000..20c7af3d --- /dev/null +++ b/shared/src/i18n/nl/memories.ts @@ -0,0 +1,82 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': "Foto's", + 'memories.notConnected': 'Immich niet verbonden', + 'memories.notConnectedHint': + "Verbind je Immich-instantie in Instellingen om je reisfoto's hier te zien.", + 'memories.notConnectedMultipleHint': + "Verbind een van deze fotoproviders: {provider_names} in Instellingen om foto's aan dit reisplan toe te voegen.", + 'memories.noDates': "Voeg data toe aan je reis om foto's te laden.", + 'memories.noPhotos': "Geen foto's gevonden", + 'memories.noPhotosHint': + "Geen foto's gevonden in Immich voor de datumreeks van deze reis.", + 'memories.photosFound': "foto's", + 'memories.fromOthers': 'van anderen', + 'memories.sharePhotos': "Foto's delen", + 'memories.sharing': 'Wordt gedeeld', + 'memories.reviewTitle': "Je foto's bekijken", + 'memories.reviewHint': "Klik op foto's om ze uit te sluiten van delen.", + 'memories.shareCount': "{count} foto's delen", + 'memories.providerUrl': 'Server-URL', + 'memories.providerApiKey': 'API-sleutel', + 'memories.providerUsername': 'Gebruikersnaam', + 'memories.providerPassword': 'Wachtwoord', + 'memories.providerOTP': 'MFA-code (indien ingeschakeld)', + 'memories.skipSSLVerification': 'SSL-certificaatverificatie overslaan', + 'memories.immichAutoUpload': + "Journey-foto's bij upload ook naar Immich spiegelen", + 'memories.providerUrlHintSynology': + 'Voeg het pad van de Photos-app toe aan de URL, bijv. https://nas:5001/photo', + 'memories.testConnection': 'Verbinding testen', + 'memories.testShort': 'Testen', + 'memories.testFirst': 'Test eerst de verbinding', + 'memories.connected': 'Verbonden', + 'memories.disconnected': 'Niet verbonden', + 'memories.connectionSuccess': 'Verbonden met Immich', + 'memories.connectionError': 'Kon niet verbinden met Immich', + 'memories.saved': '{provider_name}-instellingen opgeslagen', + 'memories.providerDisconnectedBanner': + "Je {provider_name}-verbinding is verbroken. Maak opnieuw verbinding in Instellingen om foto's te bekijken.", + 'memories.saveError': + '{provider_name}-instellingen konden niet worden opgeslagen', + 'memories.saveRouteNotConfigured': + 'Opslagroute is niet geconfigureerd voor deze provider', + 'memories.testRouteNotConfigured': + 'Testroute is niet geconfigureerd voor deze provider', + 'memories.fillRequiredFields': 'Vul alle verplichte velden in', + 'memories.oldest': 'Oudste eerst', + 'memories.newest': 'Nieuwste eerst', + 'memories.allLocations': 'Alle locaties', + 'memories.addPhotos': "Foto's toevoegen", + 'memories.linkAlbum': 'Album koppelen', + 'memories.selectAlbum': 'Immich-album selecteren', + 'memories.selectAlbumMultiple': 'Album selecteren', + 'memories.noAlbums': 'Geen albums gevonden', + 'memories.syncAlbum': 'Album synchroniseren', + 'memories.unlinkAlbum': 'Ontkoppelen', + 'memories.photos': 'fotos', + 'memories.selectPhotos': "Selecteer foto's uit Immich", + 'memories.selectPhotosMultiple': "Foto's selecteren", + 'memories.selectHint': "Tik op foto's om ze te selecteren.", + 'memories.selected': 'geselecteerd', + 'memories.addSelected': "{count} foto's toevoegen", + 'memories.alreadyAdded': 'Toegevoegd', + 'memories.private': 'Privé', + 'memories.stopSharing': 'Delen stoppen', + 'memories.tripDates': 'Reisdata', + 'memories.allPhotos': "Alle foto's", + 'memories.confirmShareTitle': 'Delen met reisgenoten?', + 'memories.confirmShareHint': + "{count} foto's worden zichtbaar voor alle leden van deze reis. Je kunt individuele foto's later privé maken.", + 'memories.confirmShareButton': "Foto's delen", + 'memories.error.loadAlbums': 'Albums laden mislukt', + 'memories.error.linkAlbum': 'Album koppelen mislukt', + 'memories.error.unlinkAlbum': 'Album ontkoppelen mislukt', + 'memories.error.syncAlbum': 'Album synchroniseren mislukt', + 'memories.error.loadPhotos': "Foto's laden mislukt", + 'memories.error.addPhotos': "Foto's toevoegen mislukt", + 'memories.error.removePhoto': 'Foto verwijderen mislukt', + 'memories.error.toggleSharing': 'Delen bijwerken mislukt', +}; +export default memories; diff --git a/shared/src/i18n/nl/nav.ts b/shared/src/i18n/nl/nav.ts new file mode 100644 index 00000000..3c88eb7d --- /dev/null +++ b/shared/src/i18n/nl/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Reis', + 'nav.share': 'Delen', + 'nav.settings': 'Instellingen', + 'nav.admin': 'Admin', + 'nav.logout': 'Uitloggen', + 'nav.lightMode': 'Lichte modus', + 'nav.darkMode': 'Donkere modus', + 'nav.autoMode': 'Automatisch', + 'nav.administrator': 'Beheerder', + 'nav.myTrips': 'Mijn reizen', + 'nav.profile': 'Profiel', + 'nav.bottomSettings': 'Instellingen', + 'nav.bottomAdmin': 'Beheerdersinstellingen', + 'nav.bottomLogout': 'Uitloggen', + 'nav.bottomAdminBadge': 'Beheerder', +}; +export default nav; diff --git a/shared/src/i18n/nl/notif.ts b/shared/src/i18n/nl/notif.ts new file mode 100644 index 00000000..93f50566 --- /dev/null +++ b/shared/src/i18n/nl/notif.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Melding', + 'notif.test.simple.text': 'Dit is een eenvoudige testmelding.', + 'notif.test.boolean.text': 'Accepteer je deze testmelding?', + 'notif.test.navigate.text': 'Klik hieronder om naar het dashboard te gaan.', + 'notif.trip_invite.title': 'Reisuitnodiging', + 'notif.trip_invite.text': '{actor} heeft je uitgenodigd voor {trip}', + 'notif.booking_change.title': 'Boeking bijgewerkt', + 'notif.booking_change.text': '{actor} heeft een boeking bijgewerkt in {trip}', + 'notif.trip_reminder.title': 'Reisherinnering', + 'notif.trip_reminder.text': 'Je reis {trip} komt eraan!', + 'notif.todo_due.title': 'Taak verloopt', + 'notif.todo_due.text': '{todo} in {trip} verloopt op {due}', + 'notif.vacay_invite.title': 'Vacay Fusion-uitnodiging', + 'notif.vacay_invite.text': + '{actor} nodigt je uit om vakantieplannen te fuseren', + 'notif.photos_shared.title': "Foto's gedeeld", + 'notif.photos_shared.text': + "{actor} heeft {count} foto('s) gedeeld in {trip}", + 'notif.collab_message.title': 'Nieuw bericht', + 'notif.collab_message.text': '{actor} heeft een bericht gestuurd in {trip}', + 'notif.packing_tagged.title': 'Paklijsttaak', + 'notif.packing_tagged.text': + '{actor} heeft je toegewezen aan {category} in {trip}', + 'notif.version_available.title': 'Nieuwe versie beschikbaar', + 'notif.version_available.text': 'TREK {version} is nu beschikbaar', + 'notif.action.view_trip': 'Reis bekijken', + 'notif.action.view_collab': 'Berichten bekijken', + 'notif.action.view_packing': 'Paklijst bekijken', + 'notif.action.view_photos': "Foto's bekijken", + 'notif.action.view_vacay': 'Vacay bekijken', + 'notif.action.view_admin': 'Naar admin', + 'notif.action.view': 'Bekijken', + 'notif.action.accept': 'Accepteren', + 'notif.action.decline': 'Weigeren', + 'notif.generic.title': 'Melding', + 'notif.generic.text': 'Je hebt een nieuwe melding', + 'notif.dev.unknown_event.title': '[DEV] Onbekende gebeurtenis', + 'notif.dev.unknown_event.text': + 'Gebeurtenistype "{event}" is niet geregistreerd in EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/nl/notifications.ts b/shared/src/i18n/nl/notifications.ts new file mode 100644 index 00000000..0ef542d4 --- /dev/null +++ b/shared/src/i18n/nl/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Meldingen', + 'notifications.markAllRead': 'Alles als gelezen markeren', + 'notifications.deleteAll': 'Alles verwijderen', + 'notifications.showAll': 'Alle meldingen weergeven', + 'notifications.empty': 'Geen meldingen', + 'notifications.emptyDescription': 'Je bent helemaal bijgewerkt!', + 'notifications.all': 'Alle', + 'notifications.unreadOnly': 'Ongelezen', + 'notifications.markRead': 'Markeren als gelezen', + 'notifications.markUnread': 'Markeren als ongelezen', + 'notifications.delete': 'Verwijderen', + 'notifications.system': 'Systeem', + 'notifications.synologySessionCleared.title': 'Synology Photos verbroken', + 'notifications.synologySessionCleared.text': + 'Je server of account is gewijzigd — ga naar Instellingen om je verbinding opnieuw te testen.', + 'notifications.test.title': 'Testmelding van {actor}', + 'notifications.test.text': 'Dit is een eenvoudige testmelding.', + 'notifications.test.booleanTitle': '{actor} vraagt om uw goedkeuring', + 'notifications.test.booleanText': 'Booleaanse testmelding.', + 'notifications.test.accept': 'Goedkeuren', + 'notifications.test.decline': 'Afwijzen', + 'notifications.test.navigateTitle': 'Bekijk iets', + 'notifications.test.navigateText': 'Navigatie-testmelding.', + 'notifications.test.goThere': 'Ga erheen', + 'notifications.test.adminTitle': 'Admin-broadcast', + 'notifications.test.adminText': + '{actor} heeft een testmelding naar alle admins gestuurd.', + 'notifications.test.tripTitle': '{actor} heeft gepost in uw reis', + 'notifications.test.tripText': 'Testmelding voor reis "{trip}".', + 'notifications.versionAvailable.title': 'Update beschikbaar', + 'notifications.versionAvailable.text': 'TREK {version} is nu beschikbaar.', + 'notifications.versionAvailable.button': 'Details bekijken', +}; +export default notifications; diff --git a/shared/src/i18n/nl/oauth.ts b/shared/src/i18n/nl/oauth.ts new file mode 100644 index 00000000..8b898223 --- /dev/null +++ b/shared/src/i18n/nl/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Reizen', + 'oauth.scope.group.places': 'Plaatsen', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Paklijst', + 'oauth.scope.group.todos': 'Taken', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Reserveringen', + 'oauth.scope.group.collab': 'Samenwerking', + 'oauth.scope.group.notifications': 'Meldingen', + 'oauth.scope.group.vacay': 'Vakantie', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Weer', + 'oauth.scope.group.journey': 'Reisverslag', + 'oauth.scope.trips:read.label': 'Reizen en reisplannen bekijken', + 'oauth.scope.trips:read.description': + 'Reizen, dagen, notities en leden lezen', + 'oauth.scope.trips:write.label': 'Reizen en reisplannen bewerken', + 'oauth.scope.trips:write.description': + 'Reizen, dagen en notities aanmaken, bijwerken en leden beheren', + 'oauth.scope.trips:delete.label': 'Reizen verwijderen', + 'oauth.scope.trips:delete.description': + 'Hele reizen permanent verwijderen — deze actie is onomkeerbaar', + 'oauth.scope.trips:share.label': 'Deellinks beheren', + 'oauth.scope.trips:share.description': + 'Publieke deellinks aanmaken, bijwerken en intrekken', + 'oauth.scope.places:read.label': 'Plaatsen en kaartgegevens bekijken', + 'oauth.scope.places:read.description': + 'Plaatsen, dagtoewijzingen, tags en categorieën lezen', + 'oauth.scope.places:write.label': 'Plaatsen beheren', + 'oauth.scope.places:write.description': + 'Plaatsen, toewijzingen en tags aanmaken, bijwerken en verwijderen', + 'oauth.scope.atlas:read.label': 'Atlas bekijken', + 'oauth.scope.atlas:read.description': + "Bezochte landen, regio's en bucketlist lezen", + 'oauth.scope.atlas:write.label': 'Atlas beheren', + 'oauth.scope.atlas:write.description': + "Landen en regio's markeren als bezocht, bucketlist beheren", + 'oauth.scope.packing:read.label': 'Paklijsten bekijken', + 'oauth.scope.packing:read.description': + 'Pakartikelen, tassen en categorietoewijzingen lezen', + 'oauth.scope.packing:write.label': 'Paklijsten beheren', + 'oauth.scope.packing:write.description': + 'Pakartikelen en tassen toevoegen, bijwerken, verwijderen, omschakelen en herordenen', + 'oauth.scope.todos:read.label': 'Takenlijsten bekijken', + 'oauth.scope.todos:read.description': + 'Reistaakitems en categorietoewijzingen lezen', + 'oauth.scope.todos:write.label': 'Takenlijsten beheren', + 'oauth.scope.todos:write.description': + 'Taakitems aanmaken, bijwerken, omschakelen, verwijderen en herordenen', + 'oauth.scope.budget:read.label': 'Budget bekijken', + 'oauth.scope.budget:read.description': + 'Budgetitems en kostenspecificatie lezen', + 'oauth.scope.budget:write.label': 'Budget beheren', + 'oauth.scope.budget:write.description': + 'Budgetitems aanmaken, bijwerken en verwijderen', + 'oauth.scope.reservations:read.label': 'Reserveringen bekijken', + 'oauth.scope.reservations:read.description': + 'Reserveringen en accommodatiedetails lezen', + 'oauth.scope.reservations:write.label': 'Reserveringen beheren', + 'oauth.scope.reservations:write.description': + 'Reserveringen aanmaken, bijwerken, verwijderen en herordenen', + 'oauth.scope.collab:read.label': 'Samenwerking bekijken', + 'oauth.scope.collab:read.description': + 'Samenwerkingsnotities, polls en berichten lezen', + 'oauth.scope.collab:write.label': 'Samenwerking beheren', + 'oauth.scope.collab:write.description': + 'Samenwerkingsnotities, polls en berichten aanmaken, bijwerken en verwijderen', + 'oauth.scope.notifications:read.label': 'Meldingen bekijken', + 'oauth.scope.notifications:read.description': + 'In-app meldingen en ongelezen aantallen lezen', + 'oauth.scope.notifications:write.label': 'Meldingen beheren', + 'oauth.scope.notifications:write.description': + 'Meldingen als gelezen markeren en erop reageren', + 'oauth.scope.vacay:read.label': 'Vakantieplannen bekijken', + 'oauth.scope.vacay:read.description': + 'Vakantieplanningsgegevens, invoeren en statistieken lezen', + 'oauth.scope.vacay:write.label': 'Vakantieplannen beheren', + 'oauth.scope.vacay:write.description': + 'Vakantie-invoeren, feestdagen en teamplannen aanmaken en beheren', + 'oauth.scope.geo:read.label': 'Kaarten & geocodering', + 'oauth.scope.geo:read.description': + "Locaties zoeken, kaart-URL's oplossen en coördinaten omgekeerd geocoderen", + 'oauth.scope.weather:read.label': 'Weersverwachtingen', + 'oauth.scope.weather:read.description': + 'Weersverwachtingen ophalen voor reislocaties en -datums', + 'oauth.scope.journey:read.label': 'Reisverslagen bekijken', + 'oauth.scope.journey:read.description': + 'Reisverslagen, vermeldingen en lijst van bijdragers lezen', + 'oauth.scope.journey:write.label': 'Reisverslagen beheren', + 'oauth.scope.journey:write.description': + 'Reisverslagen en hun vermeldingen aanmaken, bijwerken en verwijderen', + 'oauth.scope.journey:share.label': 'Reisverslag-links beheren', + 'oauth.scope.journey:share.description': + 'Publieke deellinks voor reisverslagen aanmaken, bijwerken en intrekken', +}; +export default oauth; diff --git a/shared/src/i18n/nl/packing.ts b/shared/src/i18n/nl/packing.ts new file mode 100644 index 00000000..5ee7a4b8 --- /dev/null +++ b/shared/src/i18n/nl/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Paklijst', + 'packing.empty': 'Paklijst is leeg', + 'packing.import': 'Importeren', + 'packing.importTitle': 'Paklijst importeren', + 'packing.importHint': + 'Eén item per regel. Optioneel categorie en aantal gescheiden door komma, puntkomma of tab: Naam, Categorie, Aantal', + 'packing.importPlaceholder': + 'Tandenborstel\nZonnebrand, Hygiëne\nT-Shirts, Kleding, 5\nPaspoort, Documenten', + 'packing.importCsv': 'CSV/TXT laden', + 'packing.importAction': '{count} importeren', + 'packing.importSuccess': '{count} items geïmporteerd', + 'packing.importError': 'Import mislukt', + 'packing.importEmpty': 'Geen items om te importeren', + 'packing.progress': '{packed} van {total} ingepakt ({percent}%)', + 'packing.clearChecked': '{count} aangevinkte verwijderen', + 'packing.clearCheckedShort': '{count} verwijderen', + 'packing.suggestions': 'Suggesties', + 'packing.suggestionsTitle': 'Suggesties toevoegen', + 'packing.allSuggested': 'Alle suggesties toegevoegd', + 'packing.allPacked': 'Alles ingepakt!', + 'packing.addPlaceholder': 'Nieuw item toevoegen...', + 'packing.categoryPlaceholder': 'Categorie...', + 'packing.filterAll': 'Alle', + 'packing.filterOpen': 'Openstaand', + 'packing.filterDone': 'Klaar', + 'packing.emptyTitle': 'Paklijst is leeg', + 'packing.emptyHint': 'Voeg items toe of gebruik de suggesties', + 'packing.emptyFiltered': 'Geen items gevonden voor dit filter', + 'packing.menuRename': 'Hernoemen', + 'packing.menuCheckAll': 'Alles aanvinken', + 'packing.menuUncheckAll': 'Alles uitvinken', + 'packing.menuDeleteCat': 'Categorie verwijderen', + 'packing.addItem': 'Item toevoegen', + 'packing.addItemPlaceholder': 'Itemnaam...', + 'packing.addCategory': 'Categorie toevoegen', + 'packing.newCategoryPlaceholder': 'Categorienaam (bijv. Kleding)', + 'packing.applyTemplate': 'Sjabloon toepassen', + 'packing.template': 'Sjabloon', + 'packing.templateApplied': '{count} items toegevoegd vanuit sjabloon', + 'packing.templateError': 'Fout bij toepassen van sjabloon', + 'packing.saveAsTemplate': 'Opslaan als sjabloon', + 'packing.templateName': 'Sjabloonnaam', + 'packing.templateSaved': 'Paklijst opgeslagen als sjabloon', + 'packing.noMembers': 'Geen leden', + 'packing.bags': 'Bagage', + 'packing.noBag': 'Niet toegewezen', + 'packing.totalWeight': 'Totaalgewicht', + 'packing.bagName': 'Naam...', + 'packing.addBag': 'Bagage toevoegen', + 'packing.changeCategory': 'Categorie wijzigen', + 'packing.confirm.clearChecked': + 'Weet je zeker dat je {count} aangevinkte items wilt verwijderen?', + 'packing.confirm.deleteCat': + 'Weet je zeker dat je de categorie "{name}" met {count} items wilt verwijderen?', + 'packing.defaultCategory': 'Overig', + 'packing.toast.saveError': 'Opslaan mislukt', + 'packing.toast.deleteError': 'Verwijderen mislukt', + 'packing.toast.renameError': 'Hernoemen mislukt', + 'packing.toast.addError': 'Toevoegen mislukt', + 'packing.suggestions.items': [ + { + name: 'Paspoort', + category: 'Documenten', + }, + { + name: 'Identiteitskaart', + category: 'Documenten', + }, + { + name: 'Reisverzekering', + category: 'Documenten', + }, + { + name: 'Vliegtickets', + category: 'Documenten', + }, + { + name: 'Creditcard', + category: 'Financiën', + }, + { + name: 'Contant geld', + category: 'Financiën', + }, + { + name: 'Visum', + category: 'Documenten', + }, + { + name: 'T-shirts', + category: 'Kleding', + }, + { + name: 'Broeken', + category: 'Kleding', + }, + { + name: 'Ondergoed', + category: 'Kleding', + }, + { + name: 'Sokken', + category: 'Kleding', + }, + { + name: 'Jas', + category: 'Kleding', + }, + { + name: 'Slaapkleding', + category: 'Kleding', + }, + { + name: 'Zwemkleding', + category: 'Kleding', + }, + { + name: 'Regenjas', + category: 'Kleding', + }, + { + name: 'Comfortabele schoenen', + category: 'Kleding', + }, + { + name: 'Tandenborstel', + category: 'Toiletartikelen', + }, + { + name: 'Tandpasta', + category: 'Toiletartikelen', + }, + { + name: 'Shampoo', + category: 'Toiletartikelen', + }, + { + name: 'Deodorant', + category: 'Toiletartikelen', + }, + { + name: 'Zonnebrandcrème', + category: 'Toiletartikelen', + }, + { + name: 'Scheermesje', + category: 'Toiletartikelen', + }, + { + name: 'Oplader', + category: 'Elektronica', + }, + { + name: 'Powerbank', + category: 'Elektronica', + }, + { + name: 'Koptelefoon', + category: 'Elektronica', + }, + { + name: 'Reisadapter', + category: 'Elektronica', + }, + { + name: 'Camera', + category: 'Elektronica', + }, + { + name: 'Pijnstillers', + category: 'Gezondheid', + }, + { + name: 'Pleisters', + category: 'Gezondheid', + }, + { + name: 'Ontsmettingsmiddel', + category: 'Gezondheid', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/nl/pdf.ts b/shared/src/i18n/nl/pdf.ts new file mode 100644 index 00000000..89cc8a24 --- /dev/null +++ b/shared/src/i18n/nl/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Reisplan', + 'pdf.planned': 'Gepland', + 'pdf.costLabel': 'Kosten EUR', + 'pdf.preview': 'PDF-voorbeeld', + 'pdf.saveAsPdf': 'Opslaan als PDF', +}; +export default pdf; diff --git a/shared/src/i18n/nl/perm.ts b/shared/src/i18n/nl/perm.ts new file mode 100644 index 00000000..7957b4fc --- /dev/null +++ b/shared/src/i18n/nl/perm.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Rechtinstellingen', + 'perm.subtitle': 'Bepaal wie welke acties mag uitvoeren in de applicatie', + 'perm.saved': 'Rechtinstellingen opgeslagen', + 'perm.resetDefaults': 'Standaardwaarden herstellen', + 'perm.customized': 'aangepast', + 'perm.level.admin': 'Alleen beheerder', + 'perm.level.tripOwner': 'Reiseigenaar', + 'perm.level.tripMember': 'Reisleden', + 'perm.level.everybody': 'Iedereen', + 'perm.cat.trip': 'Reisbeheer', + 'perm.cat.members': 'Ledenbeheer', + 'perm.cat.files': 'Bestanden', + 'perm.cat.content': 'Inhoud & planning', + 'perm.cat.extras': 'Budget, paklijsten & samenwerking', + 'perm.action.trip_create': 'Reizen aanmaken', + 'perm.action.trip_edit': 'Reisdetails bewerken', + 'perm.action.trip_delete': 'Reizen verwijderen', + 'perm.action.trip_archive': 'Reizen archiveren / dearchiveren', + 'perm.action.trip_cover_upload': 'Omslagfoto uploaden', + 'perm.action.member_manage': 'Leden toevoegen / verwijderen', + 'perm.action.file_upload': 'Bestanden uploaden', + 'perm.action.file_edit': 'Bestandsmetadata bewerken', + 'perm.action.file_delete': 'Bestanden verwijderen', + 'perm.action.place_edit': 'Plaatsen toevoegen / bewerken / verwijderen', + 'perm.action.day_edit': 'Dagen, notities & toewijzingen bewerken', + 'perm.action.reservation_edit': 'Reserveringen beheren', + 'perm.action.budget_edit': 'Budget beheren', + 'perm.action.packing_edit': 'Paklijsten beheren', + 'perm.action.collab_edit': 'Samenwerking (notities, polls, chat)', + 'perm.action.share_manage': 'Deellinks beheren', + 'perm.actionHint.trip_create': 'Wie kan nieuwe reizen aanmaken', + 'perm.actionHint.trip_edit': + 'Wie kan reisnaam, data, beschrijving en valuta wijzigen', + 'perm.actionHint.trip_delete': 'Wie kan een reis permanent verwijderen', + 'perm.actionHint.trip_archive': 'Wie kan een reis archiveren of dearchiveren', + 'perm.actionHint.trip_cover_upload': + 'Wie kan de omslagfoto uploaden of wijzigen', + 'perm.actionHint.member_manage': + 'Wie kan reisleden uitnodigen of verwijderen', + 'perm.actionHint.file_upload': 'Wie kan bestanden uploaden naar een reis', + 'perm.actionHint.file_edit': + 'Wie kan bestandsbeschrijvingen en links bewerken', + 'perm.actionHint.file_delete': + 'Wie kan bestanden naar de prullenbak verplaatsen of permanent verwijderen', + 'perm.actionHint.place_edit': + 'Wie kan plaatsen toevoegen, bewerken of verwijderen', + 'perm.actionHint.day_edit': + 'Wie kan dagen, dagnotities en plaatstoewijzingen bewerken', + 'perm.actionHint.reservation_edit': + 'Wie kan reserveringen aanmaken, bewerken of verwijderen', + 'perm.actionHint.budget_edit': + 'Wie kan budgetposten aanmaken, bewerken of verwijderen', + 'perm.actionHint.packing_edit': 'Wie kan pakitems en tassen beheren', + 'perm.actionHint.collab_edit': + 'Wie kan notities, polls aanmaken en berichten versturen', + 'perm.actionHint.share_manage': + 'Wie kan openbare deellinks aanmaken of verwijderen', +}; +export default perm; diff --git a/shared/src/i18n/nl/photos.ts b/shared/src/i18n/nl/photos.ts new file mode 100644 index 00000000..04f5d5fc --- /dev/null +++ b/shared/src/i18n/nl/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': "Foto's", + 'photos.subtitle': "{count} foto's voor {trip}", + 'photos.dropHere': "Foto's hier neerzetten...", + 'photos.dropHereActive': "Foto's hier neerzetten", + 'photos.captionForAll': 'Bijschrift (voor alle)', + 'photos.captionPlaceholder': 'Optioneel bijschrift...', + 'photos.addCaption': 'Bijschrift toevoegen...', + 'photos.allDays': 'Alle dagen', + 'photos.noPhotos': "Nog geen foto's", + 'photos.uploadHint': "Upload je reisfoto's", + 'photos.clickToSelect': 'of klik om te selecteren', + 'photos.linkPlace': 'Koppel plaats', + 'photos.noPlace': 'Geen plaats', + 'photos.uploadN': "{n} foto('s) uploaden", + 'photos.linkDay': 'Dag koppelen', + 'photos.noDay': 'Geen dag', + 'photos.dayLabel': 'Dag {number}', + 'photos.photoSelected': 'Foto geselecteerd', + 'photos.photosSelected': "Foto's geselecteerd", + 'photos.fileTypeHint': "JPG, PNG, WebP · max. 10 MB · tot 30 foto's", +}; +export default photos; diff --git a/shared/src/i18n/nl/places.ts b/shared/src/i18n/nl/places.ts new file mode 100644 index 00000000..43c37d00 --- /dev/null +++ b/shared/src/i18n/nl/places.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Plaats/activiteit toevoegen', + 'places.importFile': 'Bestand importeren', + 'places.sidebarDrop': 'Loslaten om te importeren', + 'places.importFileHint': + 'Importeer .gpx-, .kml- of .kmz-bestanden uit tools zoals Google My Maps, Google Earth of een GPS-tracker.', + 'places.importFileDropHere': + 'Klik om een bestand te selecteren of sleep het hier naartoe', + 'places.importFileDropActive': 'Laat het bestand los om het te selecteren', + 'places.importFileUnsupported': + 'Niet-ondersteund bestandstype. Gebruik .gpx, .kml of .kmz.', + 'places.importFileTooLarge': + 'Bestand is te groot. Maximale uploadgrootte is {maxMb} MB.', + 'places.importFileError': 'Importeren mislukt', + 'places.importAllSkipped': 'Alle plaatsen waren al in de reis.', + 'places.gpxImported': '{count} plaatsen geïmporteerd uit GPX', + 'places.gpxImportTypes': 'Wat wil je importeren?', + 'places.gpxImportWaypoints': 'Waypoints', + 'places.gpxImportRoutes': 'Routes', + 'places.gpxImportTracks': 'Tracks (met routegeometrie)', + 'places.gpxImportNoneSelected': + 'Selecteer minstens één type om te importeren.', + 'places.kmlImportTypes': 'Wat wil je importeren?', + 'places.kmlImportPoints': 'Punten (Placemarks)', + 'places.kmlImportPaths': 'Paden (LineStrings)', + 'places.kmlImportNoneSelected': 'Selecteer minstens één type.', + 'places.selectionCount': '{count} geselecteerd', + 'places.deleteSelected': 'Selectie verwijderen', + 'places.kmlKmzImported': '{count} plaatsen geïmporteerd uit KMZ/KML', + 'places.urlResolved': 'Plaats geïmporteerd van URL', + 'places.importList': 'Lijst importeren', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Geïmporteerd: {created} • Overgeslagen: {skipped}', + 'places.importGoogleList': 'Google Lijst', + 'places.importNaverList': 'Naver Lijst', + 'places.googleListHint': + 'Plak een gedeelde Google Maps lijstlink om alle plaatsen te importeren.', + 'places.googleListImported': '{count} plaatsen geimporteerd uit "{list}"', + 'places.googleListError': 'Google Maps lijst importeren mislukt', + 'places.naverListHint': + 'Plak een gedeelde Naver Maps lijstlink om alle plaatsen te importeren.', + 'places.naverListImported': '{count} plaatsen geimporteerd uit "{list}"', + 'places.naverListError': 'Naver Maps lijst importeren mislukt', + 'places.viewDetails': 'Details bekijken', + 'places.assignToDay': 'Aan welke dag toevoegen?', + 'places.all': 'Alle', + 'places.unplanned': 'Ongepland', + 'places.filterTracks': 'Tracks', + 'places.search': 'Plaatsen zoeken...', + 'places.allCategories': 'Alle categorieën', + 'places.categoriesSelected': 'categorieën', + 'places.clearFilter': 'Filter wissen', + 'places.count': '{count} plaatsen', + 'places.countSingular': '1 plaats', + 'places.allPlanned': 'Alle plaatsen zijn gepland', + 'places.noneFound': 'Geen plaatsen gevonden', + 'places.editPlace': 'Plaats bewerken', + 'places.formName': 'Naam', + 'places.formNamePlaceholder': 'bijv. Eiffeltoren', + 'places.formDescription': 'Beschrijving', + 'places.formDescriptionPlaceholder': 'Korte beschrijving...', + 'places.formAddress': 'Adres', + 'places.formAddressPlaceholder': 'Straat, stad, land', + 'places.formLat': 'Breedtegraad (bijv. 48.8566)', + 'places.formLng': 'Lengtegraad (bijv. 2.3522)', + 'places.formCategory': 'Categorie', + 'places.noCategory': 'Geen categorie', + 'places.categoryNamePlaceholder': 'Categorienaam', + 'places.formTime': 'Tijd', + 'places.startTime': 'Starttijd', + 'places.endTime': 'Einde', + 'places.endTimeBeforeStart': 'Eindtijd is vóór de starttijd', + 'places.timeCollision': 'Tijdoverlap met:', + 'places.formWebsite': 'Website', + 'places.formNotes': 'Notities', + 'places.formNotesPlaceholder': 'Persoonlijke notities...', + 'places.formReservation': 'Reservering', + 'places.reservationNotesPlaceholder': + 'Reserveringsnotities, bevestigingsnummer...', + 'places.mapsSearchPlaceholder': 'Plaatsen zoeken...', + 'places.mapsSearchError': 'Zoeken naar plaatsen mislukt.', + 'places.loadingDetails': 'Plaatsgegevens laden…', + 'places.osmHint': + "Zoeken via OpenStreetMap (geen foto's, openingstijden of beoordelingen). Voeg een Google API-sleutel toe in instellingen voor volledige details.", + 'places.osmActive': + "Zoeken via OpenStreetMap (geen foto's, beoordelingen of openingstijden). Voeg een Google API-sleutel toe in Instellingen voor uitgebreide gegevens.", + 'places.categoryCreateError': 'Categorie aanmaken mislukt', + 'places.nameRequired': 'Voer een naam in', + 'places.saveError': 'Opslaan mislukt', +}; +export default places; diff --git a/shared/src/i18n/nl/planner.ts b/shared/src/i18n/nl/planner.ts new file mode 100644 index 00000000..d193eee7 --- /dev/null +++ b/shared/src/i18n/nl/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Plaatsen', + 'planner.bookings': 'Boekingen', + 'planner.packingList': 'Paklijst', + 'planner.documents': 'Documenten', + 'planner.dayPlan': 'Dagplan', + 'planner.reservations': 'Reserveringen', + 'planner.minTwoPlaces': 'Minimaal 2 plaatsen met coördinaten nodig', + 'planner.noGeoPlaces': 'Geen plaatsen met coördinaten beschikbaar', + 'planner.routeCalculated': 'Route berekend', + 'planner.routeCalcFailed': 'Route kon niet worden berekend', + 'planner.routeError': 'Fout bij routeberekening', + 'planner.icsExportFailed': 'ICS-export mislukt', + 'planner.routeOptimized': 'Route geoptimaliseerd', + 'planner.reservationUpdated': 'Reservering bijgewerkt', + 'planner.reservationAdded': 'Reservering toegevoegd', + 'planner.confirmDeleteReservation': 'Reservering verwijderen?', + 'planner.reservationDeleted': 'Reservering verwijderd', + 'planner.days': 'Dagen', + 'planner.allPlaces': 'Alle plaatsen', + 'planner.totalPlaces': '{n} plaatsen totaal', + 'planner.noDaysPlanned': 'Nog geen dagen gepland', + 'planner.editTrip': 'Reis bewerken →', + 'planner.placeOne': '1 plaats', + 'planner.placeN': '{n} plaatsen', + 'planner.addNote': 'Notitie toevoegen', + 'planner.noEntries': 'Geen invoeren voor deze dag', + 'planner.addPlace': 'Plaats/activiteit toevoegen', + 'planner.addPlaceShort': '+ Plaats/activiteit toevoegen', + 'planner.resPending': 'Reservering in behandeling · ', + 'planner.resConfirmed': 'Reservering bevestigd · ', + 'planner.notePlaceholder': 'Notitie…', + 'planner.noteTimePlaceholder': 'Tijd (optioneel)', + 'planner.noteExamplePlaceholder': + 'bijv. S3 om 14:30 vanaf centraal station, veerboot van pier 7, lunchpauze…', + 'planner.totalCost': 'Totale kosten', + 'planner.searchPlaces': 'Plaatsen zoeken…', + 'planner.allCategories': 'Alle categorieën', + 'planner.noPlacesFound': 'Geen plaatsen gevonden', + 'planner.addFirstPlace': 'Eerste plaats toevoegen', + 'planner.noReservations': 'Geen reserveringen', + 'planner.addFirstReservation': 'Eerste reservering toevoegen', + 'planner.new': 'Nieuw', + 'planner.addToDay': '+ Dag', + 'planner.calculating': 'Berekenen…', + 'planner.route': 'Route', + 'planner.optimize': 'Optimaliseren', + 'planner.openGoogleMaps': 'Openen in Google Maps', + 'planner.selectDayHint': + 'Selecteer een dag uit de lijst links om het dagplan te bekijken', + 'planner.noPlacesForDay': 'Nog geen plaatsen voor deze dag', + 'planner.addPlacesLink': 'Plaatsen toevoegen →', + 'planner.minTotal': 'min. totaal', + 'planner.noReservation': 'Geen reservering', + 'planner.removeFromDay': 'Verwijderen van dag', + 'planner.addToThisDay': 'Toevoegen aan dag', + 'planner.overview': 'Overzicht', + 'planner.noDays': 'Geen dagen', + 'planner.editTripToAddDays': 'Bewerk de reis om dagen toe te voegen', + 'planner.dayCount': '{n} dagen', + 'planner.clickToUnlock': 'Klik om te ontgrendelen', + 'planner.keepPosition': 'Positie behouden tijdens route-optimalisatie', + 'planner.dayDetails': 'Dagdetails', + 'planner.dayN': 'Dag {n}', +}; +export default planner; diff --git a/shared/src/i18n/nl/register.ts b/shared/src/i18n/nl/register.ts new file mode 100644 index 00000000..a0e3b535 --- /dev/null +++ b/shared/src/i18n/nl/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Wachtwoorden komen niet overeen', + 'register.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten', + 'register.failed': 'Registratie mislukt', + 'register.getStarted': 'Aan de slag', + 'register.subtitle': + 'Maak een account aan en begin met het plannen van je droomreizen.', + 'register.feature1': 'Onbeperkte reisplannen', + 'register.feature2': 'Interactieve kaartweergave', + 'register.feature3': 'Beheer plaatsen en categorieën', + 'register.feature4': 'Houd reserveringen bij', + 'register.feature5': 'Maak paklijsten', + 'register.feature6': "Bewaar foto's en bestanden", + 'register.createAccount': 'Account aanmaken', + 'register.startPlanning': 'Begin met het plannen van je reis', + 'register.minChars': 'Min. 6 tekens', + 'register.confirmPassword': 'Bevestig wachtwoord', + 'register.repeatPassword': 'Herhaal wachtwoord', + 'register.registering': 'Registreren...', + 'register.register': 'Registreren', + 'register.hasAccount': 'Heb je al een account?', + 'register.signIn': 'Inloggen', +}; +export default register; diff --git a/shared/src/i18n/nl/reservations.ts b/shared/src/i18n/nl/reservations.ts new file mode 100644 index 00000000..d370e780 --- /dev/null +++ b/shared/src/i18n/nl/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Boekingen', + 'reservations.empty': 'Nog geen reserveringen', + 'reservations.emptyHint': + 'Voeg reserveringen toe voor vluchten, hotels en meer', + 'reservations.add': 'Reservering toevoegen', + 'reservations.addManual': 'Handmatige boeking', + 'reservations.placeHint': + 'Tip: Reserveringen kun je het beste direct vanuit een plaats aanmaken om ze te koppelen aan je dagplan.', + 'reservations.confirmed': 'Bevestigd', + 'reservations.pending': 'In behandeling', + 'reservations.summary': '{confirmed} bevestigd, {pending} in behandeling', + 'reservations.fromPlan': 'Vanuit plan', + 'reservations.showFiles': 'Bestanden tonen', + 'reservations.editTitle': 'Reservering bewerken', + 'reservations.status': 'Status', + 'reservations.datetime': 'Datum en tijd', + 'reservations.startTime': 'Starttijd', + 'reservations.endTime': 'Eindtijd', + 'reservations.date': 'Datum', + 'reservations.time': 'Tijd', + 'reservations.timeAlt': 'Tijd (alternatief, bijv. 19:30)', + 'reservations.notes': 'Notities', + 'reservations.notesPlaceholder': 'Extra notities...', + 'reservations.meta.airline': 'Luchtvaartmaatschappij', + 'reservations.meta.flightNumber': 'Vluchtnr.', + 'reservations.meta.from': 'Van', + 'reservations.meta.to': 'Naar', + 'reservations.needsReview': 'Controleren', + 'reservations.needsReviewHint': + 'Luchthaven kon niet automatisch worden herkend — bevestig de locatie.', + 'reservations.searchLocation': 'Station, haven, adres zoeken...', + 'reservations.meta.trainNumber': 'Treinnr.', + 'reservations.meta.platform': 'Perron', + 'reservations.meta.seat': 'Stoel', + 'reservations.meta.checkIn': 'Inchecken', + 'reservations.meta.checkInUntil': 'Check-in tot', + 'reservations.meta.checkOut': 'Uitchecken', + 'reservations.meta.linkAccommodation': 'Accommodatie', + 'reservations.meta.pickAccommodation': 'Koppel aan accommodatie', + 'reservations.meta.noAccommodation': 'Geen', + 'reservations.meta.hotelPlace': 'Accommodatie', + 'reservations.meta.pickHotel': 'Selecteer accommodatie', + 'reservations.meta.fromDay': 'Van', + 'reservations.meta.toDay': 'Tot', + 'reservations.meta.selectDay': 'Selecteer dag', + 'reservations.type.flight': 'Vlucht', + 'reservations.type.hotel': 'Accommodatie', + 'reservations.type.restaurant': 'Restaurant', + 'reservations.type.train': 'Trein', + 'reservations.type.car': 'Auto', + 'reservations.type.cruise': 'Cruise', + 'reservations.type.event': 'Evenement', + 'reservations.type.tour': 'Rondleiding', + 'reservations.type.other': 'Overig', + 'reservations.confirm.delete': + 'Weet je zeker dat je de reservering "{name}" wilt verwijderen?', + 'reservations.confirm.deleteTitle': 'Boeking verwijderen?', + 'reservations.confirm.deleteBody': '"{name}" wordt permanent verwijderd.', + 'reservations.toast.updated': 'Reservering bijgewerkt', + 'reservations.toast.removed': 'Reservering verwijderd', + 'reservations.toast.fileUploaded': 'Bestand geüpload', + 'reservations.toast.uploadError': 'Uploaden mislukt', + 'reservations.newTitle': 'Nieuwe reservering', + 'reservations.bookingType': 'Boekingstype', + 'reservations.titleLabel': 'Titel', + 'reservations.titlePlaceholder': 'bijv. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Locatie / Adres', + 'reservations.locationPlaceholder': 'Adres, luchthaven, hotel...', + 'reservations.confirmationCode': 'Boekingscode', + 'reservations.confirmationPlaceholder': 'bijv. ABC12345', + 'reservations.day': 'Dag', + 'reservations.noDay': 'Geen dag', + 'reservations.place': 'Plaats', + 'reservations.noPlace': 'Geen plaats', + 'reservations.pendingSave': 'wordt opgeslagen…', + 'reservations.uploading': 'Uploaden...', + 'reservations.attachFile': 'Bestand bijvoegen', + 'reservations.linkExisting': 'Bestaand bestand koppelen', + 'reservations.toast.saveError': 'Opslaan mislukt', + 'reservations.toast.updateError': 'Bijwerken mislukt', + 'reservations.toast.deleteError': 'Verwijderen mislukt', + 'reservations.confirm.remove': 'Reservering voor "{name}" verwijderen?', + 'reservations.linkAssignment': 'Koppelen aan dagtoewijzing', + 'reservations.pickAssignment': 'Selecteer een toewijzing uit je plan...', + 'reservations.noAssignment': 'Geen koppeling (zelfstandig)', + 'reservations.price': 'Prijs', + 'reservations.budgetCategory': 'Budgetcategorie', + 'reservations.budgetCategoryPlaceholder': 'bijv. Transport, Accommodatie', + 'reservations.budgetCategoryAuto': 'Automatisch (op basis van boekingstype)', + 'reservations.budgetHint': + 'Er wordt automatisch een budgetpost aangemaakt bij het opslaan.', + 'reservations.departureDate': 'Vertrek', + 'reservations.arrivalDate': 'Aankomst', + 'reservations.departureTime': 'Vertrektijd', + 'reservations.arrivalTime': 'Aankomsttijd', + 'reservations.pickupDate': 'Ophalen', + 'reservations.returnDate': 'Inleveren', + 'reservations.pickupTime': 'Ophaaltijd', + 'reservations.returnTime': 'Inlevertijd', + 'reservations.endDate': 'Einddatum', + 'reservations.meta.departureTimezone': 'TZ vertrek', + 'reservations.meta.arrivalTimezone': 'TZ aankomst', + 'reservations.span.departure': 'Vertrek', + 'reservations.span.arrival': 'Aankomst', + 'reservations.span.inTransit': 'Onderweg', + 'reservations.span.pickup': 'Ophalen', + 'reservations.span.return': 'Inleveren', + 'reservations.span.active': 'Actief', + 'reservations.span.start': 'Start', + 'reservations.span.end': 'Einde', + 'reservations.span.ongoing': 'Lopend', + 'reservations.validation.endBeforeStart': + 'Einddatum/-tijd moet na de startdatum/-tijd liggen', + 'reservations.addBooking': 'Boeking toevoegen', +}; +export default reservations; diff --git a/shared/src/i18n/nl/settings.ts b/shared/src/i18n/nl/settings.ts new file mode 100644 index 00000000..b23e5098 --- /dev/null +++ b/shared/src/i18n/nl/settings.ts @@ -0,0 +1,298 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Instellingen', + 'settings.subtitle': 'Configureer je persoonlijke instellingen', + 'settings.tabs.display': 'Weergave', + 'settings.tabs.map': 'Kaart', + 'settings.tabs.notifications': 'Meldingen', + 'settings.tabs.integrations': 'Integraties', + 'settings.tabs.account': 'Account', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'Over', + 'settings.map': 'Kaart', + 'settings.mapTemplate': 'Kaartsjabloon', + 'settings.mapTemplatePlaceholder.select': 'Selecteer sjabloon...', + 'settings.mapDefaultHint': 'Laat leeg voor OpenStreetMap (standaard)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL-sjabloon voor kaarttegels', + 'settings.mapProvider': 'Kaartprovider', + 'settings.mapProviderHint': + 'Geldt voor Trip Planner en Journey kaarten. Atlas gebruikt altijd Leaflet.', + 'settings.mapLeafletSubtitle': 'Klassiek 2D, elke raster-tile', + 'settings.mapMapboxSubtitle': 'Vector tiles, 3D-gebouwen & terrein', + 'settings.mapExperimental': 'Experimenteel', + 'settings.mapMapboxToken': 'Mapbox Access Token', + 'settings.mapMapboxTokenHint': 'Openbaar token (pk.*) van', + 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', + 'settings.mapStyle': 'Kaartstijl', + 'settings.mapStylePlaceholder': 'Kies een Mapbox-stijl', + 'settings.mapStyleHint': 'Preset of eigen mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D-gebouwen & terrein', + 'settings.map3dHint': + 'Kanteling + echte 3D-gebouwenextrusies — werkt op elke stijl, inclusief satelliet.', + 'settings.mapHighQuality': 'Hoge kwaliteit modus', + 'settings.mapHighQualityHint': + 'Antialiasing + globeprojectie voor scherpere randen en een realistische wereldweergave.', + 'settings.mapHighQualityWarning': + 'Kan de prestaties op minder krachtige apparaten beïnvloeden.', + 'settings.mapTipLabel': 'Tip:', + 'settings.mapTip': + 'Rechts-klik en sleep om de kaart te roteren/kantelen. Middenklik om een locatie toe te voegen (rechts-klik is voor rotatie).', + 'settings.latitude': 'Breedtegraad', + 'settings.longitude': 'Lengtegraad', + 'settings.saveMap': 'Kaart opslaan', + 'settings.apiKeys': 'API-sleutels', + 'settings.mapsKey': 'Google Maps API-sleutel', + 'settings.mapsKeyHint': + 'Voor plaatsen zoeken. Vereist Places API (New). Verkrijgbaar op console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API-sleutel', + 'settings.weatherKeyHint': + 'Voor weergegevens. Gratis op openweathermap.org/api', + 'settings.keyPlaceholder': 'Sleutel invoeren...', + 'settings.configured': 'Geconfigureerd', + 'settings.saveKeys': 'Sleutels opslaan', + 'settings.display': 'Weergave', + 'settings.colorMode': 'Kleurmodus', + 'settings.light': 'Licht', + 'settings.dark': 'Donker', + 'settings.auto': 'Automatisch', + 'settings.language': 'Taal', + 'settings.temperature': 'Temperatuureenheid', + 'settings.timeFormat': 'Tijdnotatie', + 'settings.blurBookingCodes': 'Boekingscodes vervagen', + 'settings.notifications': 'Meldingen', + 'settings.notifyTripInvite': 'Reisuitnodigingen', + 'settings.notifyBookingChange': 'Boekingswijzigingen', + 'settings.notifyTripReminder': 'Reisherinneringen', + 'settings.notifyTodoDue': 'Taak verloopt', + 'settings.notifyVacayInvite': 'Vacay-fusieuitnodigingen', + 'settings.notifyPhotosShared': "Gedeelde foto's (Immich)", + 'settings.notifyCollabMessage': 'Chatberichten (Collab)', + 'settings.notifyPackingTagged': 'Paklijst: toewijzingen', + 'settings.notifyWebhook': 'Webhook-meldingen', + 'settings.notificationsDisabled': + 'Meldingen zijn niet geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te schakelen.', + 'settings.notificationsActive': 'Actief kanaal', + 'settings.notificationsManagedByAdmin': + 'Meldingsgebeurtenissen worden geconfigureerd door je beheerder.', + 'settings.on': 'Aan', + 'settings.off': 'Uit', + 'settings.mcp.title': 'MCP-configuratie', + 'settings.mcp.endpoint': 'MCP-eindpunt', + 'settings.mcp.clientConfig': 'Clientconfiguratie', + 'settings.mcp.clientConfigHint': + 'Vervang door een API-token uit de onderstaande lijst. Het pad naar npx moet mogelijk worden aangepast voor jouw systeem (bijv. C:\\PROGRA~1\\nodejs\\npx.cmd op Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).', + 'settings.mcp.copy': 'Kopiëren', + 'settings.mcp.copied': 'Gekopieerd!', + 'settings.mcp.apiTokens': 'API-tokens', + 'settings.mcp.createToken': 'Nieuw token aanmaken', + 'settings.mcp.noTokens': + 'Nog geen tokens. Maak er een aan om MCP-clients te verbinden.', + 'settings.mcp.tokenCreatedAt': 'Aangemaakt', + 'settings.mcp.tokenUsedAt': 'Gebruikt', + 'settings.mcp.deleteTokenTitle': 'Token verwijderen', + 'settings.mcp.deleteTokenMessage': + 'Dit token werkt onmiddellijk niet meer. Elke MCP-client die het gebruikt verliest de toegang.', + 'settings.mcp.modal.createTitle': 'API-token aanmaken', + 'settings.mcp.modal.tokenName': 'Tokennaam', + 'settings.mcp.modal.tokenNamePlaceholder': 'bijv. Claude Desktop, Werklaptop', + 'settings.mcp.modal.creating': 'Aanmaken…', + 'settings.mcp.modal.create': 'Token aanmaken', + 'settings.mcp.modal.createdTitle': 'Token aangemaakt', + 'settings.mcp.modal.createdWarning': + 'Dit token wordt slechts één keer getoond. Kopieer en bewaar het nu — het kan niet worden hersteld.', + 'settings.mcp.modal.done': 'Klaar', + 'settings.mcp.toast.created': 'Token aangemaakt', + 'settings.mcp.toast.createError': 'Token aanmaken mislukt', + 'settings.mcp.toast.deleted': 'Token verwijderd', + 'settings.mcp.toast.deleteError': 'Token verwijderen mislukt', + 'settings.mcp.apiTokensDeprecated': + 'API-tokens zijn verouderd en worden in een toekomstige versie verwijderd. Gebruik OAuth 2.1-clients in plaats daarvan.', + 'settings.oauth.clients': 'OAuth 2.1-clients', + 'settings.oauth.clientsHint': + 'Registreer OAuth 2.1-clients zodat externe MCP-toepassingen (Claude Web, Cursor, enz.) verbinding kunnen maken zonder statische tokens.', + 'settings.oauth.createClient': 'Nieuwe client', + 'settings.oauth.noClients': 'Geen OAuth-clients geregistreerd.', + 'settings.oauth.clientId': 'Client-ID', + 'settings.oauth.clientSecret': 'Clientgeheim', + 'settings.oauth.deleteClient': 'Client verwijderen', + 'settings.oauth.deleteClientMessage': + 'Deze client en alle actieve sessies worden permanent verwijderd. Elke toepassing die deze client gebruikt, verliest onmiddellijk de toegang.', + 'settings.oauth.rotateSecret': 'Geheim vernieuwen', + 'settings.oauth.rotateSecretMessage': + 'Er wordt een nieuw clientgeheim gegenereerd en alle bestaande sessies worden direct ongeldig. Werk uw toepassing bij voordat u dit venster sluit.', + 'settings.oauth.rotateSecretConfirm': 'Vernieuwen', + 'settings.oauth.rotateSecretConfirming': 'Vernieuwen…', + 'settings.oauth.rotateSecretDoneTitle': 'Nieuw geheim gegenereerd', + 'settings.oauth.rotateSecretDoneWarning': + 'Dit geheim wordt slechts eenmalig getoond. Kopieer het nu en werk uw toepassing bij — alle vorige sessies zijn ongeldig gemaakt.', + 'settings.oauth.activeSessions': 'Actieve OAuth-sessies', + 'settings.oauth.sessionScopes': 'Rechten', + 'settings.oauth.sessionExpires': 'Verloopt', + 'settings.oauth.revoke': 'Intrekken', + 'settings.oauth.revokeSession': 'Sessie intrekken', + 'settings.oauth.revokeSessionMessage': + 'Dit trekt onmiddellijk de toegang voor deze OAuth-sessie in.', + 'settings.oauth.modal.createTitle': 'OAuth-client registreren', + 'settings.oauth.modal.presets': 'Snelle instellingen', + 'settings.oauth.modal.clientName': 'Toepassingsnaam', + 'settings.oauth.modal.clientNamePlaceholder': + 'bijv. Claude Web, Mijn MCP-app', + 'settings.oauth.modal.redirectUris': "Redirect-URI's", + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Eén URI per regel. HTTPS vereist (localhost uitgezonderd). Exacte overeenkomst vereist.', + 'settings.oauth.modal.scopes': 'Toegestane rechten', + 'settings.oauth.modal.scopesHint': + "list_trips en get_trip_summary zijn altijd beschikbaar — geen recht vereist. Ze helpen de AI trip-ID's te ontdekken.", + 'settings.oauth.modal.selectAll': 'Alles selecteren', + 'settings.oauth.modal.deselectAll': 'Alles deselecteren', + 'settings.oauth.modal.creating': 'Registreren…', + 'settings.oauth.modal.create': 'Client registreren', + 'settings.oauth.modal.createdTitle': 'Client geregistreerd', + 'settings.oauth.modal.createdWarning': + 'Het clientgeheim wordt slechts eenmalig getoond. Kopieer het nu — het kan niet worden hersteld.', + 'settings.oauth.toast.createError': + 'OAuth-client kon niet worden geregistreerd', + 'settings.oauth.toast.deleted': 'OAuth-client verwijderd', + 'settings.oauth.toast.deleteError': 'OAuth-client kon niet worden verwijderd', + 'settings.oauth.toast.revoked': 'Sessie ingetrokken', + 'settings.oauth.toast.revokeError': 'Sessie kon niet worden ingetrokken', + 'settings.oauth.toast.rotateError': 'Clientgeheim kon niet worden vernieuwd', + 'settings.oauth.modal.machineClient': 'Machineclient (zonder browserinlog)', + 'settings.oauth.modal.machineClientHint': + "Gebruikt de client_credentials grant — geen redirect-URI's nodig. Het token wordt direct verstrekt via client_id + client_secret en handelt namens jou binnen de geselecteerde scopes.", + 'settings.oauth.modal.machineClientUsage': + 'Token ophalen: POST /oauth/token met grant_type=client_credentials, client_id en client_secret. Geen browser, geen vernieuwingstoken.', + 'settings.oauth.badge.machine': 'machine', + 'settings.account': 'Account', + 'settings.about': 'Over', + 'settings.about.reportBug': 'Bug melden', + 'settings.about.reportBugHint': 'Probleem gevonden? Laat het ons weten', + 'settings.about.featureRequest': 'Feature aanvragen', + 'settings.about.featureRequestHint': 'Stel een nieuwe functie voor', + 'settings.about.wikiHint': 'Documentatie en handleidingen', + 'settings.about.supporters.badge': 'Maandelijkse Steuners', + 'settings.about.supporters.title': 'Reisgezelschap voor TREK', + 'settings.about.supporters.subtitle': + 'Terwijl jij je volgende route plant, plannen deze mensen mee aan de toekomst van TREK. Hun maandelijkse bijdrage gaat rechtstreeks naar ontwikkeling en echte uren — zodat TREK Open Source blijft.', + 'settings.about.supporters.since': 'steuner sinds {date}', + 'settings.about.supporters.tierEmpty': 'Wees de eerste', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + "TREK is een zelf-gehoste reisplanner die je helpt je reizen te organiseren van het eerste idee tot de laatste herinnering. Dagplanning, budget, paklijsten, foto's en nog veel meer — alles op één plek, op je eigen server.", + 'settings.about.madeWith': 'Gemaakt met', + 'settings.about.madeBy': + 'door Maurice en een groeiende open-source community.', + 'settings.username': 'Gebruikersnaam', + 'settings.email': 'E-mail', + 'settings.role': 'Rol', + 'settings.roleAdmin': 'Beheerder', + 'settings.oidcLinked': 'Gekoppeld met', + 'settings.changePassword': 'Wachtwoord wijzigen', + 'settings.mustChangePassword': + 'U moet uw wachtwoord wijzigen voordat u kunt doorgaan. Stel hieronder een nieuw wachtwoord in.', + 'settings.currentPassword': 'Huidig wachtwoord', + 'settings.currentPasswordRequired': 'Huidig wachtwoord is verplicht', + 'settings.newPassword': 'Nieuw wachtwoord', + 'settings.confirmPassword': 'Bevestig nieuw wachtwoord', + 'settings.updatePassword': 'Wachtwoord bijwerken', + 'settings.passwordRequired': 'Voer het huidige en nieuwe wachtwoord in', + 'settings.passwordTooShort': 'Wachtwoord moet minimaal 8 tekens bevatten', + 'settings.passwordMismatch': 'Wachtwoorden komen niet overeen', + 'settings.passwordWeak': + 'Wachtwoord moet hoofdletters, kleine letters, een cijfer en een speciaal teken bevatten', + 'settings.passwordChanged': 'Wachtwoord succesvol gewijzigd', + 'settings.deleteAccount': 'Account verwijderen', + 'settings.deleteAccountTitle': 'Account verwijderen?', + 'settings.deleteAccountWarning': + 'Je account en al je reizen, plaatsen en bestanden worden permanent verwijderd. Deze actie kan niet ongedaan worden gemaakt.', + 'settings.deleteAccountConfirm': 'Permanent verwijderen', + 'settings.deleteBlockedTitle': 'Verwijderen niet mogelijk', + 'settings.deleteBlockedMessage': + 'Je bent de enige beheerder. Maak eerst een andere gebruiker beheerder voordat je je account verwijdert.', + 'settings.roleUser': 'Gebruiker', + 'settings.saveProfile': 'Profiel opslaan', + 'settings.mfa.title': 'Tweefactorauthenticatie (2FA)', + 'settings.mfa.description': + 'Voegt een tweede stap toe bij het inloggen. Gebruik een authenticator-app (Google Authenticator, Authy, etc.).', + 'settings.mfa.requiredByPolicy': + 'Je beheerder vereist tweestapsverificatie. Stel hieronder een authenticator-app in voordat je verdergaat.', + 'settings.mfa.backupTitle': 'Back-upcodes', + 'settings.mfa.backupDescription': + 'Gebruik deze eenmalige codes als je geen toegang meer hebt tot je authenticator-app.', + 'settings.mfa.backupWarning': + 'Sla deze codes nu op. Elke code kan maar een keer worden gebruikt.', + 'settings.mfa.backupCopy': 'Codes kopiëren', + 'settings.mfa.backupDownload': 'TXT downloaden', + 'settings.mfa.backupPrint': 'Afdrukken / PDF', + 'settings.mfa.backupCopied': 'Back-upcodes gekopieerd', + 'settings.mfa.enabled': '2FA is ingeschakeld op je account.', + 'settings.mfa.disabled': '2FA is niet ingeschakeld.', + 'settings.mfa.setup': 'Authenticator instellen', + 'settings.mfa.scanQr': + 'Scan deze QR-code met je app of voer de sleutel handmatig in.', + 'settings.mfa.secretLabel': 'Geheime sleutel (handmatige invoer)', + 'settings.mfa.codePlaceholder': '6-cijferige code', + 'settings.mfa.enable': '2FA inschakelen', + 'settings.mfa.cancelSetup': 'Annuleren', + 'settings.mfa.disableTitle': '2FA uitschakelen', + 'settings.mfa.disableHint': + 'Voer je wachtwoord en een huidige code van je authenticator in.', + 'settings.mfa.disable': '2FA uitschakelen', + 'settings.mfa.toastEnabled': 'Tweefactorauthenticatie ingeschakeld', + 'settings.mfa.toastDisabled': 'Tweefactorauthenticatie uitgeschakeld', + 'settings.mfa.demoBlocked': 'Niet beschikbaar in demomodus', + 'settings.toast.mapSaved': 'Kaartinstellingen opgeslagen', + 'settings.toast.keysSaved': 'API-sleutels opgeslagen', + 'settings.toast.displaySaved': 'Weergave-instellingen opgeslagen', + 'settings.toast.profileSaved': 'Profiel opgeslagen', + 'settings.uploadAvatar': 'Profielfoto uploaden', + 'settings.removeAvatar': 'Profielfoto verwijderen', + 'settings.avatarUploaded': 'Profielfoto bijgewerkt', + 'settings.avatarRemoved': 'Profielfoto verwijderd', + 'settings.avatarError': 'Uploaden mislukt', + 'settings.bookingLabels': 'Routelabels voor boekingen', + 'settings.bookingLabelsHint': + 'Toon station- / luchthavennamen op de kaart. Indien uit, alleen het icoon.', + 'settings.notifyVersionAvailable': 'Nieuwe versie beschikbaar', + 'settings.notificationPreferences.noChannels': + 'Er zijn geen meldingskanalen geconfigureerd. Vraag een beheerder om e-mail- of webhookmeldingen in te stellen.', + 'settings.webhookUrl.label': 'Webhook-URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Voer je Discord-, Slack- of aangepaste webhook-URL in om meldingen te ontvangen.', + 'settings.webhookUrl.saved': 'Webhook-URL opgeslagen', + 'settings.webhookUrl.test': 'Testen', + 'settings.webhookUrl.testSuccess': 'Test-webhook succesvol verzonden', + 'settings.webhookUrl.testFailed': 'Test-webhook mislukt', + 'settings.ntfyUrl.topicLabel': 'Ntfy-onderwerp', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy-server-URL (optioneel)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Voer je Ntfy-onderwerp in om pushmeldingen te ontvangen. Laat het serverveld leeg om de standaard te gebruiken die door je beheerder is ingesteld.', + 'settings.ntfyUrl.tokenLabel': 'Toegangstoken (optioneel)', + 'settings.ntfyUrl.tokenHint': + 'Vereist voor onderwerpen die met een wachtwoord zijn beveiligd.', + 'settings.ntfyUrl.saved': 'Ntfy-instellingen opgeslagen', + 'settings.ntfyUrl.test': 'Testen', + 'settings.ntfyUrl.testSuccess': 'Test-Ntfy-melding succesvol verzonden', + 'settings.ntfyUrl.testFailed': 'Test-Ntfy-melding mislukt', + 'settings.ntfyUrl.tokenCleared': 'Toegangstoken gewist', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/nl/share.ts b/shared/src/i18n/nl/share.ts new file mode 100644 index 00000000..a3f0a208 --- /dev/null +++ b/shared/src/i18n/nl/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Openbare link', + 'share.linkHint': + 'Maak een link die iedereen kan gebruiken om deze reis te bekijken zonder in te loggen. Alleen-lezen — bewerken niet mogelijk.', + 'share.createLink': 'Link aanmaken', + 'share.deleteLink': 'Link verwijderen', + 'share.createError': 'Kon link niet aanmaken', + 'share.permMap': 'Kaart en plan', + 'share.permBookings': 'Boekingen', + 'share.permPacking': 'Paklijst', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/nl/shared.ts b/shared/src/i18n/nl/shared.ts new file mode 100644 index 00000000..308e87fd --- /dev/null +++ b/shared/src/i18n/nl/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link verlopen of ongeldig', + 'shared.expiredHint': 'Deze gedeelde reislink is niet meer actief.', + 'shared.readOnly': 'Alleen-lezen weergave', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Boekingen', + 'shared.tabPacking': 'Paklijst', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'dagen', + 'shared.places': 'plaatsen', + 'shared.other': 'Overig', + 'shared.totalBudget': 'Totaal budget', + 'shared.messages': 'berichten', + 'shared.sharedVia': 'Gedeeld via', + 'shared.confirmed': 'Bevestigd', + 'shared.pending': 'In afwachting', +}; +export default shared; diff --git a/shared/src/i18n/nl/stats.ts b/shared/src/i18n/nl/stats.ts new file mode 100644 index 00000000..ca290354 --- /dev/null +++ b/shared/src/i18n/nl/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Landen', + 'stats.cities': 'Steden', + 'stats.trips': 'Reizen', + 'stats.places': 'Plaatsen', + 'stats.worldProgress': 'Wereldvoortgang', + 'stats.visited': 'bezocht', + 'stats.remaining': 'resterend', + 'stats.visitedCountries': 'Bezochte landen', +}; +export default stats; diff --git a/shared/src/i18n/nl/system_notice.ts b/shared/src/i18n/nl/system_notice.ts new file mode 100644 index 00000000..eb423d37 --- /dev/null +++ b/shared/src/i18n/nl/system_notice.ts @@ -0,0 +1,61 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Welkom bij TREK', + 'system_notice.welcome_v1.body': + "Jouw alles-in-één reisplanner. Maak reisschema's, deel trips met vrienden en blijf georganiseerd — online en offline.", + 'system_notice.welcome_v1.cta_label': 'Reis plannen', + 'system_notice.welcome_v1.hero_alt': + 'Schilderachtige reisbestemming met TREK interface', + 'system_notice.welcome_v1.highlight_plan': "Dag-voor-dag reisschema's", + 'system_notice.welcome_v1.highlight_share': 'Samenwerken met reisgezelschap', + 'system_notice.welcome_v1.highlight_offline': 'Werkt offline op mobiel', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Vorige melding', + 'system_notice.pager.next': 'Volgende melding', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Ga naar melding {n}', + 'system_notice.pager.position': 'Melding {current} van {total}', + 'system_notice.v3_photos.title': "Foto's zijn verplaatst in 3.0", + 'system_notice.v3_photos.body': + "**Foto's** in de Reisplanner zijn verwijderd. Je foto's zijn veilig — TREK heeft je Immich- of Synology-bibliotheek nooit gewijzigd.\n\nFoto's leven nu in de **Journey**-addon. Journey is optioneel — als het nog niet beschikbaar is, vraag je admin het te activeren via Admin → Addons.", + 'system_notice.v3_journey.title': 'Maak kennis met Journey — reisdagboek', + 'system_notice.v3_journey.body': + 'Documenteer je reizen als rijke verhalen met tijdlijnen, fotogalerijen en interactieve kaarten.', + 'system_notice.v3_journey.cta_label': 'Journey openen', + 'system_notice.v3_journey.highlight_timeline': + 'Dag-voor-dag tijdlijn & galerij', + 'system_notice.v3_journey.highlight_photos': + 'Importeer van Immich of Synology', + 'system_notice.v3_journey.highlight_share': + 'Openbaar delen — geen login vereist', + 'system_notice.v3_journey.highlight_export': 'Exporteer als PDF-fotoboek', + 'system_notice.v3_features.title': 'Meer hoogtepunten in 3.0', + 'system_notice.v3_features.body': + 'Nog een paar dingen die het weten waard zijn in deze release.', + 'system_notice.v3_features.highlight_dashboard': + 'Mobile-first dashboard herontwerp', + 'system_notice.v3_features.highlight_offline': + 'Volledige offline modus als PWA', + 'system_notice.v3_features.highlight_search': 'Realtime plaatsautocomplete', + 'system_notice.v3_features.highlight_import': + 'Importeer plaatsen uit KMZ/KML-bestanden', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1-upgrade', + 'system_notice.v3_mcp.body': + 'De MCP-integratie is volledig vernieuwd. OAuth 2.1 is nu de aanbevolen authenticatiemethode. Statische tokens (trek_…) zijn verouderd en worden verwijderd in een toekomstige versie.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 aanbevolen (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': + '24 gedetailleerde toestemmingsscopes', + 'system_notice.v3_mcp.highlight_deprecated': + 'Statische trek_-tokens verouderd', + 'system_notice.v3_mcp.highlight_tools': 'Uitgebreide tools & prompts', + 'system_notice.v3_thankyou.title': 'Een persoonlijk woord van mij', + 'system_notice.v3_thankyou.body': + 'Voordat je verdergaat — ik wil even stilstaan.\n\nTREK begon als een zijproject dat ik bouwde voor mijn eigen reizen. Ik had nooit gedacht dat het zou uitgroeien tot iets waar 4.000 van jullie op vertrouwen om avonturen te plannen. Elke ster, elke issue, elk functieverzoek — ik lees ze allemaal, en ze houden me op de been tijdens de late avonden tussen een fulltime baan en de universiteit.\n\nIk wil dat jullie weten: TREK zal altijd open source zijn, altijd self-hosted, altijd van jullie. Geen tracking, geen abonnementen, geen addertjes. Gewoon een tool gebouwd door iemand die net zo veel van reizen houdt als jullie.\n\nSpeciale dank aan [jubnl](https://github.com/jubnl) — je bent een ongelooflijke medewerker geworden. Zo veel van wat 3.0 geweldig maakt draagt jouw vingerafdruk. Bedankt dat je in dit project geloofde toen het nog ruw was.\n\nEn aan ieder van jullie die een bug meldde, een string vertaalde, TREK deelde met een vriend of het simpelweg gebruikte om een reis te plannen — **bedankt**. Jullie zijn de reden dat dit bestaat.\n\nOp nog vele avonturen samen.\n\n— Maurice\n\n---\n\n[Sluit je aan bij de community op Discord](https://discord.gg/7Q6M6jDwzf)\n\nAls TREK je reizen beter maakt, houdt een [klein kopje koffie](https://ko-fi.com/mauriceboe) altijd de lichten aan.', + 'system_notice.v3014_whitespace_collision.title': + 'Actie vereist: gebruikersaccountconflict', + 'system_notice.v3014_whitespace_collision.body': + 'De 3.0.14-upgrade heeft één of meer conflicten in gebruikersnaam of e-mailadres gedetecteerd, veroorzaakt door spaties aan het begin of einde van opgeslagen waarden. Getroffen accounts zijn automatisch hernoemd. Controleer de serverlogboeken op regels die beginnen met **[migration] WHITESPACE COLLISION** om te achterhalen welke accounts moeten worden beoordeeld.', +}; +export default system_notice; diff --git a/shared/src/i18n/nl/todo.ts b/shared/src/i18n/nl/todo.ts new file mode 100644 index 00000000..ac17300e --- /dev/null +++ b/shared/src/i18n/nl/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Paklijst', + 'todo.subtab.todo': 'Taken', + 'todo.completed': 'voltooid', + 'todo.filter.all': 'Alles', + 'todo.filter.open': 'Openstaand', + 'todo.filter.done': 'Klaar', + 'todo.uncategorized': 'Zonder categorie', + 'todo.namePlaceholder': 'Taaknaam', + 'todo.descriptionPlaceholder': 'Beschrijving (optioneel)', + 'todo.unassigned': 'Niet toegewezen', + 'todo.noCategory': 'Geen categorie', + 'todo.hasDescription': 'Heeft beschrijving', + 'todo.addItem': 'Nieuwe taak', + 'todo.sidebar.sortBy': 'Sorteren op', + 'todo.priority': 'Prioriteit', + 'todo.newCategoryLabel': 'nieuw', + 'todo.newCategory': 'Categorienaam', + 'todo.addCategory': 'Categorie toevoegen', + 'todo.newItem': 'Nieuwe taak', + 'todo.empty': 'Nog geen taken. Voeg een taak toe om te beginnen!', + 'todo.filter.my': 'Mijn taken', + 'todo.filter.overdue': 'Verlopen', + 'todo.sidebar.tasks': 'Taken', + 'todo.sidebar.categories': 'Categorieën', + 'todo.detail.title': 'Taak', + 'todo.detail.description': 'Beschrijving', + 'todo.detail.category': 'Categorie', + 'todo.detail.dueDate': 'Vervaldatum', + 'todo.detail.assignedTo': 'Toegewezen aan', + 'todo.detail.delete': 'Verwijderen', + 'todo.detail.save': 'Wijzigingen opslaan', + 'todo.detail.create': 'Taak aanmaken', + 'todo.detail.priority': 'Prioriteit', + 'todo.detail.noPriority': 'Geen', + 'todo.sortByPrio': 'Prioriteit', +}; +export default todo; diff --git a/shared/src/i18n/nl/transport.ts b/shared/src/i18n/nl/transport.ts new file mode 100644 index 00000000..d7d79b33 --- /dev/null +++ b/shared/src/i18n/nl/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Vervoer toevoegen', + 'transport.modalTitle.create': 'Vervoer toevoegen', + 'transport.modalTitle.edit': 'Vervoer bewerken', + 'transport.title': 'Transport', + 'transport.addManual': 'Handmatig transport', +}; +export default transport; diff --git a/shared/src/i18n/nl/trip.ts b/shared/src/i18n/nl/trip.ts new file mode 100644 index 00000000..42930739 --- /dev/null +++ b/shared/src/i18n/nl/trip.ts @@ -0,0 +1,32 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transport', + 'trip.tabs.reservations': 'Boekingen', + 'trip.tabs.reservationsShort': 'Boek', + 'trip.tabs.packing': 'Paklijst', + 'trip.tabs.packingShort': 'Inpakken', + 'trip.tabs.lists': 'Lijsten', + 'trip.tabs.listsShort': 'Lijsten', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'Bestanden', + 'trip.loading': 'Reis laden...', + 'trip.loadingPhotos': 'Plaatsfoto laden...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Plaatsen', + 'trip.toast.placeUpdated': 'Plaats bijgewerkt', + 'trip.toast.placeAdded': 'Plaats toegevoegd', + 'trip.toast.placeDeleted': 'Plaats verwijderd', + 'trip.toast.selectDay': 'Selecteer eerst een dag', + 'trip.toast.assignedToDay': 'Plaats toegewezen aan dag', + 'trip.toast.reorderError': 'Herordenen mislukt', + 'trip.toast.reservationUpdated': 'Reservering bijgewerkt', + 'trip.toast.reservationAdded': 'Reservering toegevoegd', + 'trip.toast.deleted': 'Verwijderd', + 'trip.confirm.deletePlace': + 'Weet je zeker dat je deze plaats wilt verwijderen?', + 'trip.confirm.deletePlaces': '{count} plaatsen verwijderen?', + 'trip.toast.placesDeleted': '{count} plaatsen verwijderd', +}; +export default trip; diff --git a/shared/src/i18n/nl/trips.ts b/shared/src/i18n/nl/trips.ts new file mode 100644 index 00000000..601215be --- /dev/null +++ b/shared/src/i18n/nl/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} verwijderd', + 'trips.memberRemoveError': 'Verwijderen mislukt', + 'trips.memberAdded': '{username} toegevoegd', + 'trips.memberAddError': 'Toevoegen mislukt', + 'trips.reminder': 'Herinnering', + 'trips.reminderNone': 'Geen', + 'trips.reminderDay': 'dag', + 'trips.reminderDays': 'dagen', + 'trips.reminderCustom': 'Aangepast', + 'trips.reminderDaysBefore': 'dagen voor vertrek', + 'trips.reminderDisabledHint': + 'Reisherinneringen zijn uitgeschakeld. Schakel ze in via Admin > Instellingen > Meldingen.', +}; +export default trips; diff --git a/shared/src/i18n/nl/undo.ts b/shared/src/i18n/nl/undo.ts new file mode 100644 index 00000000..65c6da94 --- /dev/null +++ b/shared/src/i18n/nl/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Ongedaan maken', + 'undo.tooltip': 'Ongedaan maken: {action}', + 'undo.assignPlace': 'Locatie aan dag toegewezen', + 'undo.removeAssignment': 'Locatie uit dag verwijderd', + 'undo.reorder': 'Locaties hergeordend', + 'undo.optimize': 'Route geoptimaliseerd', + 'undo.deletePlace': 'Locatie verwijderd', + 'undo.deletePlaces': 'Plaatsen verwijderd', + 'undo.moveDay': 'Locatie naar andere dag verplaatst', + 'undo.lock': 'Vergrendeling locatie gewijzigd', + 'undo.importGpx': 'GPX-import', + 'undo.importKeyholeMarkup': 'KMZ/KML-import', + 'undo.importGoogleList': 'Google Maps-import', + 'undo.importNaverList': 'Naver Maps-import', + 'undo.addPlace': 'Locatie toegevoegd', + 'undo.done': 'Ongedaan gemaakt: {action}', +}; +export default undo; diff --git a/shared/src/i18n/nl/vacay.ts b/shared/src/i18n/nl/vacay.ts new file mode 100644 index 00000000..2b99888f --- /dev/null +++ b/shared/src/i18n/nl/vacay.ts @@ -0,0 +1,105 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Plan en beheer vakantiedagen', + 'vacay.settings': 'Instellingen', + 'vacay.year': 'Jaar', + 'vacay.addYear': 'Volgend jaar toevoegen', + 'vacay.addPrevYear': 'Vorig jaar toevoegen', + 'vacay.removeYear': 'Jaar verwijderen', + 'vacay.removeYearConfirm': '{year} verwijderen?', + 'vacay.removeYearHint': + 'Alle vakantie-invoeren en bedrijfsvakanties voor dit jaar worden permanent verwijderd.', + 'vacay.remove': 'Verwijderen', + 'vacay.persons': 'Personen', + 'vacay.noPersons': 'Geen personen toegevoegd', + 'vacay.addPerson': 'Persoon toevoegen', + 'vacay.editPerson': 'Persoon bewerken', + 'vacay.removePerson': 'Persoon verwijderen', + 'vacay.removePersonConfirm': '{name} verwijderen?', + 'vacay.removePersonHint': + 'Alle vakantie-invoeren voor deze persoon worden permanent verwijderd.', + 'vacay.personName': 'Naam', + 'vacay.personNamePlaceholder': 'Naam invoeren', + 'vacay.color': 'Kleur', + 'vacay.add': 'Toevoegen', + 'vacay.legend': 'Legenda', + 'vacay.publicHoliday': 'Feestdag', + 'vacay.companyHoliday': 'Bedrijfsvakantie', + 'vacay.weekend': 'Weekend', + 'vacay.modeVacation': 'Vakantie', + 'vacay.modeCompany': 'Bedrijfsvakantie', + 'vacay.entitlement': 'Recht', + 'vacay.entitlementDays': 'Dagen', + 'vacay.used': 'Gebruikt', + 'vacay.remaining': 'Resterend', + 'vacay.carriedOver': 'van {year}', + 'vacay.blockWeekends': 'Weekenden blokkeren', + 'vacay.blockWeekendsHint': 'Voorkom vakantie-invoeren op zaterdag en zondag', + 'vacay.weekendDays': 'Weekenddagen', + 'vacay.mon': 'Ma', + 'vacay.tue': 'Di', + 'vacay.wed': 'Wo', + 'vacay.thu': 'Do', + 'vacay.fri': 'Vr', + 'vacay.sat': 'Za', + 'vacay.sun': 'Zo', + 'vacay.publicHolidays': 'Feestdagen', + 'vacay.publicHolidaysHint': 'Markeer feestdagen in de kalender', + 'vacay.selectCountry': 'Selecteer land', + 'vacay.selectRegion': 'Selecteer regio (optioneel)', + 'vacay.companyHolidays': 'Bedrijfsvakanties', + 'vacay.companyHolidaysHint': + 'Sta het markeren van bedrijfsbrede vakantiedagen toe', + 'vacay.companyHolidaysNoDeduct': + 'Bedrijfsvakanties worden niet afgetrokken van vakantiedagen.', + 'vacay.weekStart': 'Week begint op', + 'vacay.weekStartHint': 'Kies of de kalenderweek op maandag of zondag begint', + 'vacay.carryOver': 'Overdracht', + 'vacay.carryOverHint': + 'Draag resterende vakantiedagen automatisch over naar het volgende jaar', + 'vacay.sharing': 'Delen', + 'vacay.sharingHint': 'Deel je vakantieplan met andere TREK-gebruikers', + 'vacay.owner': 'Eigenaar', + 'vacay.shareEmailPlaceholder': 'E-mail van TREK-gebruiker', + 'vacay.shareSuccess': 'Plan succesvol gedeeld', + 'vacay.shareError': 'Plan delen mislukt', + 'vacay.dissolve': 'Fusie opheffen', + 'vacay.dissolveHint': + 'Kalenders weer scheiden. Je invoeren blijven behouden.', + 'vacay.dissolveAction': 'Opheffen', + 'vacay.dissolved': 'Kalender gescheiden', + 'vacay.fusedWith': 'Gefuseerd met', + 'vacay.you': 'jij', + 'vacay.noData': 'Geen gegevens', + 'vacay.changeColor': 'Kleur wijzigen', + 'vacay.inviteUser': 'Gebruiker uitnodigen', + 'vacay.inviteHint': + 'Nodig een andere TREK-gebruiker uit om een gecombineerde vakantiekalender te delen.', + 'vacay.selectUser': 'Selecteer gebruiker', + 'vacay.sendInvite': 'Uitnodiging verzenden', + 'vacay.inviteSent': 'Uitnodiging verzonden', + 'vacay.inviteError': 'Uitnodiging verzenden mislukt', + 'vacay.pending': 'in behandeling', + 'vacay.noUsersAvailable': 'Geen gebruikers beschikbaar', + 'vacay.accept': 'Accepteren', + 'vacay.decline': 'Afwijzen', + 'vacay.acceptFusion': 'Accepteren en fuseren', + 'vacay.inviteTitle': 'Fusieverzoek', + 'vacay.inviteWantsToFuse': 'wil een vakantiekalender met je delen.', + 'vacay.fuseInfo1': + 'Jullie zien allebei alle vakantie-invoeren in één gedeelde kalender.', + 'vacay.fuseInfo2': + 'Beide partijen kunnen invoeren voor elkaar aanmaken en bewerken.', + 'vacay.fuseInfo3': + 'Beide partijen kunnen invoeren verwijderen en vakantierechten wijzigen.', + 'vacay.fuseInfo4': + 'Instellingen zoals feestdagen en bedrijfsvakanties worden gedeeld.', + 'vacay.fuseInfo5': + 'De fusie kan op elk moment door beide partijen worden opgeheven. Je invoeren blijven behouden.', + 'vacay.addCalendar': 'Kalender toevoegen', + 'vacay.calendarColor': 'Kleur', + 'vacay.calendarLabel': 'Label', + 'vacay.noCalendars': 'Geen kalenders', +}; +export default vacay; diff --git a/shared/src/i18n/pl/admin.ts b/shared/src/i18n/pl/admin.ts new file mode 100644 index 00000000..3beb1c54 --- /dev/null +++ b/shared/src/i18n/pl/admin.ts @@ -0,0 +1,366 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.smtp.title': 'E-maile i powiadomienia', + 'admin.smtp.hint': + 'Konfiguracja SMTP dla powiadomień e-mail. Opcjonalnie: URL Webhooka dla Discorda, Slacka, itp.', + 'admin.smtp.testButton': 'Wyślij testowego e-maila', + 'admin.smtp.testSuccess': 'Testowy e-mail został wysłany pomyślnie', + 'admin.smtp.testFailed': 'Nie udało się wysłać testowego e-maila', + 'admin.title': 'Administracja', + 'admin.subtitle': 'Zarządzanie użytkownikami i ustawienia systemowe', + 'admin.tabs.users': 'Użytkownicy', + 'admin.tabs.categories': 'Kategorie', + 'admin.tabs.backup': 'Backupy', + 'admin.tabs.notifications': 'Powiadomienia', + 'admin.tabs.audit': 'Audit', + 'admin.stats.users': 'Użytkownicy', + 'admin.stats.trips': 'Podróże', + 'admin.stats.places': 'Miejsca', + 'admin.stats.photos': 'Zdjęcia', + 'admin.stats.files': 'Pliki', + 'admin.table.user': 'Użytkownik', + 'admin.table.email': 'E-mail', + 'admin.table.role': 'Rola', + 'admin.table.created': 'Utworzono', + 'admin.table.lastLogin': 'Ostatnie logowanie', + 'admin.table.actions': 'Akcje', + 'admin.you': '(Ty)', + 'admin.editUser': 'Edytuj użytkownika', + 'admin.newPassword': 'Nowe hasło', + 'admin.newPasswordHint': 'Pozostaw puste, aby zachować obecne hasło', + 'admin.deleteUser': + 'Usunąć użytkownika "{name}"? Wszystkie jego podróże zostaną trwale usunięte.', + 'admin.deleteUserTitle': 'Usuń użytkownika', + 'admin.newPasswordPlaceholder': 'Podaj nowe hasło...', + 'admin.toast.loadError': 'Nie udało się załadować danych administratora', + 'admin.toast.userUpdated': 'Użytkownik został zaktualizowany', + 'admin.toast.updateError': 'Nie udało się zaktualizować użytkownika', + 'admin.toast.userDeleted': 'Użytkownik został usunięty', + 'admin.toast.deleteError': 'Nie udało się usunąć użytkownika', + 'admin.toast.cannotDeleteSelf': 'Nie można usunąć własnego konta', + 'admin.toast.userCreated': 'Użytkownik został utworzony', + 'admin.toast.createError': 'Nie udało się utworzyć użytkownika', + 'admin.toast.fieldsRequired': 'Nazwa użytkownika, e-mail i hasło są wymagane', + 'admin.createUser': 'Utwórz użytkownika', + 'admin.invite.title': 'Linki zaproszeń', + 'admin.invite.subtitle': 'Twórz jednorazowe linki do rejestracji', + 'admin.invite.create': 'Utwórz link', + 'admin.invite.createAndCopy': 'Utwórz i skopiuj', + 'admin.invite.empty': 'Nie utworzono jeszcze żadnych linków zaproszeń', + 'admin.invite.maxUses': 'Maksymalna liczba użyć', + 'admin.invite.expiry': 'Wygasa po', + 'admin.invite.uses': 'użycia', + 'admin.invite.expiresAt': 'wygasa', + 'admin.invite.createdBy': 'utworzone przez', + 'admin.invite.active': 'Aktywny', + 'admin.invite.expired': 'Wygasł', + 'admin.invite.usedUp': 'Wykorzystany', + 'admin.invite.copied': 'Link zaproszenia został skopiowany do schowka', + 'admin.invite.copyLink': 'Skopiuj link', + 'admin.invite.deleted': 'Link zaproszenia został usunięty', + 'admin.invite.createError': 'Nie udało się utworzyć linku zaproszenia', + 'admin.invite.deleteError': 'Nie udało się usunąć linku zaproszenia', + 'admin.tabs.settings': 'Ustawienia', + 'admin.allowRegistration': 'Zezwól na rejestrację', + 'admin.allowRegistrationHint': + 'Nowi użytkownicy mogą się rejestrować samodzielnie', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Wymagaj uwierzytelniania dwuskładnikowego (2FA)', + 'admin.requireMfaHint': + 'Użytkownicy bez 2FA muszą ukończyć konfigurację w Ustawieniach zanim zaczną korzystać z aplikacji.', + 'admin.apiKeys': 'Klucze API', + 'admin.apiKeysHint': + 'Opcjonalne. Umożliwiają pobieranie większej ilości danych o miejscach, takich jak zdjęcia i pogoda.', + 'admin.mapsKey': 'Klucz Google Maps API', + 'admin.mapsKeyHint': + 'Wymagany do wyszukiwania miejsc. Uzyskaj go na console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Bez klucza API, OpenStreetMap jest wykorzystywany do wyszukiwania miejsc. Z kluczem API Google, zdjęcia, oceny i godziny otwarcia również mogą być pobierane. Uzyskaj go na console.cloud.google.com.', + 'admin.recommended': 'Polecane', + 'admin.weatherKey': 'Klucz OpenWeatherMap API', + 'admin.weatherKeyHint': + 'Do danych pogodowych. Uzyskaj go bezpłatnie na openweathermap.org', + 'admin.validateKey': 'Testuj', + 'admin.keyValid': 'Połączono', + 'admin.keyInvalid': 'Niepoprawny', + 'admin.keySaved': 'Klucze API zostały zapisane', + 'admin.oidcTitle': 'Logowanie jednokrotne (OIDC)', + 'admin.oidcSubtitle': + 'Zezwól na logowanie za pomocą zewnętrznych dostawców, takich jak Google, Apple, Authentik lub Keycloak.', + 'admin.oidcDisplayName': 'Wyświetlana nazwa', + 'admin.oidcIssuer': 'URL wystawcy', + 'admin.oidcIssuerHint': + 'Adres URL wystawcy OpenID Connect dostawcy, np. https://accounts.google.com', + 'admin.oidcSaved': 'Konfiguracja OIDC została zapisana', + 'admin.oidcOnlyMode': 'Wyłącz uwierzytelnianie hasłem', + 'admin.oidcOnlyModeHint': + 'Po włączeniu dozwolone jest tylko logowanie jednokrotne. Logowanie i rejestracja za pomocą hasła są zablokowane.', + 'admin.fileTypes': 'Dozwolone typy plików', + 'admin.fileTypesHint': + 'Ustaw, które typy plików mogą być przesyłane przez użytkowników.', + 'admin.fileTypesFormat': + 'Rozszerzenia oddzielone przecinkami (np. jpg,png,pdf,doc). Użyj * aby zezwolić na wszystkie typy.', + 'admin.fileTypesSaved': 'Ustawienia typów plików zostały zapisane', + 'admin.placesPhotos.title': 'Zdjęcia miejsc', + 'admin.placesPhotos.subtitle': + 'Pobiera zdjęcia z Google Places API. Wyłącz, aby zaoszczędzić limit API. Zdjęcia z Wikimedia nie są objęte.', + 'admin.placesAutocomplete.title': 'Autouzupełnianie miejsc', + 'admin.placesAutocomplete.subtitle': + 'Używa Google Places API do sugestii wyszukiwania. Wyłącz, aby zaoszczędzić limit API.', + 'admin.placesDetails.title': 'Szczegóły miejsca', + 'admin.placesDetails.subtitle': + 'Pobiera szczegółowe informacje o miejscu (godziny, ocena, strona) z Google Places API. Wyłącz, aby zaoszczędzić limit API.', + 'admin.bagTracking.title': 'Kontrola bagażu', + 'admin.bagTracking.subtitle': + 'Włącz wagę i przypisywanie do toreb dla przedmiotów do pakowania', + 'admin.collab.chat.title': 'Czat', + 'admin.collab.chat.subtitle': 'Wiadomości w czasie rzeczywistym', + 'admin.collab.notes.title': 'Notatki', + 'admin.collab.notes.subtitle': 'Wspólne notatki i dokumenty', + 'admin.collab.polls.title': 'Ankiety', + 'admin.collab.polls.subtitle': 'Ankiety grupowe i głosowania', + 'admin.collab.whatsnext.title': 'Co dalej', + 'admin.collab.whatsnext.subtitle': 'Sugestie aktywności i następne kroki', + 'admin.tabs.config': 'Personalizacja', + 'admin.tabs.defaults': 'Domyślne ustawienia', + 'admin.defaultSettings.title': 'Domyślne ustawienia użytkownika', + 'admin.defaultSettings.description': + 'Ustaw domyślne wartości dla całej instancji. Użytkownicy, którzy nie zmienili ustawienia, zobaczą te wartości. Ich własne zmiany zawsze mają pierwszeństwo.', + 'admin.defaultSettings.saved': 'Domyślne zapisane', + 'admin.defaultSettings.reset': 'Przywróć wbudowaną wartość domyślną', + 'admin.defaultSettings.resetToBuiltIn': 'przywróć', + 'admin.tabs.templates': 'Szablony pakowania', + 'admin.packingTemplates.title': 'Szablony pakowania', + 'admin.packingTemplates.subtitle': + 'Twórz szablony list pakowania do wielokrotnego użycia dla swoich podróży', + 'admin.packingTemplates.create': 'Nowy szablon', + 'admin.packingTemplates.namePlaceholder': + 'Nazwa szablonu (np. Wakacje na plaży)', + 'admin.packingTemplates.empty': 'Nie utworzono jeszcze żadnych szablonów', + 'admin.packingTemplates.items': 'przedmiotów', + 'admin.packingTemplates.categories': 'kategorie', + 'admin.packingTemplates.itemName': 'Nazwa przedmiotu', + 'admin.packingTemplates.itemCategory': 'Kategoria', + 'admin.packingTemplates.categoryName': 'Nazwa kategorii (np. Ubrania)', + 'admin.packingTemplates.addCategory': 'Dodaj kategorię', + 'admin.packingTemplates.created': 'Szablon został utworzony', + 'admin.packingTemplates.deleted': 'Szablon został usunięty', + 'admin.packingTemplates.loadError': 'Nie udało się załadować szablonów', + 'admin.packingTemplates.createError': 'Nie udało się utworzyć szablonu', + 'admin.packingTemplates.deleteError': 'Nie udało się usunąć szablonu', + 'admin.packingTemplates.saveError': 'Nie udało się zapisać szablonu', + 'admin.tabs.addons': 'Dodatki', + 'admin.addons.title': 'Dodatki', + 'admin.addons.subtitle': + 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w TREK.', + 'admin.addons.catalog.packing.name': 'Listy', + 'admin.addons.catalog.packing.description': + 'Listy pakowania i zadania do wykonania dla Twoich podróży', + 'admin.addons.catalog.budget.name': 'Budżet', + 'admin.addons.catalog.budget.description': + 'Śledź wydatki i planuj budżet podróży', + 'admin.addons.catalog.documents.name': 'Dokumenty', + 'admin.addons.catalog.documents.description': + 'Przechowuj i zarządzaj dokumentami podróżnymi', + 'admin.addons.catalog.vacay.name': 'Urlopy', + 'admin.addons.catalog.vacay.description': + 'Osobisty planer urlopu z widokiem kalendarza', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Mapa świata z odwiedzonymi krajami i statystykami podróży', + 'admin.addons.catalog.collab.name': 'Współpraca', + 'admin.addons.catalog.collab.description': + 'Notatki w czasie rzeczywistym, ankiety i czat do planowania podróży', + 'admin.addons.catalog.memories.name': 'Zdjęcia (Immich)', + 'admin.addons.catalog.memories.description': + 'Udostępniaj zdjęcia z podróży za pośrednictwem swojej instancji Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol dla integracji asystenta AI', + 'admin.addons.subtitleBefore': + 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w ', + 'admin.addons.subtitleAfter': '.', + 'admin.addons.enabled': 'Włączone', + 'admin.addons.disabled': 'Wyłączone', + 'admin.addons.type.trip': 'Podróż', + 'admin.addons.type.global': 'Globalne', + 'admin.addons.type.integration': 'Integracja', + 'admin.addons.tripHint': 'Dostępne jako zakładka w każdej podróży', + 'admin.addons.globalHint': 'Dostępne jako osobna sekcja w menu głównym', + 'admin.addons.integrationHint': + 'Usługi backendowe i integracje API bez dedykowanej strony', + 'admin.addons.toast.updated': 'Dodatek został zaktualizowany', + 'admin.addons.toast.error': 'Nie udało się zaktualizować dodatku', + 'admin.addons.noAddons': 'Brak dostępnych dodatków', + 'admin.weather.title': 'Dane pogodowe', + 'admin.weather.badge': 'Od 24 marca 2026', + 'admin.weather.description': + 'TREK korzysta z Open-Meteo jako źródła danych pogodowych. Open-Meteo to darmowy, otwartoźródłowy serwis pogodowy — klucz API nie jest wymagany.', + 'admin.weather.forecast': '16-dniowa prognoza', + 'admin.weather.forecastDesc': 'Wcześniej 5 dni (OpenWeatherMap)', + 'admin.weather.climate': 'Historyczne dane klimatyczne', + 'admin.weather.climateDesc': + 'Średnie z ostatnich 85 lat dla dni poza 16-dniową prognozą', + 'admin.weather.requests': '10,000 zapytań / dzień', + 'admin.weather.requestsDesc': 'Bezpłatnie, bez klucza API', + 'admin.weather.locationHint': + 'Pogoda jest określana na podstawie pierwszego miejsca z przypisanymi współrzędnymi w danym dniu. Jeśli do dnia nie przypisano żadnego miejsca, jako punkt odniesienia używane jest dowolne miejsce z listy.', + 'admin.tabs.mcpTokens': 'Dostęp MCP', + 'admin.mcpTokens.title': 'Dostęp MCP', + 'admin.mcpTokens.subtitle': + 'Zarządzaj sesjami OAuth i tokenami API dla wszystkich użytkowników', + 'admin.mcpTokens.sectionTitle': 'Tokeny API', + 'admin.mcpTokens.owner': 'Właściciel', + 'admin.mcpTokens.tokenName': 'Nazwa tokenu', + 'admin.mcpTokens.created': 'Utworzono', + 'admin.mcpTokens.lastUsed': 'Ostatnio użyto', + 'admin.mcpTokens.never': 'Nigdy', + 'admin.mcpTokens.empty': 'Nie utworzono jeszcze żadnych tokenów MCP', + 'admin.mcpTokens.deleteTitle': 'Usuń token', + 'admin.mcpTokens.deleteMessage': + 'Spowoduje to natychmiastowe unieważnienie tokenu. Użytkownik straci dostęp MCP przez ten token.', + 'admin.mcpTokens.deleteSuccess': 'Token został usunięty', + 'admin.mcpTokens.deleteError': 'Nie udało się usunąć tokenu', + 'admin.mcpTokens.loadError': 'Nie udało się załadować tokenów', + 'admin.oauthSessions.sectionTitle': 'Sesje OAuth', + 'admin.oauthSessions.clientName': 'Klient', + 'admin.oauthSessions.owner': 'Właściciel', + 'admin.oauthSessions.scopes': 'Uprawnienia', + 'admin.oauthSessions.created': 'Utworzono', + 'admin.oauthSessions.empty': 'Brak aktywnych sesji OAuth', + 'admin.oauthSessions.revokeTitle': 'Unieważnij sesję', + 'admin.oauthSessions.revokeMessage': + 'Ta sesja OAuth zostanie natychmiast unieważniona. Klient straci dostęp do MCP.', + 'admin.oauthSessions.revokeSuccess': 'Sesja unieważniona', + 'admin.oauthSessions.revokeError': 'Nie udało się unieważnić sesji', + 'admin.oauthSessions.loadError': 'Nie udało się załadować sesji OAuth', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Zdarzenia związane z bezpieczeństwem i administracją (kopie zapasowe, użytkownicy, MFA, ustawienia).', + 'admin.audit.empty': 'Brak zapisów w historii aktywności.', + 'admin.audit.refresh': 'Odśwież', + 'admin.audit.loadMore': 'Załaduj więcej', + 'admin.audit.showing': '{count} załadowanych · {total} łącznie', + 'admin.audit.col.time': 'Czas', + 'admin.audit.col.user': 'Użytkownik', + 'admin.audit.col.action': 'Akcja', + 'admin.audit.col.resource': 'Zasób', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Szczegóły', + 'admin.github.title': 'Historia wydań', + 'admin.github.subtitle': 'Najnowsze aktualizacje z {repo}', + 'admin.github.latest': 'Najnowsze', + 'admin.github.prerelease': 'Wersja testowa', + 'admin.github.showDetails': 'Pokaż szczegóły', + 'admin.github.hideDetails': 'Ukryj szczegóły', + 'admin.github.loadMore': 'Załaduj więcej', + 'admin.github.loading': 'Ładowanie...', + 'admin.github.error': 'Nie udało się załadować wydań', + 'admin.github.by': 'przez', + 'admin.github.support': 'Pomóż mi rozwijać TREK', + 'admin.update.available': 'Dostępna aktualizacja', + 'admin.update.text': + 'Dostępna jest wersja TREK {version}. Używasz {current}.', + 'admin.update.button': 'Zobacz na GitHubie', + 'admin.update.install': 'Zainstaluj aktualizację', + 'admin.update.confirmTitle': 'Zainstalować aktualizację?', + 'admin.update.confirmText': + 'TREK zostanie zaktualizowany z {current} do {version}. Serwer zostanie automatycznie zrestartowany po zakończeniu.', + 'admin.update.dataInfo': + 'Wszystkie twoje dane (podróże, użytkownicy, klucze API, przesłane pliki, urlopy, Atlas, budżety) zostaną zachowane.', + 'admin.update.warning': + 'Aplikacja będzie niedostępna przez krótki czas podczas restartu.', + 'admin.update.confirm': 'Zaktualizuj', + 'admin.update.installing': 'Aktualizowanie...', + 'admin.update.success': 'Aktualizacja zakończona! Serwer restartuje się...', + 'admin.update.failed': 'Aktualizacja nie powiodła się', + 'admin.update.backupHint': + 'Zalecamy utworzenie kopii zapasowej przed aktualizacją.', + 'admin.update.backupLink': 'Zrób kopię zapasową', + 'admin.update.howTo': 'Jak zaktualizować', + 'admin.update.dockerText': + 'Twoja instancja TREK działa w Dockerze. Aby zaktualizować do {version}, uruchom następujące polecenia na swoim serwerze:', + 'admin.update.reloadHint': 'Proszę odświeżyć stronę za kilka sekund.', + 'admin.notifications.title': 'Powiadomienia', + 'admin.notifications.hint': 'Wybierz jeden kanał powiadomień.', + 'admin.notifications.none': 'Wyłączone', + 'admin.notifications.email': 'E-mail (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Zapisz ustawienia powiadomień', + 'admin.notifications.saved': 'Ustawienia powiadomień zapisane', + 'admin.notifications.testWebhook': 'Wyślij testowy webhook', + 'admin.notifications.testWebhookSuccess': 'Testowy webhook wysłany pomyślnie', + 'admin.notifications.testWebhookFailed': 'Testowy webhook nie powiódł się', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Powiadomienia w aplikacji są zawsze aktywne i nie można ich globalnie wyłączyć.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook admina', + 'admin.notifications.adminWebhookPanel.hint': + 'Ten webhook służy wyłącznie do powiadomień admina (np. alertów o nowych wersjach). Jest niezależny od webhooków użytkowników i wysyła automatycznie, gdy URL jest skonfigurowany.', + 'admin.notifications.adminWebhookPanel.saved': 'URL webhooka admina zapisany', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Testowy webhook wysłany pomyślnie', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Wysyłanie testowego webhooka nie powiodło się', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Pozwala użytkownikom skonfigurować własne tematy ntfy dla powiadomień push. Ustaw domyślny serwer poniżej, aby wstępnie wypełnić ustawienia użytkownika.', + 'admin.notifications.testNtfy': 'Wyślij testowe Ntfy', + 'admin.notifications.testNtfySuccess': 'Testowe Ntfy wysłane pomyślnie', + 'admin.notifications.testNtfyFailed': + 'Wysyłanie testowego Ntfy nie powiodło się', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'Ten temat Ntfy jest używany wyłącznie do powiadomień admina (np. alertów o wersjach). Jest niezależny od tematów użytkowników i zawsze wysyła po skonfigurowaniu.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL serwera Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Używany również jako domyślny serwer dla powiadomień ntfy użytkowników. Pozostaw puste, aby użyć ntfy.sh. Użytkownicy mogą to nadpisać w swoich ustawieniach.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Temat admina', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Token dostępu (opcjonalne)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Token dostępu admina wyczyszczony', + 'admin.notifications.adminNtfyPanel.saved': 'Ustawienia admin Ntfy zapisane', + 'admin.notifications.adminNtfyPanel.test': 'Wyślij testowe Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Testowe Ntfy wysłane pomyślnie', + 'admin.notifications.adminNtfyPanel.testFailed': + 'Wysyłanie testowego Ntfy nie powiodło się', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin Ntfy zawsze wysyła po skonfigurowaniu tematu', + 'admin.notifications.adminNotificationsHint': + 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.', + 'admin.notifications.tripReminders.title': 'Przypomnienia o podróżach', + 'admin.notifications.tripReminders.hint': + 'Wysyła powiadomienie z przypomnieniem przed rozpoczęciem podróży (wymaga ustawienia dni przypomnienia dla podróży).', + 'admin.notifications.tripReminders.enabled': + 'Przypomnienia o podróżach włączone', + 'admin.notifications.tripReminders.disabled': + 'Przypomnienia o podróżach wyłączone', + 'admin.webhook.hint': + 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).', + 'admin.tabs.permissions': 'Uprawnienia', + 'admin.addons.catalog.journey.name': 'Dziennik podróży', + 'admin.addons.catalog.journey.description': + 'Śledzenie podróży i dziennik z zameldowaniami, zdjęciami i codziennymi historiami', +}; +export default admin; diff --git a/shared/src/i18n/pl/airport.ts b/shared/src/i18n/pl/airport.ts new file mode 100644 index 00000000..eb87e9bd --- /dev/null +++ b/shared/src/i18n/pl/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Kod lotniska lub miasto (np. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/pl/atlas.ts b/shared/src/i18n/pl/atlas.ts new file mode 100644 index 00000000..5fb652c7 --- /dev/null +++ b/shared/src/i18n/pl/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Twój ślad podróżniczy po świecie', + 'atlas.countries': 'Kraje', + 'atlas.trips': 'Podróże', + 'atlas.places': 'Miejsca', + 'atlas.unmark': 'Usuń', + 'atlas.confirmMark': 'Oznaczyć ten kraj jako odwiedzony?', + 'atlas.confirmUnmark': 'Usunąć ten kraj z listy odwiedzonych?', + 'atlas.confirmUnmarkRegion': 'Usunąć ten region z listy odwiedzonych?', + 'atlas.markVisited': 'Oznacz jako odwiedzony', + 'atlas.markVisitedHint': 'Dodaj ten kraj do listy odwiedzonych', + 'atlas.markRegionVisitedHint': 'Dodaj ten region do listy odwiedzonych', + 'atlas.addToBucket': 'Dodaj do listy marzeń', + 'atlas.addPoi': 'Dodaj miejsce', + 'atlas.bucketNamePlaceholder': 'Nazwa (kraj, miasto, miejsce...)', + 'atlas.month': 'Miesiąc', + 'atlas.year': 'Rok', + 'atlas.addToBucketHint': 'Zapisz jako miejsce, które chcesz odwiedzić', + 'atlas.bucketWhen': 'Kiedy planujesz je odwiedzić?', + 'atlas.statsTab': 'Statystyki', + 'atlas.bucketTab': 'Lista marzeń', + 'atlas.addBucket': 'Dodaj do listy marzeń', + 'atlas.bucketNotesPlaceholder': 'Notatki (opcjonalnie)', + 'atlas.bucketEmpty': 'Twoja lista marzeń jest pusta', + 'atlas.bucketEmptyHint': 'Dodaj miejsca, które chcesz odwiedzić', + 'atlas.days': 'Dni', + 'atlas.visitedCountries': 'Odwiedzone kraje', + 'atlas.cities': 'Miasta', + 'atlas.noData': 'Brak danych o podróżach', + 'atlas.noDataHint': + 'Utwórz podróż i dodaj miejsca, aby zobaczyć swoją mapę świata', + 'atlas.lastTrip': 'Ostatnia podróż', + 'atlas.nextTrip': 'Następna podróż', + 'atlas.daysLeft': 'dni do wyjazdu', + 'atlas.streak': 'Seria', + 'atlas.years': 'lata', + 'atlas.yearInRow': 'rok z rzędu', + 'atlas.yearsInRow': 'lat z rzędu', + 'atlas.tripIn': 'podróż w', + 'atlas.tripsIn': 'podróży w', + 'atlas.since': 'od', + 'atlas.europe': 'Europa', + 'atlas.asia': 'Azja', + 'atlas.northAmerica': 'Ameryka Pn.', + 'atlas.southAmerica': 'Ameryka Pd.', + 'atlas.africa': 'Afryka', + 'atlas.oceania': 'Oceania', + 'atlas.other': 'Inne', + 'atlas.firstVisit': 'Pierwsza podróż', + 'atlas.lastVisitLabel': 'Ostatnia podróż', + 'atlas.tripSingular': 'Podróż', + 'atlas.tripPlural': 'Podróże', + 'atlas.placeVisited': 'Odwiedzone miejsce', + 'atlas.placesVisited': 'Odwiedzone miejsca', + 'atlas.searchCountry': 'Szukaj kraju...', +}; +export default atlas; diff --git a/shared/src/i18n/pl/backup.ts b/shared/src/i18n/pl/backup.ts new file mode 100644 index 00000000..a440d721 --- /dev/null +++ b/shared/src/i18n/pl/backup.ts @@ -0,0 +1,81 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Kopia zapasowa danych', + 'backup.subtitle': 'Baza danych i wszystkie przesłane pliki', + 'backup.refresh': 'Odśwież', + 'backup.upload': 'Prześlij kopię zapasową', + 'backup.uploading': 'Przesyłanie...', + 'backup.create': 'Utwórz kopię zapasową', + 'backup.creating': 'Tworzenie...', + 'backup.empty': 'Brak kopii zapasowych', + 'backup.createFirst': 'Utwórz pierwszą kopię zapasową', + 'backup.download': 'Pobierz', + 'backup.restore': 'Przywróć', + 'backup.confirm.restore': + 'Przywrócić kopię zapasową "{name}"?\n\nWszystkie obecne dane zostaną zastąpione danymi z kopii zapasowej.', + 'backup.confirm.uploadRestore': + 'Przesłać i przywrócić plik kopii zapasowej "{name}"?\n\nWszystkie obecne dane zostaną nadpisane.', + 'backup.confirm.delete': 'Usunąć kopię zapasową "{name}"?', + 'backup.toast.loadError': 'Nie udało się załadować kopii zapasowych', + 'backup.toast.created': 'Kopia zapasowa została utworzona pomyślnie', + 'backup.toast.createError': 'Nie udało się utworzyć kopii zapasowej', + 'backup.toast.restored': + 'Kopia zapasowa została przywrócona. Strona zostanie przeładowana...', + 'backup.toast.restoreError': 'Nie udało się przywrócić kopii zapasowej', + 'backup.toast.uploadError': 'Nie udało się przesłać kopii zapasowej', + 'backup.toast.deleted': 'Kopia zapasowa została usunięta', + 'backup.toast.deleteError': 'Nie udało się usunąć kopii zapasowej', + 'backup.toast.downloadError': 'Nie udało się pobrać kopii zapasowej', + 'backup.toast.settingsSaved': + 'Ustawienia automatycznej kopii zapasowej zostały zapisane', + 'backup.toast.settingsError': 'Nie udało się zapisać ustawień', + 'backup.auto.title': 'Automatyczna kopia zapasowa', + 'backup.auto.subtitle': + 'Tworzenie automatycznej kopii zapasowej według harmonogramu', + 'backup.auto.enable': 'Włącz automatyczną kopię zapasową', + 'backup.auto.enableHint': + 'Kopie zapasowe będą tworzone automatycznie zgodnie z wybranym harmonogramem', + 'backup.auto.interval': 'Częstotliwość', + 'backup.auto.hour': 'Uruchom o godzinie', + 'backup.auto.hourHint': 'Czas lokalny serwera ({format} format)', + 'backup.auto.dayOfWeek': 'Dzień tygodnia', + 'backup.auto.dayOfMonth': 'Dzień miesiąca', + 'backup.auto.dayOfMonthHint': + 'Ograniczone do 1–28 dla kompatybilności ze wszystkimi miesiącami', + 'backup.auto.scheduleSummary': 'Harmonogram', + 'backup.auto.summaryDaily': 'Każdego dnia o {hour}:00', + 'backup.auto.summaryWeekly': 'Co {day} o {hour}:00', + 'backup.auto.summaryMonthly': '{day}. dnia każdego miesiąca o {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Automatyczne kopie zapasowe są konfigurowane za pomocą zmiennych środowiskowych Dockera. Aby zmienić te ustawienia, zaktualizuj plik docker-compose.yml i uruchom ponownie kontener.', + 'backup.auto.copyEnv': 'Kopiuj zmienne środowiskowe Dockera', + 'backup.auto.envCopied': + 'Zmienne środowiskowe Dockera zostały skopiowane do schowka', + 'backup.auto.keepLabel': 'Usuń stare kopie zapasowe po', + 'backup.dow.sunday': 'Nd', + 'backup.dow.monday': 'Pon', + 'backup.dow.tuesday': 'Wt', + 'backup.dow.wednesday': 'Śr', + 'backup.dow.thursday': 'Czw', + 'backup.dow.friday': 'Pt', + 'backup.dow.saturday': 'Sob', + 'backup.interval.hourly': 'Co godzinę', + 'backup.interval.daily': 'Co dzień', + 'backup.interval.weekly': 'Co tydzień', + 'backup.interval.monthly': 'Co miesiąc', + 'backup.keep.1day': '1 dzień', + 'backup.keep.3days': '3 dni', + 'backup.keep.7days': '7 dni', + 'backup.keep.14days': '14 dni', + 'backup.keep.30days': '30 dni', + 'backup.keep.forever': 'Przechowuj na zawsze', + 'backup.restoreConfirmTitle': 'Przywrócić kopię zapasową?', + 'backup.restoreWarning': + 'Wszystkie obecne dane (podróże, miejsca, użytkownicy, przesłane pliki) zostaną trwale zastąpione danymi z kopii zapasowej. Tej operacji nie można cofnąć.', + 'backup.restoreTip': + 'Wskazówka: Przed przywróceniem utwórz kopię zapasową bieżącej instancji.', + 'backup.restoreConfirm': 'Tak, przywróć', +}; +export default backup; diff --git a/shared/src/i18n/pl/budget.ts b/shared/src/i18n/pl/budget.ts new file mode 100644 index 00000000..00fd7e44 --- /dev/null +++ b/shared/src/i18n/pl/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budżet', + 'budget.emptyTitle': 'Nie utworzono jeszcze budżetu', + 'budget.emptyText': 'Utwórz kategorie i wpisy, aby zaplanować budżet podróży', + 'budget.emptyPlaceholder': 'Podaj nazwę kategorii...', + 'budget.createCategory': 'Utwórz kategorię', + 'budget.category': 'Kategoria', + 'budget.categoryName': 'Nazwa kategorii', + 'budget.table.name': 'Nazwa', + 'budget.table.total': 'Łącznie', + 'budget.table.persons': 'Osoby', + 'budget.table.days': 'Dni', + 'budget.table.perPerson': 'Za osobę', + 'budget.table.perDay': 'Za dzień', + 'budget.table.perPersonDay': 'Za osobę/dzień', + 'budget.table.note': 'Notatka', + 'budget.newEntry': 'Nowy wpis', + 'budget.defaultEntry': 'Nowy wpis', + 'budget.defaultCategory': 'Nowa kategoria', + 'budget.total': 'Łącznie', + 'budget.totalBudget': 'Całkowity budżet', + 'budget.byCategory': 'Według kategorii', + 'budget.editTooltip': 'Kliknij, aby edytować', + 'budget.linkedToReservation': 'Powiązano z rezerwacją — edytuj nazwę tam', + 'budget.confirm.deleteCategory': + 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} wpisami?', + 'budget.deleteCategory': 'Usuń kategorię', + 'budget.perPerson': 'Za osobę', + 'budget.paid': 'Zapłacone', + 'budget.open': 'Otwarte', + 'budget.noMembers': 'Brak przypisanych członków', + 'budget.settlement': 'Rozliczenie', + 'budget.settlementInfo': + 'Kliknij avatar członka przy pozycji w budżecie, aby oznaczyć go na zielono — oznacza to, że zapłacił. Rozliczenie pokaże, kto komu i ile jest winien.', + 'budget.netBalances': 'Bilans', + 'budget.exportCsv': 'Eksportuj CSV', + 'budget.table.date': 'Data', + 'budget.categoriesLabel': 'kategorie', +}; +export default budget; diff --git a/shared/src/i18n/pl/categories.ts b/shared/src/i18n/pl/categories.ts new file mode 100644 index 00000000..9e247acb --- /dev/null +++ b/shared/src/i18n/pl/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Kategorie', + 'categories.subtitle': 'Zarządzaj kategoriami miejsc', + 'categories.new': 'Nowa kategoria', + 'categories.empty': 'Brak kategorii', + 'categories.namePlaceholder': 'Nazwa kategorii', + 'categories.icon': 'Ikona', + 'categories.color': 'Kolor', + 'categories.customColor': 'Wybierz własny kolor', + 'categories.preview': 'Podgląd', + 'categories.defaultName': 'Kategoria', + 'categories.update': 'Aktualizuj', + 'categories.create': 'Utwórz', + 'categories.confirm.delete': + 'Usunąć kategorię? Miejsca w tej kategorii nie zostaną usunięte.', + 'categories.toast.loadError': 'Nie udało się załadować kategorii', + 'categories.toast.nameRequired': 'Proszę podać nazwę', + 'categories.toast.updated': 'Kategoria została zaktualizowana', + 'categories.toast.created': 'Kategoria została utworzona', + 'categories.toast.saveError': 'Nie udało się zapisać kategorii', + 'categories.toast.deleted': 'Kategoria została usunięta', + 'categories.toast.deleteError': 'Nie udało się usunąć kategorii', +}; +export default categories; diff --git a/shared/src/i18n/pl/collab.ts b/shared/src/i18n/pl/collab.ts new file mode 100644 index 00000000..7dfbf806 --- /dev/null +++ b/shared/src/i18n/pl/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Czat', + 'collab.tabs.notes': 'Notatki', + 'collab.tabs.polls': 'Ankiety', + 'collab.whatsNext.title': 'Co dalej', + 'collab.whatsNext.today': 'Dzisiaj', + 'collab.whatsNext.tomorrow': 'Jutro', + 'collab.whatsNext.empty': 'Brak nadchodzących aktywności', + 'collab.whatsNext.until': 'do', + 'collab.whatsNext.emptyHint': 'Aktywności z godzinami pojawią się tutaj', + 'collab.chat.send': 'Wyślij', + 'collab.chat.placeholder': 'Napisz wiadomość...', + 'collab.chat.empty': 'Rozpocznij konwersację', + 'collab.chat.emptyHint': + 'Wiadomości są widoczne dla wszystkich uczestników podróży', + 'collab.chat.emptyDesc': + 'Dziel się pomysłami, planami i aktualizacjami z uczestnikami podróży', + 'collab.chat.today': 'Dzisiaj', + 'collab.chat.yesterday': 'Wczoraj', + 'collab.chat.deletedMessage': 'usunięto wiadomość', + 'collab.chat.loadMore': 'Załaduj starsze wiadomości', + 'collab.chat.justNow': 'teraz', + 'collab.chat.minutesAgo': '{n}m temu', + 'collab.chat.hoursAgo': '{n}h temu', + 'collab.notes.title': 'Notatki', + 'collab.notes.new': 'Nowa notatka', + 'collab.notes.empty': 'Brak notatek', + 'collab.notes.emptyHint': 'Zapisuj pomysły i plany', + 'collab.notes.all': 'Wszystkie', + 'collab.notes.titlePlaceholder': 'Tytuł notatki', + 'collab.notes.contentPlaceholder': 'Napisz coś...', + 'collab.notes.categoryPlaceholder': 'Kategoria', + 'collab.notes.newCategory': 'Nowa kategoria...', + 'collab.notes.category': 'Kategoria', + 'collab.notes.noCategory': 'Brak kategorii', + 'collab.notes.color': 'Kolor', + 'collab.notes.save': 'Zapisz', + 'collab.notes.cancel': 'Anuluj', + 'collab.notes.edit': 'Edytuj', + 'collab.notes.delete': 'Usuń', + 'collab.notes.pin': 'Przypnij', + 'collab.notes.unpin': 'Odepnij', + 'collab.notes.daysAgo': '{n}d temu', + 'collab.notes.categorySettings': 'Zarządzaj kategoriami', + 'collab.notes.create': 'Utwórz', + 'collab.notes.website': 'Strona internetowa', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Załącz pliki', + 'collab.notes.noCategoriesYet': 'Brak kategorii', + 'collab.notes.emptyDesc': 'Utwórz notatkę, aby rozpocząć', + 'collab.polls.title': 'Ankiety', + 'collab.polls.new': 'Nowa ankieta', + 'collab.polls.empty': 'Brak ankiet', + 'collab.polls.emptyHint': 'Zapytaj grupę i głosujcie razem', + 'collab.polls.question': 'Pytanie', + 'collab.polls.questionPlaceholder': 'Co powinniśmy zrobić?', + 'collab.polls.addOption': '+ Dodaj opcję', + 'collab.polls.optionPlaceholder': 'Opcja {n}', + 'collab.polls.create': 'Utwórz ankietę', + 'collab.polls.close': 'Zamknij', + 'collab.polls.closed': 'Zamknięta', + 'collab.polls.votes': '{n} głosów', + 'collab.polls.vote': '{n} głos', + 'collab.polls.multipleChoice': 'Wielokrotny wybór', + 'collab.polls.multiChoice': 'Wielokrotny wybór', + 'collab.polls.deadline': 'Koniec', + 'collab.polls.option': 'Opcja', + 'collab.polls.options': 'Opcje', + 'collab.polls.delete': 'Usuń', + 'collab.polls.closedSection': 'Zamknięte', + 'collab.chat.reply': 'Odpowiedz', +}; +export default collab; diff --git a/shared/src/i18n/pl/common.ts b/shared/src/i18n/pl/common.ts new file mode 100644 index 00000000..349fa25b --- /dev/null +++ b/shared/src/i18n/pl/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Zapisz', + 'common.showMore': 'Pokaż więcej', + 'common.showLess': 'Pokaż mniej', + 'common.cancel': 'Anuluj', + 'common.clear': 'Wyczyść', + 'common.delete': 'Usuń', + 'common.edit': 'Edytuj', + 'common.add': 'Dodaj', + 'common.loading': 'Ładowanie...', + 'common.error': 'Błąd', + 'common.unknownError': 'Nieznany błąd', + 'common.tooManyAttempts': 'Zbyt wiele prób. Spróbuj ponownie później.', + 'common.back': 'Wstecz', + 'common.all': 'Wszystko', + 'common.close': 'Zamknij', + 'common.open': 'Otwórz', + 'common.upload': 'Prześlij', + 'common.search': 'Szukaj', + 'common.confirm': 'Potwierdź', + 'common.ok': 'OK', + 'common.yes': 'Tak', + 'common.no': 'Nie', + 'common.or': 'lub', + 'common.none': 'Brak', + 'common.date': 'Data', + 'common.rename': 'Zmień nazwę', + 'common.discardChanges': 'Odrzuć zmiany', + 'common.discard': 'Odrzuć', + 'common.name': 'Nazwa', + 'common.email': 'E-mail', + 'common.password': 'Hasło', + 'common.saving': 'Zapisywanie...', + 'common.expand': 'Rozwiń', + 'common.collapse': 'Zwiń', + 'common.update': 'Aktualizuj', + 'common.change': 'Zmień', + 'common.uploading': 'Przesyłanie...', + 'common.backToPlanning': 'Powrót do planowania', + 'common.reset': 'Resetuj', + 'common.copy': 'Kopiuj', + 'common.copied': 'Skopiowano', + 'common.import': 'Importuj', + 'common.select': 'Wybierz', + 'common.selectAll': 'Zaznacz wszystko', + 'common.deselectAll': 'Odznacz wszystko', + 'common.saved': 'Zapisano', + 'common.justNow': 'przed chwilą', + 'common.hoursAgo': '{count} godz. temu', + 'common.daysAgo': '{count} dn. temu', +}; +export default common; diff --git a/shared/src/i18n/pl/dashboard.ts b/shared/src/i18n/pl/dashboard.ts new file mode 100644 index 00000000..229f9612 --- /dev/null +++ b/shared/src/i18n/pl/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Moje podróże', + 'dashboard.subtitle.loading': 'Ładowanie podróży...', + 'dashboard.subtitle.trips': '{count} podróży ({archived} zarchiwizowanych)', + 'dashboard.subtitle.empty': 'Rozpocznij swoją pierwszą podróż', + 'dashboard.subtitle.activeOne': '{count} aktywna podróż', + 'dashboard.subtitle.activeMany': '{count} aktywnych podróży', + 'dashboard.subtitle.archivedSuffix': ' · {count} zarchiwizowanych', + 'dashboard.newTrip': 'Nowa podróż', + 'dashboard.gridView': 'Widok siatki', + 'dashboard.listView': 'Widok listy', + 'dashboard.currency': 'Waluta', + 'dashboard.timezone': 'Strefy czasowe', + 'dashboard.localTime': 'Czas lokalny', + 'dashboard.timezoneCustomTitle': 'Własna strefa czasowa', + 'dashboard.timezoneCustomLabelPlaceholder': 'Nazwa (opcjonalnie)', + 'dashboard.timezoneCustomTzPlaceholder': 'np. Europe/Warsaw', + 'dashboard.timezoneCustomAdd': 'Dodaj', + 'dashboard.timezoneCustomErrorEmpty': 'Podaj identyfikator strefy czasowej', + 'dashboard.timezoneCustomErrorInvalid': + 'Nieprawidłowa strefa czasowa. Użyj formatu takiego jak Europe/Warsaw', + 'dashboard.timezoneCustomErrorDuplicate': 'Już dodana', + 'dashboard.emptyTitle': 'Brak podróży', + 'dashboard.emptyText': 'Utwórz swoją pierwszą podróż i zacznij planować!', + 'dashboard.emptyButton': 'Utwórz pierwszą podróż', + 'dashboard.nextTrip': 'Następna podróż', + 'dashboard.shared': 'Udostępniona', + 'dashboard.sharedBy': 'Udostępniona przez {name}', + 'dashboard.days': 'Dni', + 'dashboard.places': 'Miejsca', + 'dashboard.archive': 'Archiwizuj', + 'dashboard.restore': 'Przywróć', + 'dashboard.archived': 'Zarchiwizowana', + 'dashboard.status.ongoing': 'W trakcie', + 'dashboard.status.today': 'Dzisiaj', + 'dashboard.status.tomorrow': 'Jutro', + 'dashboard.status.past': 'Zakończona', + 'dashboard.status.daysLeft': '{count} dni do końca', + 'dashboard.toast.loadError': 'Nie udało się załadować podróży', + 'dashboard.toast.created': 'Podróż została utworzona pomyślnie!', + 'dashboard.toast.createError': 'Nie udało się utworzyć podróży', + 'dashboard.toast.updated': 'Podróż została zaktualizowana!', + 'dashboard.toast.updateError': 'Nie udało się zaktualizować podróży', + 'dashboard.toast.deleted': 'Podróż została usunięta', + 'dashboard.toast.deleteError': 'Nie udało się usunąć podróży', + 'dashboard.toast.archived': 'Podróż została zarchiwizowana', + 'dashboard.toast.archiveError': 'Nie udało się zarchiwizować podróży', + 'dashboard.toast.restored': 'Podróż została przywrócona', + 'dashboard.toast.restoreError': 'Nie udało się przywrócić podróży', + 'dashboard.confirm.delete': + 'Usunąć podróż "{title}"? Wszystkie miejsca i plany zostaną trwale usunięte.', + 'dashboard.editTrip': 'Edytuj podróż', + 'dashboard.createTrip': 'Utwórz nową podróż', + 'dashboard.tripTitle': 'Nazwa podróży', + 'dashboard.tripTitlePlaceholder': 'np. Lato w Japonii', + 'dashboard.tripDescription': 'Opis', + 'dashboard.tripDescriptionPlaceholder': 'Opisz swoją podróż', + 'dashboard.startDate': 'Data rozpoczęcia', + 'dashboard.endDate': 'Data zakończenia', + 'dashboard.dayCount': 'Liczba dni', + 'dashboard.dayCountHint': + 'Ile dni zaplanować, gdy nie ustawiono dat podróży.', + 'dashboard.noDateHint': + 'Nie ustawiono daty — zostanie utworzonych 7 domyślnych dni. Możesz to zmienić w dowolnym momencie.', + 'dashboard.coverImage': 'Okładka', + 'dashboard.addCoverImage': 'Dodaj okładkę (lub przeciągnij i upuść)', + 'dashboard.addMembers': 'Uczestnicy podróży', + 'dashboard.addMember': 'Dodaj uczestnika', + 'dashboard.coverSaved': 'Okładka została zapisana', + 'dashboard.coverUploadError': 'Nie udało się przesłać okładki', + 'dashboard.coverRemoveError': 'Nie udało się usunąć okładki', + 'dashboard.titleRequired': 'Nazwa podróży jest wymagana', + 'dashboard.endDateError': 'Data zakończenia musi być po dacie rozpoczęcia', + 'dashboard.members': 'Towarzysze', + 'dashboard.copyTrip': 'Kopiuj', + 'dashboard.copySuffix': 'kopia', + 'dashboard.toast.copied': 'Podróż skopiowana!', + 'dashboard.toast.copyError': 'Nie udało się skopiować podróży', + 'dashboard.greeting.morning': 'Dzień dobry,', + 'dashboard.greeting.afternoon': 'Dzień dobry,', + 'dashboard.greeting.evening': 'Dobry wieczór,', + 'dashboard.mobile.liveNow': 'Na żywo', + 'dashboard.mobile.tripProgress': 'Postęp podróży', + 'dashboard.mobile.daysLeft': 'Pozostało {count} dni', + 'dashboard.mobile.places': 'Miejsca', + 'dashboard.mobile.buddies': 'Towarzysze', + 'dashboard.mobile.newTrip': 'Nowa podróż', + 'dashboard.mobile.currency': 'Waluta', + 'dashboard.mobile.timezone': 'Strefa czasowa', + 'dashboard.mobile.upcomingTrips': 'Nadchodzące podróże', + 'dashboard.mobile.yourTrips': 'Twoje podróże', + 'dashboard.mobile.trips': 'podróże', + 'dashboard.mobile.starts': 'Początek', + 'dashboard.mobile.duration': 'Czas trwania', + 'dashboard.mobile.day': 'dzień', + 'dashboard.mobile.days': 'dni', + 'dashboard.mobile.ongoing': 'W trakcie', + 'dashboard.mobile.startsToday': 'Zaczyna się dziś', + 'dashboard.mobile.tomorrow': 'Jutro', + 'dashboard.mobile.inDays': 'Za {count} dni', + 'dashboard.mobile.inMonths': 'Za {count} miesięcy', + 'dashboard.mobile.completed': 'Zakończone', + 'dashboard.mobile.currencyConverter': 'Przelicznik walut', +}; +export default dashboard; diff --git a/shared/src/i18n/pl/day.ts b/shared/src/i18n/pl/day.ts new file mode 100644 index 00000000..ee8aef19 --- /dev/null +++ b/shared/src/i18n/pl/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Prawdopodobieństwo opadów', + 'day.precipitation': 'Opady', + 'day.wind': 'Wiatr', + 'day.sunrise': 'Wschód słońca', + 'day.sunset': 'Zachód słońca', + 'day.hourlyForecast': 'Prognoza godzinowa', + 'day.climateHint': + 'Historyczne średnie — rzeczywista prognoza dostępna na następne 16 dni od tej daty.', + 'day.noWeather': 'Brak danych pogodowych. Dodaj miejsce ze współrzędnymi.', + 'day.overview': 'Przegląd dnia', + 'day.accommodation': 'Zakwaterowanie', + 'day.addAccommodation': 'Dodaj zakwaterowanie', + 'day.hotelDayRange': 'Zastosuj do dni', + 'day.noPlacesForHotel': 'Najpierw dodaj miejsca do swojej podróży', + 'day.allDays': 'Wszystkie', + 'day.checkIn': 'Zameldowanie', + 'day.checkInUntil': 'Do', + 'day.checkOut': 'Wymeldowanie', + 'day.confirmation': 'Potwierdzenie', + 'day.editAccommodation': 'Edytuj zakwaterowanie', + 'day.reservations': 'Rezerwacje', +}; +export default day; diff --git a/shared/src/i18n/pl/dayplan.ts b/shared/src/i18n/pl/dayplan.ts new file mode 100644 index 00000000..3c0845b9 --- /dev/null +++ b/shared/src/i18n/pl/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Eksportuj kalendarz (ICS)', + 'dayplan.emptyDay': 'Brak miejsc zaplanowanych na ten dzień', + 'dayplan.cannotReorderTransport': + 'Nie można zmieniać kolejności dla rezerwacji z określoną godziną', + 'dayplan.confirmRemoveTimeTitle': 'Usunąć godzinę?', + 'dayplan.confirmRemoveTimeBody': + 'To miejsce ma określoną godzinę ({time}). Przeniesienie go usunie godzinę i umożliwi swobodne sortowanie.', + 'dayplan.confirmRemoveTimeAction': 'Usuń godzinę i przenieś', + 'dayplan.cannotDropOnTimed': + 'Nie można umieszczać elementów pomiędzy wpisami z określoną godziną', + 'dayplan.cannotBreakChronology': + 'Spowodowałoby to naruszenie chronologicznej kolejności elementów i rezerwacji z określoną godziną', + 'dayplan.addNote': 'Dodaj notatkę', + 'dayplan.editNote': 'Edytuj notatkę', + 'dayplan.noteAdd': 'Dodaj notatkę', + 'dayplan.noteEdit': 'Edytuj notatkę', + 'dayplan.noteTitle': 'Notatka', + 'dayplan.noteSubtitle': 'Notatka dnia', + 'dayplan.totalCost': 'Łączny koszt', + 'dayplan.days': 'Dni', + 'dayplan.dayN': 'Dzień {n}', + 'dayplan.calculating': 'Obliczanie...', + 'dayplan.route': 'Trasa', + 'dayplan.optimize': 'Optymalizuj', + 'dayplan.optimized': 'Trasa została zoptymalizowana', + 'dayplan.routeError': 'Nie udało się obliczyć trasy', + 'dayplan.toast.needTwoPlaces': + 'Potrzeba co najmniej dwóch miejsc, aby zoptymalizować trasę', + 'dayplan.toast.routeOptimized': 'Trasa została zoptymalizowana', + 'dayplan.toast.noGeoPlaces': + 'Nie znaleziono miejsc ze współrzędnymi do obliczenia trasy', + 'dayplan.confirmed': 'Potwierdzono', + 'dayplan.pendingRes': 'Oczekujące', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Eksportuj plan dnia jako PDF', + 'dayplan.pdfError': 'Nie udało się wyeksportować pliku PDF', + 'dayplan.mobile.addPlace': 'Dodaj miejsce', + 'dayplan.mobile.searchPlaces': 'Szukaj miejsc...', + 'dayplan.mobile.allAssigned': 'Wszystkie miejsca przypisane', + 'dayplan.mobile.noMatch': 'Brak wyników', + 'dayplan.mobile.createNew': 'Utwórz nowe miejsce', +}; +export default dayplan; diff --git a/shared/src/i18n/pl/externalNotifications.ts b/shared/src/i18n/pl/externalNotifications.ts new file mode 100644 index 00000000..3944c586 --- /dev/null +++ b/shared/src/i18n/pl/externalNotifications.ts @@ -0,0 +1,64 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const pl: NotificationLocale = { + email: { + footer: + 'Otrzymałeś/aś tę wiadomość, ponieważ masz włączone powiadomienia w TREK.', + manage: 'Zarządzaj preferencjami w ustawieniach', + madeWith: 'Made with', + openTrek: 'Otwórz TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Zaproszenie do "${p.trip}"`, + body: `${p.actor} zaprosił ${p.invitee || 'członka'} do podróży "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Nowa rezerwacja: ${p.booking}`, + body: `${p.actor} dodał rezerwację "${p.booking}" (${p.type}) do "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Przypomnienie o podróży: ${p.trip}`, + body: `Twoja podróż "${p.trip}" zbliża się!`, + }), + todo_due: (p) => ({ + title: `Zadanie z terminem: ${p.todo}`, + body: `"${p.todo}" w "${p.trip}" — termin ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Zaproszenie Vacay Fusion', + body: `${p.actor} zaprosił Cię do połączenia planów urlopowych. Otwórz TREK, aby zaakceptować lub odrzucić.`, + }), + photos_shared: (p) => ({ + title: `${p.count} zdjęć udostępnionych`, + body: `${p.actor} udostępnił ${p.count} zdjęcie/zdjęcia w "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Nowa wiadomość w "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Pakowanie: ${p.category}`, + body: `${p.actor} przypisał Cię do kategorii "${p.category}" w "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Nowa wersja TREK dostępna', + body: `TREK ${p.version} jest teraz dostępny. Odwiedź panel administracyjny, aby zaktualizować.`, + }), + synology_session_cleared: () => ({ + title: 'Sesja Synology wyczyszczona', + body: 'Twoje konto lub URL Synology uległo zmianie. Zostałeś wylogowany z Synology Photos.', + }), + }, + passwordReset: { + subject: 'Zresetuj hasło', + greeting: 'Cześć', + body: 'Otrzymaliśmy prośbę o zresetowanie hasła do Twojego konta TREK. Kliknij przycisk poniżej, aby ustawić nowe hasło.', + ctaIntro: 'Zresetuj hasło', + expiry: 'Link wygaśnie za 60 minut.', + ignore: + 'Jeśli to nie Ty, zignoruj tę wiadomość — Twoje hasło pozostanie bez zmian.', + }, +}; + +export default pl; diff --git a/shared/src/i18n/pl/files.ts b/shared/src/i18n/pl/files.ts new file mode 100644 index 00000000..85d7e739 --- /dev/null +++ b/shared/src/i18n/pl/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Pliki', + 'files.pageTitle': 'Pliki i dokumenty', + 'files.subtitle': '{count} plików dla {trip}', + 'files.download': 'Pobierz', + 'files.openError': 'Nie można otworzyć pliku', + 'files.downloadPdf': 'Pobierz PDF', + 'files.count': '{count} plików', + 'files.countSingular': '1 plik', + 'files.uploaded': '{count} przesłanych', + 'files.uploadError': 'Przesyłanie nie powiodło się', + 'files.dropzone': 'Przeciągnij pliki tutaj', + 'files.dropzoneHint': 'lub kliknij, aby przeglądać', + 'files.allowedTypes': + 'Obrazki, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Maks 50 MB', + 'files.uploading': 'Przesyłanie...', + 'files.filterAll': 'Wszystkie', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Obrazki', + 'files.filterDocs': 'Dokumenty', + 'files.filterCollab': 'Wspólne notatki', + 'files.sourceCollab': 'Z wspólnych notatek', + 'files.empty': 'Brak plików', + 'files.emptyHint': 'Prześlij pliki, aby dodać je do swojej podróży', + 'files.openTab': 'Otwórz w nowej karcie', + 'files.confirm.delete': 'Czy na pewno chcesz usunąć ten plik?', + 'files.toast.deleted': 'Plik został usunięty', + 'files.toast.deleteError': 'Nie udało się usunąć pliku', + 'files.sourcePlan': 'Plan dni', + 'files.sourceBooking': 'Rezerwacje', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Załącz', + 'files.pasteHint': 'Możesz również wkleić obrazki ze schowka (Ctrl+V)', + 'files.trash': 'Kosz', + 'files.trashEmpty': 'Kosz jest pusty', + 'files.emptyTrash': 'Opróżnij kosz', + 'files.restore': 'Przywróć', + 'files.star': 'Oznacz', + 'files.unstar': 'Usuń oznaczenie', + 'files.assign': 'Przypisz', + 'files.assignTitle': 'Przypisz plik', + 'files.assignPlace': 'Miejsce', + 'files.assignBooking': 'Rezerwacja', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Nieprzypisane', + 'files.unlink': 'Usuń link', + 'files.toast.trashed': 'Przeniesiono do kosza', + 'files.toast.restored': 'Plik został przywrócony', + 'files.toast.trashEmptied': 'Kosz został opróżniony', + 'files.toast.assigned': 'Plik został przypisany', + 'files.toast.assignError': 'Nie udało się przypisać', + 'files.toast.restoreError': 'Nie udało się przywrócić', + 'files.confirm.permanentDelete': + 'Czy na pewno chcesz trwale usunąć ten plik? Tej operacji nie można cofnąć.', + 'files.confirm.emptyTrash': + 'Czy na pewno chcesz trwale usunąć wszystkie pliki z kosza? Tej operacji nie można cofnąć.', + 'files.noteLabel': 'Notatka', + 'files.notePlaceholder': 'Dodaj notatkę...', +}; +export default files; diff --git a/shared/src/i18n/pl/index.ts b/shared/src/i18n/pl/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/pl/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/pl/inspector.ts b/shared/src/i18n/pl/inspector.ts new file mode 100644 index 00000000..7e652dcc --- /dev/null +++ b/shared/src/i18n/pl/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Otwarte', + 'inspector.closed': 'Zamknięte', + 'inspector.openingHours': 'Godziny otwarcia', + 'inspector.showHours': 'Pokaż godziny otwarcia', + 'inspector.files': 'Pliki', + 'inspector.filesCount': '{count} plików', + 'inspector.removeFromDay': 'Usuń z dnia', + 'inspector.remove': 'Usuń', + 'inspector.addToDay': 'Dodaj do dnia', + 'inspector.confirmedRes': 'Potwierdzona rezerwacja', + 'inspector.pendingRes': 'Oczekująca rezerwacja', + 'inspector.google': 'Otwórz w Mapach Google', + 'inspector.website': 'Otwórz stronę internetową', + 'inspector.addRes': 'Rezerwacja', + 'inspector.editRes': 'Edytuj rezerwację', + 'inspector.participants': 'Uczestnicy', + 'inspector.trackStats': 'Statystyki trasy', +}; +export default inspector; diff --git a/shared/src/i18n/pl/journey.ts b/shared/src/i18n/pl/journey.ts new file mode 100644 index 00000000..83a70308 --- /dev/null +++ b/shared/src/i18n/pl/journey.ts @@ -0,0 +1,239 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Szukaj podróży…', + 'journey.search.noResults': 'Brak podróży pasujących do „{query}"', + 'journey.title': 'Dziennik podróży', + 'journey.subtitle': 'Dokumentuj swoje podróże na bieżąco', + 'journey.new': 'Nowy dziennik podróży', + 'journey.create': 'Utwórz', + 'journey.titlePlaceholder': 'Dokąd jedziesz?', + 'journey.empty': 'Brak dzienników podróży', + 'journey.emptyHint': 'Zacznij dokumentować swoją następną podróż', + 'journey.deleted': 'Dziennik podróży usunięty', + 'journey.createError': 'Nie udało się utworzyć dziennika podróży', + 'journey.deleteError': 'Nie udało się usunąć dziennika podróży', + 'journey.deleteConfirmTitle': 'Usuń', + 'journey.deleteConfirmMessage': + 'Usunąć „{title}"? Tej operacji nie można cofnąć.', + 'journey.deleteConfirmGeneric': 'Czy na pewno chcesz to usunąć?', + 'journey.notFound': 'Nie znaleziono dziennika podróży', + 'journey.photos': 'Zdjęcia', + 'journey.timelineEmpty': 'Brak przystanków', + 'journey.timelineEmptyHint': + 'Dodaj zameldowanie lub napisz wpis w dzienniku, aby rozpocząć', + 'journey.status.draft': 'Szkic', + 'journey.status.active': 'Aktywny', + 'journey.status.completed': 'Zakończony', + 'journey.status.upcoming': 'Nadchodzący', + 'journey.status.archived': 'Zarchiwizowano', + 'journey.checkin.add': 'Zamelduj się', + 'journey.checkin.namePlaceholder': 'Nazwa miejsca', + 'journey.checkin.notesPlaceholder': 'Notatki (opcjonalnie)', + 'journey.checkin.save': 'Zapisz', + 'journey.checkin.error': 'Nie udało się zapisać zameldowania', + 'journey.entry.add': 'Dziennik', + 'journey.entry.edit': 'Edytuj wpis', + 'journey.entry.titlePlaceholder': 'Tytuł (opcjonalnie)', + 'journey.entry.bodyPlaceholder': 'Co się dziś wydarzyło?', + 'journey.entry.save': 'Zapisz', + 'journey.entry.error': 'Nie udało się zapisać wpisu', + 'journey.photo.add': 'Zdjęcie', + 'journey.photo.uploadError': 'Przesyłanie nie powiodło się', + 'journey.share.share': 'Udostępnij', + 'journey.share.public': 'Publiczny', + 'journey.share.linkCopied': 'Publiczny link skopiowany', + 'journey.share.disabled': 'Udostępnianie publiczne wyłączone', + 'journey.editor.titlePlaceholder': 'Nadaj temu momentowi nazwę...', + 'journey.editor.bodyPlaceholder': 'Opowiedz historię tego dnia...', + 'journey.editor.placePlaceholder': 'Lokalizacja (opcjonalnie)', + 'journey.editor.tagsPlaceholder': + 'Tagi: ukryty skarb, najlepszy posiłek, warto wrócić...', + 'journey.visibility.private': 'Prywatny', + 'journey.visibility.shared': 'Udostępniony', + 'journey.visibility.public': 'Publiczny', + 'journey.emptyState.title': 'Twoja historia zaczyna się tutaj', + 'journey.emptyState.subtitle': + 'Zamelduj się w miejscu lub napisz swój pierwszy wpis w dzienniku', + 'journey.frontpage.subtitle': + 'Zamień swoje podróże w historie, których nigdy nie zapomnisz', + 'journey.frontpage.createJourney': 'Utwórz dziennik podróży', + 'journey.frontpage.activeJourney': 'Aktywny dziennik podróży', + 'journey.frontpage.allJourneys': 'Wszystkie dzienniki podróży', + 'journey.frontpage.journeys': 'dzienniki podróży', + 'journey.frontpage.createNew': 'Utwórz nowy dziennik podróży', + 'journey.frontpage.createNewSub': + 'Wybierz podróże, pisz historie, dziel się przygodami', + 'journey.frontpage.live': 'Na żywo', + 'journey.frontpage.synced': 'Zsynchronizowany', + 'journey.frontpage.continueWriting': 'Kontynuuj pisanie', + 'journey.frontpage.updated': 'Zaktualizowano {time}', + 'journey.frontpage.suggestionLabel': 'Podróż właśnie się zakończyła', + 'journey.frontpage.suggestionText': + 'Zamień {title} w dziennik podróży', + 'journey.frontpage.dismiss': 'Odrzuć', + 'journey.frontpage.journeyName': 'Nazwa dziennika podróży', + 'journey.frontpage.namePlaceholder': 'np. Azja Południowo-Wschodnia 2026', + 'journey.frontpage.selectTrips': 'Wybierz podróże', + 'journey.frontpage.tripsSelected': 'podróży wybranych', + 'journey.frontpage.trips': 'podróże', + 'journey.frontpage.placesImported': 'miejsc zostanie zaimportowanych', + 'journey.frontpage.places': 'miejsca', + 'journey.detail.backToJourney': 'Powrót do dziennika podróży', + 'journey.detail.syncedWithTrips': 'Zsynchronizowany z podróżami', + 'journey.detail.addEntry': 'Dodaj wpis', + 'journey.detail.newEntry': 'Nowy wpis', + 'journey.detail.editEntry': 'Edytuj wpis', + 'journey.detail.noEntries': 'Brak wpisów', + 'journey.detail.noEntriesHint': + 'Dodaj podróż, aby rozpocząć ze szkieletowymi wpisami', + 'journey.detail.noPhotos': 'Brak zdjęć', + 'journey.detail.noPhotosHint': + 'Prześlij zdjęcia do wpisów lub przeglądaj bibliotekę Immich/Synology', + 'journey.detail.journeyStats': 'Statystyki podróży', + 'journey.detail.syncedTrips': 'Zsynchronizowane podróże', + 'journey.detail.noTripsLinked': 'Brak powiązanych podróży', + 'journey.detail.contributors': 'Współtwórcy', + 'journey.detail.readMore': 'Czytaj dalej', + 'journey.detail.prosCons': 'Zalety i wady', + 'journey.detail.photos': 'zdjęć', + 'journey.detail.day': 'Dzień {number}', + 'journey.detail.places': 'miejsc', + 'journey.stats.days': 'Dni', + 'journey.stats.cities': 'Miasta', + 'journey.stats.entries': 'Wpisy', + 'journey.stats.photos': 'Zdjęcia', + 'journey.stats.places': 'Miejsca', + 'journey.skeletons.show': 'Pokaż sugestie', + 'journey.skeletons.hide': 'Ukryj sugestie', + 'journey.verdict.lovedIt': 'Świetne', + 'journey.verdict.couldBeBetter': 'Mogłoby być lepiej', + 'journey.synced.places': 'miejsca', + 'journey.synced.synced': 'zsynchronizowane', + 'journey.editor.discardChangesConfirm': 'Masz niezapisane zmiany. Odrzucić?', + 'journey.editor.uploadFailed': 'Przesyłanie zdjęć nie powiodło się', + 'journey.editor.uploadPhotos': 'Prześlij zdjęcia', + 'journey.editor.uploading': 'Przesyłanie...', + 'journey.editor.uploadingProgress': 'Przesyłanie {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} z {total} zdjęć nie powiodło się — zapisz ponownie, aby spróbować', + 'journey.editor.fromGallery': 'Z galerii', + 'journey.editor.allPhotosAdded': 'Wszystkie zdjęcia już dodane', + 'journey.editor.writeStory': 'Napisz swoją historię...', + 'journey.editor.prosCons': 'Zalety i wady', + 'journey.editor.pros': 'Zalety', + 'journey.editor.cons': 'Wady', + 'journey.editor.proPlaceholder': 'Coś świetnego...', + 'journey.editor.conPlaceholder': 'Nie tak świetne...', + 'journey.editor.addAnother': 'Dodaj kolejny', + 'journey.editor.date': 'Data', + 'journey.editor.location': 'Lokalizacja', + 'journey.editor.searchLocation': 'Szukaj lokalizacji...', + 'journey.editor.mood': 'Nastrój', + 'journey.editor.weather': 'Pogoda', + 'journey.editor.photoFirst': '1.', + 'journey.editor.makeFirst': 'Ustaw jako 1.', + 'journey.editor.searching': 'Szukanie...', + 'journey.mood.amazing': 'Niesamowity', + 'journey.mood.good': 'Dobry', + 'journey.mood.neutral': 'Neutralny', + 'journey.mood.rough': 'Ciężki', + 'journey.weather.sunny': 'Słonecznie', + 'journey.weather.partly': 'Częściowe zachmurzenie', + 'journey.weather.cloudy': 'Pochmurno', + 'journey.weather.rainy': 'Deszczowo', + 'journey.weather.stormy': 'Burzowo', + 'journey.weather.cold': 'Śnieżnie', + 'journey.trips.linkTrip': 'Powiąż podróż', + 'journey.trips.searchTrip': 'Szukaj podróży', + 'journey.trips.searchPlaceholder': 'Nazwa podróży lub cel...', + 'journey.trips.noTripsAvailable': 'Brak dostępnych podróży', + 'journey.trips.link': 'Powiąż', + 'journey.trips.tripLinked': 'Podróż powiązana', + 'journey.trips.linkFailed': 'Powiązanie podróży nie powiodło się', + 'journey.trips.addTrip': 'Dodaj podróż', + 'journey.trips.unlinkTrip': 'Odłącz podróż', + 'journey.trips.unlinkMessage': + 'Odłączyć „{title}"? Wszystkie zsynchronizowane wpisy i zdjęcia z tej podróży zostaną trwale usunięte. Tej operacji nie można cofnąć.', + 'journey.trips.unlink': 'Odłącz', + 'journey.trips.tripUnlinked': 'Podróż odłączona', + 'journey.trips.unlinkFailed': 'Odłączenie podróży nie powiodło się', + 'journey.trips.noTripsLinkedSettings': 'Brak powiązanych podróży', + 'journey.contributors.invite': 'Zaproś współtwórcę', + 'journey.contributors.searchUser': 'Szukaj użytkownika', + 'journey.contributors.searchPlaceholder': 'Nazwa użytkownika lub e-mail...', + 'journey.contributors.noUsers': 'Nie znaleziono użytkowników', + 'journey.contributors.role': 'Rola', + 'journey.contributors.added': 'Współtwórca dodany', + 'journey.contributors.addFailed': 'Dodawanie współtwórcy nie powiodło się', + 'journey.share.publicShare': 'Udostępnianie publiczne', + 'journey.share.createLink': 'Utwórz link udostępniania', + 'journey.share.linkCreated': 'Link udostępniania utworzony', + 'journey.share.createFailed': 'Tworzenie linku nie powiodło się', + 'journey.share.copy': 'Kopiuj', + 'journey.share.copied': 'Skopiowano!', + 'journey.share.timeline': 'Oś czasu', + 'journey.share.gallery': 'Galeria', + 'journey.share.map': 'Mapa', + 'journey.share.removeLink': 'Usuń link udostępniania', + 'journey.share.linkDeleted': 'Link udostępniania usunięty', + 'journey.share.deleteFailed': 'Usunięcie nie powiodło się', + 'journey.share.updateFailed': 'Aktualizacja nie powiodła się', + 'journey.invite.role': 'Rola', + 'journey.invite.viewer': 'Obserwator', + 'journey.invite.editor': 'Redaktor', + 'journey.invite.invite': 'Zaproś', + 'journey.invite.inviting': 'Zapraszanie...', + 'journey.settings.title': 'Ustawienia dziennika podróży', + 'journey.settings.coverImage': 'Zdjęcie okładkowe', + 'journey.settings.changeCover': 'Zmień okładkę', + 'journey.settings.addCover': 'Dodaj zdjęcie okładkowe', + 'journey.settings.name': 'Nazwa', + 'journey.settings.subtitle': 'Podtytuł', + 'journey.settings.subtitlePlaceholder': 'np. Tajlandia, Wietnam i Kambodża', + 'journey.settings.endJourney': 'Archiwizuj podróż', + 'journey.settings.reopenJourney': 'Przywróć podróż', + 'journey.settings.archived': 'Podróż zarchiwizowana', + 'journey.settings.reopened': 'Podróż wznowiona', + 'journey.settings.endDescription': + 'Ukrywa odznakę Na żywo. Możesz wznowić w dowolnym momencie.', + 'journey.settings.delete': 'Usuń', + 'journey.settings.deleteJourney': 'Usuń dziennik podróży', + 'journey.settings.deleteMessage': + 'Usunąć „{title}"? Wszystkie wpisy i zdjęcia zostaną utracone.', + 'journey.settings.saved': 'Ustawienia zapisane', + 'journey.settings.saveFailed': 'Zapisywanie nie powiodło się', + 'journey.settings.coverUpdated': 'Okładka zaktualizowana', + 'journey.settings.coverFailed': 'Przesyłanie nie powiodło się', + 'journey.settings.failedToDelete': 'Nie udało się usunąć', + 'journey.entries.deleteTitle': 'Usuń wpis', + 'journey.photosUploaded': '{count} zdjęć przesłanych', + 'journey.photosUploadFailed': 'Nie udało się przesłać niektórych zdjęć', + 'journey.photosAdded': '{count} zdjęć dodanych', + 'journey.public.notFound': 'Nie znaleziono', + 'journey.public.notFoundMessage': + 'Ten dziennik podróży nie istnieje lub link wygasł.', + 'journey.public.readOnly': 'Tylko do odczytu · Publiczny dziennik podróży', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Udostępnione przez', + 'journey.public.madeWith': 'Stworzone z', + 'journey.pdf.journeyBook': 'Książka podróży', + 'journey.pdf.madeWith': 'Stworzone z TREK', + 'journey.pdf.day': 'Dzień', + 'journey.pdf.theEnd': 'Koniec', + 'journey.pdf.saveAsPdf': 'Zapisz jako PDF', + 'journey.pdf.pages': 'stron', + 'journey.picker.tripPeriod': 'Okres podróży', + 'journey.picker.dateRange': 'Zakres dat', + 'journey.picker.allPhotos': 'Wszystkie zdjęcia', + 'journey.picker.albums': 'Albumy', + 'journey.picker.selected': 'wybranych', + 'journey.picker.addTo': 'Dodaj do', + 'journey.picker.newGallery': 'Nowa galeria', + 'journey.picker.selectAll': 'Zaznacz wszystko', + 'journey.picker.deselectAll': 'Odznacz wszystko', + 'journey.picker.noAlbums': 'Nie znaleziono albumów', + 'journey.picker.selectDate': 'Wybierz datę', + 'journey.picker.search': 'Szukaj', +}; +export default journey; diff --git a/shared/src/i18n/pl/login.ts b/shared/src/i18n/pl/login.ts new file mode 100644 index 00000000..45186fb4 --- /dev/null +++ b/shared/src/i18n/pl/login.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Logowanie nie powiodło się. Sprawdź dane logowania.', + 'login.tagline': 'Twoje podróże.\nTwój plan.', + 'login.description': + 'Planuj wspólnie podróże z interaktywnymi mapami, budżetami i synchronizacją w czasie rzeczywistym.', + 'login.features.maps': 'Interaktywne mapy', + 'login.features.mapsDesc': 'Google Places, trasy i grupowanie', + 'login.features.realtime': 'Synchronizacja w czasie rzeczywistym', + 'login.features.realtimeDesc': 'Planuj wspólnie przez WebSocket', + 'login.features.budget': 'Śledzenie budżetu', + 'login.features.budgetDesc': + 'Kategorie, wykresy i koszty w przeliczeniu na osobę', + 'login.features.collab': 'Współpraca', + 'login.features.collabDesc': 'Wielu użytkowników i wspólne podróże', + 'login.features.packing': 'Listy pakowania', + 'login.features.packingDesc': 'Kategorie, postęp i sugestie', + 'login.features.bookings': 'Rezerwacje', + 'login.features.bookingsDesc': 'Loty, hotele, restauracje i więcej', + 'login.features.files': 'Dokumenty', + 'login.features.filesDesc': 'Przesyłaj i zarządzaj dokumentami', + 'login.features.routes': 'Inteligentne trasy', + 'login.features.routesDesc': + 'Automatyczna optymalizacja i eksport do Google Maps', + 'login.selfHosted': + 'Własny hosting · Otwarty kod źródłowy · Twoje dane pozostają Twoje', + 'login.title': 'Zaloguj się', + 'login.subtitle': 'Witaj ponownie', + 'login.signingIn': 'Logowanie...', + 'login.signIn': 'Zaloguj się', + 'login.createAdmin': 'Utwórz konto administratora', + 'login.createAdminHint': + 'Skonfiguruj pierwsze konto administratora dla TREK.', + 'login.createAccount': 'Utwórz konto', + 'login.createAccountHint': 'Zarejestruj nowe konto.', + 'login.creating': 'Tworzenie...', + 'login.noAccount': 'Nie masz konta?', + 'login.hasAccount': 'Masz już konto?', + 'login.register': 'Zarejestruj się', + 'login.emailPlaceholder': 'twoj@email.pl', + 'login.username': 'Nazwa użytkownika', + 'login.oidc.registrationDisabled': + 'Rejestracja jest wyłączona. Skontaktuj się z administratorem.', + 'login.oidc.noEmail': 'Nie otrzymano e-maila od dostawcy.', + 'login.oidc.tokenFailed': 'Nie udało się uwierzytelnić.', + 'login.oidc.invalidState': 'Nieprawidłowa sesja. Spróbuj ponownie.', + 'login.demoFailed': 'Nie udało się zalogować do wersji demonstracyjnej', + 'login.oidcSignIn': 'Zaloguj się z {name}', + 'login.oidcOnly': + 'Uwierzytelnianie hasłem jest wyłączone. Zaloguj się za pomocą swojego dostawcy SSO.', + 'login.oidcLoggedOut': + 'Zostałeś wylogowany. Zaloguj się ponownie za pomocą swojego dostawcy SSO.', + 'login.demoHint': 'Wypróbuj demo — nie wymaga rejestracji', + 'login.mfaTitle': 'Uwierzytelnianie dwuskładnikowe', + 'login.mfaSubtitle': 'Wprowadź 6-cyfrowy kod z aplikacji uwierzytelniającej.', + 'login.mfaCodeLabel': 'Kod weryfikacyjny', + 'login.mfaCodeRequired': 'Wprowadź kod z aplikacji uwierzytelniającej.', + 'login.mfaHint': + 'Otwórz Google Authenticator, Authy lub inną aplikację TOTP.', + 'login.mfaBack': '← Powrót do logowania', + 'login.mfaVerify': 'Weryfikuj', + 'login.invalidInviteLink': 'Nieprawidłowy lub wygasły link zaproszenia', + 'login.oidcFailed': 'Logowanie OIDC nie powiodło się', + 'login.usernameRequired': 'Nazwa użytkownika jest wymagana', + 'login.passwordMinLength': 'Hasło musi mieć co najmniej 8 znaków', + 'login.forgotPassword': 'Nie pamiętasz hasła?', + 'login.forgotPasswordTitle': 'Zresetuj hasło', + 'login.forgotPasswordBody': + 'Wpisz adres e-mail użyty przy rejestracji. Jeśli konto istnieje, wyślemy link do resetu.', + 'login.forgotPasswordSubmit': 'Wyślij link', + 'login.forgotPasswordSentTitle': 'Sprawdź swoją pocztę', + 'login.forgotPasswordSentBody': + 'Jeśli istnieje konto dla tego adresu, link jest już w drodze. Wygaśnie za 60 minut.', + 'login.forgotPasswordSmtpHintOff': + 'Uwaga: administrator nie skonfigurował SMTP, więc link resetujący zostanie zapisany w konsoli serwera zamiast wysłania e-mailem.', + 'login.backToLogin': 'Wróć do logowania', + 'login.newPassword': 'Nowe hasło', + 'login.confirmPassword': 'Potwierdź nowe hasło', + 'login.passwordsDontMatch': 'Hasła nie są zgodne', + 'login.mfaCode': 'Kod 2FA', + 'login.resetPasswordTitle': 'Ustaw nowe hasło', + 'login.resetPasswordBody': + 'Wybierz silne hasło, którego tu jeszcze nie używałeś. Minimum 8 znaków.', + 'login.resetPasswordMfaBody': + 'Wpisz kod 2FA lub kod zapasowy, aby zakończyć reset.', + 'login.resetPasswordSubmit': 'Zresetuj hasło', + 'login.resetPasswordVerify': 'Zweryfikuj i zresetuj', + 'login.resetPasswordSuccessTitle': 'Hasło zaktualizowane', + 'login.resetPasswordSuccessBody': 'Możesz się teraz zalogować nowym hasłem.', + 'login.resetPasswordInvalidLink': 'Nieprawidłowy link', + 'login.resetPasswordInvalidLinkBody': + 'Brakuje linku lub jest uszkodzony. Poproś o nowy, aby kontynuować.', + 'login.resetPasswordFailed': 'Reset nie powiódł się. Link mógł wygasnąć.', + 'login.setNewPassword': 'Ustaw nowe hasło', + 'login.setNewPasswordHint': 'Musisz zmienić hasło.', +}; +export default login; diff --git a/shared/src/i18n/pl/map.ts b/shared/src/i18n/pl/map.ts new file mode 100644 index 00000000..11d7d5ed --- /dev/null +++ b/shared/src/i18n/pl/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Połączenia', + 'map.showConnections': 'Pokaż trasy rezerwacji', + 'map.hideConnections': 'Ukryj trasy rezerwacji', +}; +export default map; diff --git a/shared/src/i18n/pl/members.ts b/shared/src/i18n/pl/members.ts new file mode 100644 index 00000000..99f756ea --- /dev/null +++ b/shared/src/i18n/pl/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Udostępnij podróż', + 'members.inviteUser': 'Zaproś użytkownika', + 'members.selectUser': 'Wybierz użytkownika...', + 'members.invite': 'Zaproś', + 'members.allHaveAccess': 'Wszyscy użytkownicy mają już dostęp.', + 'members.access': 'Dostęp', + 'members.person': 'osoba', + 'members.persons': 'osoby', + 'members.you': 'ty', + 'members.owner': 'Właściciel', + 'members.leaveTrip': 'Opuść podróż', + 'members.removeAccess': 'Usuń dostęp', + 'members.confirmLeave': 'Opuścić podróż? Stracisz dostęp.', + 'members.confirmRemove': 'Usunąć dostęp dla tego użytkownika?', + 'members.loadError': 'Nie udało się załadować członków', + 'members.added': 'dodano', + 'members.addError': 'Nie udało się dodać członka', + 'members.removed': 'Usunięto członka', + 'members.removeError': 'Nie udało się usunąć członka', +}; +export default members; diff --git a/shared/src/i18n/pl/memories.ts b/shared/src/i18n/pl/memories.ts new file mode 100644 index 00000000..b35a0e23 --- /dev/null +++ b/shared/src/i18n/pl/memories.ts @@ -0,0 +1,81 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Zdjęcia', + 'memories.notConnected': 'Immich nie jest połączony', + 'memories.notConnectedHint': + 'Połącz swoją instancję Immich w ustawieniach, aby przeglądać tutaj swoje zdjęcia z podróży.', + 'memories.notConnectedMultipleHint': + 'Połącz jednego z tych dostawców zdjęć: {provider_names} w Ustawieniach, aby móc dodawać zdjęcia do tej podróży.', + 'memories.noDates': 'Dodaj daty do swojej podróży, aby załadować zdjęcia.', + 'memories.noPhotos': 'Nie znaleziono zdjęć', + 'memories.noPhotosHint': + 'Nie znaleziono zdjęć w Immich dla tego zakresu dat podróży.', + 'memories.photosFound': 'zdjęć', + 'memories.fromOthers': 'od innych', + 'memories.sharePhotos': 'Udostępnij zdjęcia', + 'memories.sharing': 'Udostępnianie', + 'memories.reviewTitle': 'Przejrzyj swoje zdjęcia', + 'memories.reviewHint': 'Kliknij w zdjęcia, aby wykluczyć je z udostępnienia.', + 'memories.shareCount': 'Udostępnij {count} zdjęć', + 'memories.providerUrl': 'URL serwera', + 'memories.providerApiKey': 'Klucz API', + 'memories.providerUsername': 'Nazwa użytkownika', + 'memories.providerPassword': 'Hasło', + 'memories.providerOTP': 'Kod MFA (jeśli włączony)', + 'memories.skipSSLVerification': 'Pomiń weryfikację certyfikatu SSL', + 'memories.immichAutoUpload': + 'Przy przesyłaniu kopiuj zdjęcia journey także do Immich', + 'memories.providerUrlHintSynology': + 'Uwzględnij ścieżkę aplikacji Photos w URL, np. https://nas:5001/photo', + 'memories.testConnection': 'Test', + 'memories.testShort': 'Test', + 'memories.connected': 'Połączono', + 'memories.disconnected': 'Nie połączono', + 'memories.connectionSuccess': 'Połączono z Immich', + 'memories.connectionError': 'Nie udało się połączyć z Immich', + 'memories.saved': 'Ustawienia {provider_name} zostały zapisane', + 'memories.providerDisconnectedBanner': + 'Połączenie z {provider_name} zostało utracone. Połącz ponownie w Ustawieniach, aby wyświetlać zdjęcia.', + 'memories.saveError': 'Nie można zapisać ustawień {provider_name}', + 'memories.addPhotos': 'Dodaj zdjęcia', + 'memories.selectPhotos': 'Wybierz zdjęcia z Immich', + 'memories.selectPhotosMultiple': 'Wybierz zdjęcia', + 'memories.selectHint': 'Dotknij zdjęć, aby je zaznaczyć.', + 'memories.selected': 'wybranych', + 'memories.addSelected': 'Dodaj {count} zdjęć', + 'memories.alreadyAdded': 'Dodano', + 'memories.private': 'Prywatne', + 'memories.stopSharing': 'Przestań udostępniać', + 'memories.oldest': 'Od najstarszych', + 'memories.newest': 'Od najnowszych', + 'memories.allLocations': 'Wszystkie lokalizacje', + 'memories.tripDates': 'Daty podróży', + 'memories.allPhotos': 'Wszystkie zdjęcia', + 'memories.confirmShareTitle': 'Udostępnić członkom podróży?', + 'memories.confirmShareHint': + '{count} zdjęć będzie widocznych dla wszystkich członków tej podróży. Możesz później ustawić poszczególne zdjęcia jako prywatne.', + 'memories.confirmShareButton': 'Udostępnij zdjęcia', + 'memories.testFirst': 'Najpierw przetestuj połączenie', + 'memories.linkAlbum': 'Połącz album', + 'memories.selectAlbum': 'Wybierz album Immich', + 'memories.selectAlbumMultiple': 'Wybierz album', + 'memories.noAlbums': 'Nie znaleziono albumów', + 'memories.syncAlbum': 'Synchronizuj album', + 'memories.unlinkAlbum': 'Odłącz album', + 'memories.photos': 'zdjęcia', + 'memories.error.loadAlbums': 'Nie udało się załadować albumów', + 'memories.error.linkAlbum': 'Nie udało się połączyć albumu', + 'memories.error.unlinkAlbum': 'Nie udało się odłączyć albumu', + 'memories.error.syncAlbum': 'Nie udało się zsynchronizować albumu', + 'memories.error.loadPhotos': 'Nie udało się załadować zdjęć', + 'memories.error.addPhotos': 'Nie udało się dodać zdjęć', + 'memories.error.removePhoto': 'Nie udało się usunąć zdjęcia', + 'memories.error.toggleSharing': 'Nie udało się zaktualizować udostępniania', + 'memories.saveRouteNotConfigured': + 'Trasa zapisu nie jest skonfigurowana dla tego dostawcy', + 'memories.testRouteNotConfigured': + 'Trasa testowa nie jest skonfigurowana dla tego dostawcy', + 'memories.fillRequiredFields': 'Proszę wypełnić wszystkie wymagane pola', +}; +export default memories; diff --git a/shared/src/i18n/pl/nav.ts b/shared/src/i18n/pl/nav.ts new file mode 100644 index 00000000..9f6e4616 --- /dev/null +++ b/shared/src/i18n/pl/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Podróż', + 'nav.share': 'Udostępnij', + 'nav.settings': 'Ustawienia', + 'nav.admin': 'Admin', + 'nav.logout': 'Wyloguj', + 'nav.lightMode': 'Tryb jasny', + 'nav.darkMode': 'Tryb ciemny', + 'nav.autoMode': 'Tryb automatyczny', + 'nav.administrator': 'Administrator', + 'nav.myTrips': 'Moje podróże', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Ustawienia', + 'nav.bottomAdmin': 'Ustawienia administratora', + 'nav.bottomLogout': 'Wyloguj się', + 'nav.bottomAdminBadge': 'Administrator', +}; +export default nav; diff --git a/shared/src/i18n/pl/notif.ts b/shared/src/i18n/pl/notif.ts new file mode 100644 index 00000000..8d3d1d1c --- /dev/null +++ b/shared/src/i18n/pl/notif.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Powiadomienie', + 'notif.test.simple.text': 'To jest proste powiadomienie testowe.', + 'notif.test.boolean.text': 'Czy akceptujesz to powiadomienie testowe?', + 'notif.test.navigate.text': 'Kliknij poniżej, aby przejść do pulpitu.', + 'notif.trip_invite.title': 'Zaproszenie do podróży', + 'notif.trip_invite.text': '{actor} zaprosił Cię do {trip}', + 'notif.booking_change.title': 'Rezerwacja zaktualizowana', + 'notif.booking_change.text': '{actor} zaktualizował rezerwację w {trip}', + 'notif.trip_reminder.title': 'Przypomnienie o podróży', + 'notif.trip_reminder.text': 'Twoja podróż {trip} zbliża się!', + 'notif.todo_due.title': 'Zadanie z terminem', + 'notif.todo_due.text': '{todo} w {trip} — termin {due}', + 'notif.vacay_invite.title': 'Zaproszenie Vacay Fusion', + 'notif.vacay_invite.text': + '{actor} zaprosił Cię do połączenia planów urlopowych', + 'notif.photos_shared.title': 'Zdjęcia udostępnione', + 'notif.photos_shared.text': + '{actor} udostępnił {count} zdjęcie/zdjęcia w {trip}', + 'notif.collab_message.title': 'Nowa wiadomość', + 'notif.collab_message.text': '{actor} wysłał wiadomość w {trip}', + 'notif.packing_tagged.title': 'Zadanie pakowania', + 'notif.packing_tagged.text': '{actor} przypisał Cię do {category} w {trip}', + 'notif.version_available.title': 'Nowa wersja dostępna', + 'notif.version_available.text': 'TREK {version} jest teraz dostępny', + 'notif.action.view_trip': 'Zobacz podróż', + 'notif.action.view_collab': 'Zobacz wiadomości', + 'notif.action.view_packing': 'Zobacz pakowanie', + 'notif.action.view_photos': 'Zobacz zdjęcia', + 'notif.action.view_vacay': 'Zobacz Vacay', + 'notif.action.view_admin': 'Przejdź do admina', + 'notif.action.view': 'Zobacz', + 'notif.action.accept': 'Akceptuj', + 'notif.action.decline': 'Odrzuć', + 'notif.generic.title': 'Powiadomienie', + 'notif.generic.text': 'Masz nowe powiadomienie', + 'notif.dev.unknown_event.title': '[DEV] Nieznane zdarzenie', + 'notif.dev.unknown_event.text': + 'Typ zdarzenia "{event}" nie jest zarejestrowany w EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/pl/notifications.ts b/shared/src/i18n/pl/notifications.ts new file mode 100644 index 00000000..068fc02b --- /dev/null +++ b/shared/src/i18n/pl/notifications.ts @@ -0,0 +1,36 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Powiadomienia', + 'notifications.markAllRead': 'Oznacz wszystkie jako przeczytane', + 'notifications.deleteAll': 'Usuń wszystkie', + 'notifications.showAll': 'Pokaż wszystkie', + 'notifications.empty': 'Brak powiadomień', + 'notifications.emptyDescription': 'Jesteś na bieżąco!', + 'notifications.all': 'Wszystkie', + 'notifications.unreadOnly': 'Nieprzeczytane', + 'notifications.markRead': 'Oznacz jako przeczytane', + 'notifications.markUnread': 'Oznacz jako nieprzeczytane', + 'notifications.delete': 'Usuń', + 'notifications.system': 'System', + 'notifications.synologySessionCleared.title': 'Synology Photos rozłączone', + 'notifications.synologySessionCleared.text': + 'Twój serwer lub konto zostało zmienione — przejdź do Ustawień, aby ponownie przetestować połączenie.', + 'notifications.versionAvailable.title': 'Dostępna aktualizacja', + 'notifications.versionAvailable.text': 'TREK {version} jest już dostępny.', + 'notifications.versionAvailable.button': 'Zobacz szczegóły', + 'notifications.test.title': 'Testowe powiadomienie od {actor}', + 'notifications.test.text': 'To jest powiadomienie testowe.', + 'notifications.test.booleanTitle': '{actor} prosi o akceptację', + 'notifications.test.booleanText': 'Testowe powiadomienie z wyborem.', + 'notifications.test.accept': 'Zatwierdź', + 'notifications.test.decline': 'Odrzuć', + 'notifications.test.navigateTitle': 'Sprawdź coś', + 'notifications.test.navigateText': 'Testowe powiadomienie nawigacyjne.', + 'notifications.test.goThere': 'Przejdź tam', + 'notifications.test.adminTitle': 'Komunikat administracyjny', + 'notifications.test.adminText': '{actor} wysłał testowe powiadomienie.', + 'notifications.test.tripTitle': '{actor} opublikował w Twojej podróży', + 'notifications.test.tripText': 'Testowe powiadomienie dla podróży "{trip}".', +}; +export default notifications; diff --git a/shared/src/i18n/pl/oauth.ts b/shared/src/i18n/pl/oauth.ts new file mode 100644 index 00000000..c53998f6 --- /dev/null +++ b/shared/src/i18n/pl/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Podróże', + 'oauth.scope.group.places': 'Miejsca', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Pakowanie', + 'oauth.scope.group.todos': 'Zadania', + 'oauth.scope.group.budget': 'Budżet', + 'oauth.scope.group.reservations': 'Rezerwacje', + 'oauth.scope.group.collab': 'Współpraca', + 'oauth.scope.group.notifications': 'Powiadomienia', + 'oauth.scope.group.vacay': 'Urlop', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Pogoda', + 'oauth.scope.group.journey': 'Dziennik podróży', + 'oauth.scope.trips:read.label': 'Przeglądaj podróże i itineraria', + 'oauth.scope.trips:read.description': + 'Odczytuj podróże, dni, notatki i członków', + 'oauth.scope.trips:write.label': 'Edytuj podróże i itineraria', + 'oauth.scope.trips:write.description': + 'Twórz i aktualizuj podróże, dni, notatki oraz zarządzaj członkami', + 'oauth.scope.trips:delete.label': 'Usuń podróże', + 'oauth.scope.trips:delete.description': + 'Trwale usuń całe podróże — ta akcja jest nieodwracalna', + 'oauth.scope.trips:share.label': 'Zarządzaj linkami udostępniania', + 'oauth.scope.trips:share.description': + 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania', + 'oauth.scope.places:read.label': 'Przeglądaj miejsca i dane mapy', + 'oauth.scope.places:read.description': + 'Odczytuj miejsca, przypisania dni, tagi i kategorie', + 'oauth.scope.places:write.label': 'Zarządzaj miejscami', + 'oauth.scope.places:write.description': + 'Twórz, aktualizuj i usuń miejsca, przypisania i tagi', + 'oauth.scope.atlas:read.label': 'Przeglądaj Atlas', + 'oauth.scope.atlas:read.description': + 'Odczytuj odwiedzone kraje, regiony i listę marzeń', + 'oauth.scope.atlas:write.label': 'Zarządzaj Atlasem', + 'oauth.scope.atlas:write.description': + 'Oznaczaj kraje i regiony jako odwiedzone, zarządzaj listą marzeń', + 'oauth.scope.packing:read.label': 'Przeglądaj listy pakowania', + 'oauth.scope.packing:read.description': + 'Odczytuj przedmioty, torby i przypisania kategorii', + 'oauth.scope.packing:write.label': 'Zarządzaj listami pakowania', + 'oauth.scope.packing:write.description': + 'Dodawaj, aktualizuj, usuwaj, zaznaczaj i porządkuj przedmioty i torby', + 'oauth.scope.todos:read.label': 'Przeglądaj listy zadań', + 'oauth.scope.todos:read.description': + 'Odczytuj zadania podróży i przypisania kategorii', + 'oauth.scope.todos:write.label': 'Zarządzaj listami zadań', + 'oauth.scope.todos:write.description': + 'Twórz, aktualizuj, zaznaczaj, usuwaj i porządkuj zadania', + 'oauth.scope.budget:read.label': 'Przeglądaj budżet', + 'oauth.scope.budget:read.description': + 'Odczytuj pozycje budżetu i zestawienie wydatków', + 'oauth.scope.budget:write.label': 'Zarządzaj budżetem', + 'oauth.scope.budget:write.description': + 'Twórz, aktualizuj i usuń pozycje budżetu', + 'oauth.scope.reservations:read.label': 'Przeglądaj rezerwacje', + 'oauth.scope.reservations:read.description': + 'Odczytuj rezerwacje i szczegóły zakwaterowania', + 'oauth.scope.reservations:write.label': 'Zarządzaj rezerwacjami', + 'oauth.scope.reservations:write.description': + 'Twórz, aktualizuj, usuwaj i porządkuj rezerwacje', + 'oauth.scope.collab:read.label': 'Przeglądaj współpracę', + 'oauth.scope.collab:read.description': + 'Odczytuj notatki, ankiety i wiadomości', + 'oauth.scope.collab:write.label': 'Zarządzaj współpracą', + 'oauth.scope.collab:write.description': + 'Twórz, aktualizuj i usuń notatki, ankiety i wiadomości', + 'oauth.scope.notifications:read.label': 'Przeglądaj powiadomienia', + 'oauth.scope.notifications:read.description': + 'Odczytuj powiadomienia i liczby nieprzeczytanych', + 'oauth.scope.notifications:write.label': 'Zarządzaj powiadomieniami', + 'oauth.scope.notifications:write.description': + 'Oznaczaj powiadomienia jako przeczytane i odpowiadaj na nie', + 'oauth.scope.vacay:read.label': 'Przeglądaj plany urlopowe', + 'oauth.scope.vacay:read.description': + 'Odczytuj dane planowania urlopu, wpisy i statystyki', + 'oauth.scope.vacay:write.label': 'Zarządzaj planami urlopowymi', + 'oauth.scope.vacay:write.description': + 'Twórz i zarządzaj wpisami urlopowymi, świętami i planami zespołu', + 'oauth.scope.geo:read.label': 'Mapy i geokodowanie', + 'oauth.scope.geo:read.description': + 'Wyszukuj miejsca, rozwiązuj adresy URL map i odwrotnie geokoduj współrzędne', + 'oauth.scope.weather:read.label': 'Prognozy pogody', + 'oauth.scope.weather:read.description': + 'Pobieraj prognozy pogody dla miejsc i dat podróży', + 'oauth.scope.journey:read.label': 'Przeglądaj dzienniki podróży', + 'oauth.scope.journey:read.description': + 'Odczytuj dzienniki podróży, wpisy i listę współautorów', + 'oauth.scope.journey:write.label': 'Zarządzaj dziennikami podróży', + 'oauth.scope.journey:write.description': + 'Twórz, aktualizuj i usuwaj dzienniki podróży oraz ich wpisy', + 'oauth.scope.journey:share.label': 'Zarządzaj linkami dzienników podróży', + 'oauth.scope.journey:share.description': + 'Twórz, aktualizuj i unieważniaj publiczne linki udostępniania dzienników podróży', +}; +export default oauth; diff --git a/shared/src/i18n/pl/packing.ts b/shared/src/i18n/pl/packing.ts new file mode 100644 index 00000000..f277ace3 --- /dev/null +++ b/shared/src/i18n/pl/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Lista pakowania', + 'packing.empty': 'Lista pakowania jest pusta', + 'packing.import': 'Importuj', + 'packing.importTitle': 'Importuj listę pakowania', + 'packing.importHint': + 'Jedna pozycja w wierszu. Format: kategoria, nazwa, waga w gramach (opcjonalnie), Torba (opcjonalnie), checked/unchecked (opcjonalnie)', + 'packing.importPlaceholder': + 'Higiena, Szczoteczka do zębów\nOdzież, Koszulki, 200\nDokumenty, Paszport, , Podręczny\nElektronika, Ładowarka, 50, Walizka, checked', + 'packing.importCsv': 'Załaduj CSV/TXT', + 'packing.importAction': 'Importuj {count}', + 'packing.importSuccess': '{count} pozycji zaimportowanych', + 'packing.importError': 'Import nie powiódł się', + 'packing.importEmpty': 'Brak pozycji do zaimportowania', + 'packing.progress': '{packed} z {total} spakowanych ({percent}%)', + 'packing.clearChecked': 'Usuń {count} spakowanych', + 'packing.clearCheckedShort': 'Usuń {count}', + 'packing.suggestions': 'Sugestie', + 'packing.suggestionsTitle': 'Dodaj sugestie', + 'packing.allSuggested': 'Dodano wszystkie sugestie', + 'packing.allPacked': 'Wszystko spakowane!', + 'packing.addPlaceholder': 'Dodaj nowy przedmiot...', + 'packing.categoryPlaceholder': 'Kategoria...', + 'packing.filterAll': 'Wszystkie', + 'packing.filterOpen': 'Do spakowania', + 'packing.filterDone': 'Spakowane', + 'packing.emptyTitle': 'Lista pakowania jest pusta', + 'packing.emptyHint': 'Dodaj przedmioty lub użyj sugestii', + 'packing.emptyFiltered': 'Brak przedmiotów pasujących do filtra', + 'packing.menuRename': 'Zmień nazwę', + 'packing.menuCheckAll': 'Zaznacz wszystko', + 'packing.menuUncheckAll': 'Odznacz wszystko', + 'packing.menuDeleteCat': 'Usuń kategorię', + 'packing.saveAsTemplate': 'Zapisz jako szablon', + 'packing.templateName': 'Nazwa szablonu', + 'packing.templateSaved': 'Lista pakowania zapisana jako szablon', + 'packing.noMembers': 'Brak członków podróży', + 'packing.addItem': 'Dodaj przedmiot', + 'packing.addItemPlaceholder': 'Nazwa przedmiotu...', + 'packing.addCategory': 'Dodaj kategorię', + 'packing.newCategoryPlaceholder': 'Nazwa kategorii (np. Odzież)', + 'packing.applyTemplate': 'Zastosuj szablon', + 'packing.template': 'Szablon', + 'packing.templateApplied': '{count} przedmiotów dodanych z szablonu', + 'packing.templateError': 'Nie udało się zastosować szablonu', + 'packing.bags': 'Torby', + 'packing.noBag': 'Nieprzypisane', + 'packing.totalWeight': 'Waga całkowita', + 'packing.bagName': 'Nazwa torby...', + 'packing.addBag': 'Dodaj torbę', + 'packing.changeCategory': 'Zmień kategorię', + 'packing.confirm.clearChecked': + 'Czy na pewno chcesz usunąć {count} spakowanych przedmiotów?', + 'packing.confirm.deleteCat': + 'Czy na pewno chcesz usunąć kategorię "{name}" z {count} przedmiotami?', + 'packing.defaultCategory': 'Inne', + 'packing.toast.saveError': 'Nie udało się zapisać', + 'packing.toast.deleteError': 'Nie udało się usunąć', + 'packing.toast.renameError': 'Nie udało się zmienić nazwy', + 'packing.toast.addError': 'Nie udało się dodać', + 'packing.suggestions.items': [ + { + name: 'Paszport', + category: 'Dokumenty', + }, + { + name: 'Dowód osobisty', + category: 'Dokumenty', + }, + { + name: 'Ubezpieczenie turystyczne', + category: 'Dokumenty', + }, + { + name: 'Bilety lotnicze', + category: 'Dokumenty', + }, + { + name: 'Karta kredytowa', + category: 'Finanse', + }, + { + name: 'Gotówka', + category: 'Finanse', + }, + { + name: 'Wiza', + category: 'Dokumenty', + }, + { + name: 'Koszulki', + category: 'Odzież', + }, + { + name: 'Spodnie', + category: 'Odzież', + }, + { + name: 'Bielizna', + category: 'Odzież', + }, + { + name: 'Skarpetki', + category: 'Odzież', + }, + { + name: 'Kurtka', + category: 'Odzież', + }, + { + name: 'Piżama', + category: 'Odzież', + }, + { + name: 'Strój kąpielowy', + category: 'Odzież', + }, + { + name: 'Kurtka przeciwdeszczowa', + category: 'Odzież', + }, + { + name: 'Wygodne buty', + category: 'Obuwie', + }, + { + name: 'Szczoteczka do zębów', + category: 'Higiena', + }, + { + name: 'Pasta do zębów', + category: 'Higiena', + }, + { + name: 'Szampon', + category: 'Higiena', + }, + { + name: 'Dezodorant', + category: 'Higiena', + }, + { + name: 'Krem z filtrem', + category: 'Higiena', + }, + { + name: 'Maszynka do golenia', + category: 'Higiena', + }, + { + name: 'Ładowarka', + category: 'Elektronika', + }, + { + name: 'Powerbank', + category: 'Elektronika', + }, + { + name: 'Słuchawki', + category: 'Elektronika', + }, + { + name: 'Adapter podróżny', + category: 'Elektronika', + }, + { + name: 'Aparat', + category: 'Elektronika', + }, + { + name: 'Leki', + category: 'Zdrowie', + }, + { + name: 'Plastry', + category: 'Zdrowie', + }, + { + name: 'Środek dezynfekujący', + category: 'Zdrowie', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/pl/pdf.ts b/shared/src/i18n/pl/pdf.ts new file mode 100644 index 00000000..3b9f89e6 --- /dev/null +++ b/shared/src/i18n/pl/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Plan podróży', + 'pdf.planned': 'Zaplanowane', + 'pdf.costLabel': 'Koszt w EUR', + 'pdf.preview': 'Podgląd PDF', + 'pdf.saveAsPdf': 'Zapisz jako PDF', +}; +export default pdf; diff --git a/shared/src/i18n/pl/perm.ts b/shared/src/i18n/pl/perm.ts new file mode 100644 index 00000000..2c94d9b8 --- /dev/null +++ b/shared/src/i18n/pl/perm.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Ustawienia uprawnień', + 'perm.subtitle': 'Kontroluj uprawnienia w aplikacji', + 'perm.saved': 'Ustawienia uprawnień zapisane', + 'perm.resetDefaults': 'Przywróć domyślne', + 'perm.customized': 'dostosowane', + 'perm.level.admin': 'Tylko admin', + 'perm.level.tripOwner': 'Właściciel podróży', + 'perm.level.tripMember': 'Członkowie podróży', + 'perm.level.everybody': 'Wszyscy', + 'perm.cat.trip': 'Zarządzanie podróżami', + 'perm.cat.members': 'Zarządzanie członkami', + 'perm.cat.files': 'Pliki', + 'perm.cat.content': 'Treść i harmonogram', + 'perm.cat.extras': 'Budżet, pakowanie i współpraca', + 'perm.action.trip_create': 'Tworzenie podróży', + 'perm.action.trip_edit': 'Edytowanie podróży', + 'perm.action.trip_delete': 'Usuwanie podróży', + 'perm.action.trip_archive': 'Archiwizacja podróży', + 'perm.action.trip_cover_upload': 'Przesyłanie okładki', + 'perm.action.member_manage': 'Zarządzanie członkami', + 'perm.action.file_upload': 'Przesyłanie plików', + 'perm.action.file_edit': 'Edytowanie plików', + 'perm.action.file_delete': 'Usuwanie plików', + 'perm.action.place_edit': 'Zarządzanie miejscami', + 'perm.action.day_edit': 'Edytowanie dni', + 'perm.action.reservation_edit': 'Zarządzanie rezerwacjami', + 'perm.action.budget_edit': 'Zarządzanie budżetem', + 'perm.action.packing_edit': 'Zarządzanie pakowaniem', + 'perm.action.collab_edit': 'Współpraca', + 'perm.action.share_manage': 'Zarządzanie udostępnianiem', + 'perm.actionHint.trip_create': 'Kto może tworzyć nowe podróże', + 'perm.actionHint.trip_edit': 'Kto może edytować szczegóły podróży', + 'perm.actionHint.trip_delete': 'Kto może usunąć podróż', + 'perm.actionHint.trip_archive': 'Kto może archiwizować podróż', + 'perm.actionHint.trip_cover_upload': 'Kto może zmieniać okładkę', + 'perm.actionHint.member_manage': 'Kto może zapraszać lub usuwać członków', + 'perm.actionHint.file_upload': 'Kto może przesyłać pliki', + 'perm.actionHint.file_edit': 'Kto może edytować pliki', + 'perm.actionHint.file_delete': 'Kto może usuwać pliki', + 'perm.actionHint.place_edit': 'Kto może zarządzać miejscami', + 'perm.actionHint.day_edit': 'Kto może edytować dni i przypisania', + 'perm.actionHint.reservation_edit': 'Kto może zarządzać rezerwacjami', + 'perm.actionHint.budget_edit': 'Kto może zarządzać budżetem', + 'perm.actionHint.packing_edit': 'Kto może zarządzać pakowaniem', + 'perm.actionHint.collab_edit': 'Kto może korzystać ze współpracy', + 'perm.actionHint.share_manage': 'Kto może zarządzać linkami', +}; +export default perm; diff --git a/shared/src/i18n/pl/photos.ts b/shared/src/i18n/pl/photos.ts new file mode 100644 index 00000000..bb0f8dd9 --- /dev/null +++ b/shared/src/i18n/pl/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Zdjęcia', + 'photos.subtitle': '{count} zdjęć dla {trip}', + 'photos.dropHere': 'Przeciągnij zdjęcia tutaj...', + 'photos.dropHereActive': 'Przeciągnij zdjęcia tutaj', + 'photos.captionForAll': 'Podpis (dla wszystkich)', + 'photos.captionPlaceholder': 'Opcjonalny podpis...', + 'photos.addCaption': 'Dodaj podpis...', + 'photos.allDays': 'Wszystkie dni', + 'photos.noPhotos': 'Brak zdjęć', + 'photos.uploadHint': 'Prześlij zdjęcia z podróży', + 'photos.clickToSelect': 'lub kliknij, aby wybrać', + 'photos.linkPlace': 'Połącz z miejscem', + 'photos.noPlace': 'Brak miejsca', + 'photos.uploadN': 'Prześlij {n} zdjęć', + 'photos.linkDay': 'Połącz dzień', + 'photos.noDay': 'Brak dnia', + 'photos.dayLabel': 'Dzień {number}', + 'photos.photoSelected': 'Zdjęcie wybrane', + 'photos.photosSelected': 'Zdjęcia wybrane', + 'photos.fileTypeHint': 'JPG, PNG, WebP · maks. 10 MB · do 30 zdjęć', +}; +export default photos; diff --git a/shared/src/i18n/pl/places.ts b/shared/src/i18n/pl/places.ts new file mode 100644 index 00000000..04a5979e --- /dev/null +++ b/shared/src/i18n/pl/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Dodaj miejsce/atrakcję', + 'places.importFile': 'Importuj plik', + 'places.sidebarDrop': 'Upuść, aby zaimportować', + 'places.importFileHint': + 'Importuj pliki .gpx, .kml lub .kmz z narzędzi takich jak Google My Maps, Google Earth lub tracker GPS.', + 'places.importFileDropHere': + 'Kliknij, aby wybrać plik lub przeciągnij i upuść tutaj', + 'places.importFileDropActive': 'Upuść plik, aby go wybrać', + 'places.importFileUnsupported': + 'Nieobsługiwany typ pliku. Użyj .gpx, .kml lub .kmz.', + 'places.importFileTooLarge': + 'Plik jest za duży. Maksymalny rozmiar przesyłania to {maxMb} MB.', + 'places.importFileError': 'Import nie powiódł się', + 'places.importAllSkipped': 'Wszystkie miejsca były już w podróży.', + 'places.gpxImported': '{count} miejsc zaimportowanych z GPX', + 'places.gpxImportTypes': 'Co chcesz zaimportować?', + 'places.gpxImportWaypoints': 'Punkty trasy', + 'places.gpxImportRoutes': 'Trasy', + 'places.gpxImportTracks': 'Trasy GPS (ze śladem)', + 'places.gpxImportNoneSelected': 'Wybierz co najmniej jeden typ do importu.', + 'places.kmlImportTypes': 'Co chcesz zaimportować?', + 'places.kmlImportPoints': 'Punkty (Placemarks)', + 'places.kmlImportPaths': 'Ścieżki (LineStrings)', + 'places.kmlImportNoneSelected': 'Wybierz co najmniej jeden typ.', + 'places.selectionCount': '{count} zaznaczono', + 'places.deleteSelected': 'Usuń wybrane', + 'places.kmlKmzImported': 'Zaimportowano {count} miejsc z KMZ/KML', + 'places.urlResolved': 'Miejsce zaimportowane z URL', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Zaimportowano: {created} • Pominięto: {skipped}', + 'places.importGoogleList': 'Lista Google', + 'places.assignToDay': 'Do którego dnia dodać?', + 'places.all': 'Wszystkie', + 'places.unplanned': 'Niezaplanowane', + 'places.filterTracks': 'Trasy', + 'places.search': 'Szukaj miejsc...', + 'places.allCategories': 'Wszystkie kategorie', + 'places.categoriesSelected': 'kategorii', + 'places.clearFilter': 'Wyczyść filtr', + 'places.count': '{count} miejsc', + 'places.countSingular': '1 miejsce', + 'places.allPlanned': 'Wszystkie miejsca są zaplanowane', + 'places.noneFound': 'Nie znaleziono miejsc', + 'places.editPlace': 'Edytuj miejsce', + 'places.formName': 'Nazwa', + 'places.formNamePlaceholder': 'np. Wieża Eiffla', + 'places.formDescription': 'Opis', + 'places.formDescriptionPlaceholder': 'Krótki opis...', + 'places.formAddress': 'Adres', + 'places.formAddressPlaceholder': 'Ulica, miasto, kraj', + 'places.formLat': 'Szerokość (np. 48.8566)', + 'places.formLng': 'Długość (np. 2.3522)', + 'places.formCategory': 'Kategoria', + 'places.noCategory': 'Brak kategorii', + 'places.categoryNamePlaceholder': 'Nazwa kategorii', + 'places.formTime': 'Godzina', + 'places.startTime': 'Początek', + 'places.endTime': 'Koniec', + 'places.endTimeBeforeStart': + 'Godzina zakończenia jest przed godziną rozpoczęcia', + 'places.timeCollision': 'Nakładanie się godzin z:', + 'places.formWebsite': 'Strona internetowa', + 'places.formNotes': 'Notatki', + 'places.formNotesPlaceholder': 'Osobiste notatki...', + 'places.formReservation': 'Rezerwacja', + 'places.reservationNotesPlaceholder': + 'Notatki z rezerwacji, numer potwierdzenia...', + 'places.mapsSearchPlaceholder': 'Szukaj miejsc...', + 'places.mapsSearchError': 'Nie udało się wyszukać miejsca.', + 'places.loadingDetails': 'Ładowanie szczegółów miejsca…', + 'places.osmHint': + 'Korzystając z OpenStreetMap (brak zdjęć, godzin otwarcia czy ocen). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', + 'places.osmActive': + 'Szukaj przez OpenStreetMap (brak zdjęć, ocen czy godzin otwarcia). Dodaj klucz API Google w ustawieniach aby uzyskać pełne dane.', + 'places.categoryCreateError': 'Nie udało się utworzyć kategorii', + 'places.nameRequired': 'Proszę podać nazwę', + 'places.saveError': 'Nie udało się zapisać', + 'places.importNaverList': 'Lista Naver', + 'places.importList': 'Import listy', + 'places.googleListHint': 'Wklej link do listy Google Maps.', + 'places.googleListImported': 'Zaimportowano {count} miejsc', + 'places.googleListError': 'Nie udało się zaimportować listy', + 'places.naverListHint': + 'Wklej link do udostępnionej listy Naver Maps, aby zaimportować wszystkie miejsca.', + 'places.naverListImported': 'Zaimportowano {count} miejsc z "{list}"', + 'places.naverListError': 'Nie udało się zaimportować listy Naver Maps', + 'places.viewDetails': 'Zobacz szczegóły', +}; +export default places; diff --git a/shared/src/i18n/pl/planner.ts b/shared/src/i18n/pl/planner.ts new file mode 100644 index 00000000..3c6babb0 --- /dev/null +++ b/shared/src/i18n/pl/planner.ts @@ -0,0 +1,69 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Miejsca', + 'planner.bookings': 'Rezerwacje', + 'planner.packingList': 'Lista pakowania', + 'planner.documents': 'Dokumenty', + 'planner.dayPlan': 'Plan', + 'planner.reservations': 'Rezerwacje', + 'planner.minTwoPlaces': + 'Wymagane są przynajmniej dwa miejsca ze współrzędnymi', + 'planner.noGeoPlaces': 'Brak miejsc ze współrzędnymi', + 'planner.routeCalculated': 'Trasa została obliczona', + 'planner.routeCalcFailed': 'Nie udało się obliczyć trasy', + 'planner.routeError': 'Błąd obliczania trasy', + 'planner.icsExportFailed': 'Eksport ICS nie powiódł się', + 'planner.routeOptimized': 'Trasa została zoptymalizowana', + 'planner.reservationUpdated': 'Rezerwacja została zaktualizowana', + 'planner.reservationAdded': 'Rezerwacja została dodana', + 'planner.confirmDeleteReservation': 'Usunąć rezerwację?', + 'planner.reservationDeleted': 'Rezerwacja została usunięta', + 'planner.days': 'Dni', + 'planner.allPlaces': 'Wszystkie miejsca', + 'planner.totalPlaces': '{n} miejsc ogółem', + 'planner.noDaysPlanned': 'Nie zaplanowano jeszcze dni', + 'planner.editTrip': 'Edytuj podróż →', + 'planner.placeOne': '1 miejsce', + 'planner.placeN': '{n} miejsc', + 'planner.addNote': 'Dodaj notatkę', + 'planner.noEntries': 'Brak wpisów dla tego dnia', + 'planner.addPlace': 'Dodaj miejsce/atrakcję', + 'planner.addPlaceShort': '+ Dodaj miejsce/atrakcję', + 'planner.resPending': 'Rezerwacja oczekująca · ', + 'planner.resConfirmed': 'Rezerwacja potwierdzona · ', + 'planner.notePlaceholder': 'Notatka…', + 'planner.noteTimePlaceholder': 'Godzina (opcjonalnie)', + 'planner.noteExamplePlaceholder': + 'np. S3 o 14:30 z dworca centralnego, prom z molo 7, przerwa na lunch…', + 'planner.totalCost': 'Całkowity koszt', + 'planner.searchPlaces': 'Szukaj miejsc…', + 'planner.allCategories': 'Wszystkie kategorie', + 'planner.noPlacesFound': 'Nie znaleziono miejsc', + 'planner.addFirstPlace': 'Dodaj pierwsze miejsce', + 'planner.noReservations': 'Brak rezerwacji', + 'planner.addFirstReservation': 'Dodaj pierwszą rezerwację', + 'planner.new': 'Nowy', + 'planner.addToDay': '+ Dzień', + 'planner.calculating': 'Obliczanie…', + 'planner.route': 'Trasa', + 'planner.optimize': 'Optymalizuj', + 'planner.openGoogleMaps': 'Otwórz w Google Maps', + 'planner.selectDayHint': + 'Wybierz dzień z listy po lewej, aby zobaczyć jego plan', + 'planner.noPlacesForDay': 'Brak miejsc dla tego dnia', + 'planner.addPlacesLink': 'Dodaj miejsca →', + 'planner.minTotal': 'min. łącznie', + 'planner.noReservation': 'Brak rezerwacji', + 'planner.removeFromDay': 'Usuń z dnia', + 'planner.addToThisDay': 'Dodaj do dnia', + 'planner.overview': 'Przegląd', + 'planner.noDays': 'Brak dni', + 'planner.editTripToAddDays': 'Edytuj podróż, aby dodać dni', + 'planner.dayCount': '{n} dni', + 'planner.clickToUnlock': 'Kliknij, aby odblokować', + 'planner.keepPosition': 'Zachowaj pozycję podczas optymalizacji trasy', + 'planner.dayDetails': 'Szczegóły dnia', + 'planner.dayN': 'Dzień {n}', +}; +export default planner; diff --git a/shared/src/i18n/pl/register.ts b/shared/src/i18n/pl/register.ts new file mode 100644 index 00000000..1a65ff03 --- /dev/null +++ b/shared/src/i18n/pl/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Hasła nie są identyczne', + 'register.passwordTooShort': 'Hasło musi mieć co najmniej 6 znaków', + 'register.failed': 'Rejestracja nie powiodła się', + 'register.getStarted': 'Rozpocznij', + 'register.subtitle': + 'Utwórz konto i zacznij planować swoje wymarzone podróże.', + 'register.feature1': 'Nieograniczone plany podróży', + 'register.feature2': 'Interaktywna mapa', + 'register.feature3': 'Zarządzaj miejscami i kategoriami', + 'register.feature4': 'Śledź rezerwacje', + 'register.feature5': 'Twórz listy pakowania', + 'register.feature6': 'Przechowuj zdjęcia i pliki', + 'register.createAccount': 'Utwórz konto', + 'register.startPlanning': 'Zacznij planować podróż', + 'register.minChars': 'Min. 6 znaków', + 'register.confirmPassword': 'Potwierdź hasło', + 'register.repeatPassword': 'Powtórz hasło', + 'register.registering': 'Rejestrowanie...', + 'register.register': 'Zarejestruj się', + 'register.hasAccount': 'Masz już konto?', + 'register.signIn': 'Zaloguj się', +}; +export default register; diff --git a/shared/src/i18n/pl/reservations.ts b/shared/src/i18n/pl/reservations.ts new file mode 100644 index 00000000..063db250 --- /dev/null +++ b/shared/src/i18n/pl/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Rezerwacje', + 'reservations.empty': 'Brak rezerwacji', + 'reservations.emptyHint': 'Dodaj rezerwacje lotów, hoteli i innych', + 'reservations.add': 'Dodaj rezerwację', + 'reservations.addManual': 'Rezerwacja ręczna', + 'reservations.placeHint': + 'Wskazówka: Rezerwacje najlepiej tworzyć bezpośrednio z miejsca, aby powiązać je z planem dnia.', + 'reservations.confirmed': 'Potwierdzona', + 'reservations.pending': 'Oczekująca', + 'reservations.summary': '{confirmed} potwierdzonych, {pending} oczekujących', + 'reservations.fromPlan': 'Z planu', + 'reservations.showFiles': 'Pokaż pliki', + 'reservations.editTitle': 'Edytuj rezerwację', + 'reservations.status': 'Status', + 'reservations.datetime': 'Data i czas', + 'reservations.startTime': 'Godzina rozpoczęcia', + 'reservations.endTime': 'Godzina zakończenia', + 'reservations.date': 'Data', + 'reservations.time': 'Godzina', + 'reservations.timeAlt': 'Godzina (alternatywna, np. 19:30)', + 'reservations.notes': 'Notatki', + 'reservations.notesPlaceholder': 'Dodatkowe notatki...', + 'reservations.meta.airline': 'Linia lotnicza', + 'reservations.meta.flightNumber': 'Numer lotu', + 'reservations.meta.from': 'Skąd', + 'reservations.meta.to': 'Dokąd', + 'reservations.meta.trainNumber': 'Numer pociągu', + 'reservations.meta.platform': 'Peron', + 'reservations.meta.seat': 'Miejsce', + 'reservations.meta.checkIn': 'Zameldowanie', + 'reservations.meta.checkInUntil': 'Check-in do', + 'reservations.meta.checkOut': 'Wymeldowanie', + 'reservations.meta.linkAccommodation': 'Zakwaterowanie', + 'reservations.meta.pickAccommodation': 'Link do zakwaterowania', + 'reservations.meta.noAccommodation': 'Brak', + 'reservations.meta.hotelPlace': 'Zakwaterowanie', + 'reservations.meta.pickHotel': 'Wybierz zakwaterowanie', + 'reservations.meta.fromDay': 'Od', + 'reservations.meta.toDay': 'Do', + 'reservations.meta.selectDay': 'Wybierz dzień', + 'reservations.type.flight': 'Lot', + 'reservations.type.hotel': 'Zakwaterowanie', + 'reservations.type.restaurant': 'Restauracja', + 'reservations.type.train': 'Pociąg', + 'reservations.type.car': 'Samochód', + 'reservations.needsReview': 'Sprawdź', + 'reservations.needsReviewHint': + 'Nie udało się automatycznie dopasować lotniska — potwierdź lokalizację.', + 'reservations.searchLocation': 'Szukaj stacji, portu, adresu...', + 'reservations.type.cruise': 'Rejs', + 'reservations.type.event': 'Wydarzenie', + 'reservations.type.tour': 'Wycieczka', + 'reservations.type.other': 'Inne', + 'reservations.confirm.delete': + 'Czy na pewno chcesz usunąć rezerwację "{name}"?', + 'reservations.confirm.deleteTitle': 'Usunąć rezerwację?', + 'reservations.confirm.deleteBody': + 'Rezerwacja "{name}" zostanie trwale usunięta.', + 'reservations.toast.updated': 'Rezerwacja została zaktualizowana', + 'reservations.toast.removed': 'Rezerwacja została usunięta', + 'reservations.toast.fileUploaded': 'Plik został przesłany', + 'reservations.toast.uploadError': 'Nie udało się przesłać pliku', + 'reservations.newTitle': 'Nowa rezerwacja', + 'reservations.bookingType': 'Rodzaj rezerwacji', + 'reservations.titleLabel': 'Tytuł', + 'reservations.titlePlaceholder': 'np. Ryanair FR123, Hotel Dubaj, ...', + 'reservations.locationAddress': 'Lokalizacja / Adres', + 'reservations.locationPlaceholder': 'Adres, Lotnisko, Hotel...', + 'reservations.confirmationCode': 'Kod rezerwacji', + 'reservations.confirmationPlaceholder': 'np. ABC12345', + 'reservations.day': 'Dzień', + 'reservations.noDay': 'Brak dnia', + 'reservations.place': 'Miejsce', + 'reservations.noPlace': 'Brak miejsca', + 'reservations.pendingSave': 'zostanie zapisane...', + 'reservations.uploading': 'Przesyłanie...', + 'reservations.attachFile': 'Załącz plik', + 'reservations.linkExisting': 'Podlinkuj przesłany plik', + 'reservations.toast.saveError': 'Nie udało się zapisać', + 'reservations.toast.updateError': 'Nie udało się zaktualizować', + 'reservations.toast.deleteError': 'Nie udało się usunąć', + 'reservations.confirm.remove': 'Usunąć rezerwację "{name}"?', + 'reservations.linkAssignment': 'Przypisz do miejsca', + 'reservations.pickAssignment': 'Wybierz miejsce z planu...', + 'reservations.noAssignment': 'Brak przypisania (samodzielna)', + 'reservations.price': 'Cena', + 'reservations.budgetCategory': 'Kategoria budżetu', + 'reservations.budgetCategoryPlaceholder': 'np. Transport, Zakwaterowanie', + 'reservations.budgetCategoryAuto': 'Auto (na podstawie typu rezerwacji)', + 'reservations.budgetHint': + 'Wpis budżetowy zostanie automatycznie utworzony podczas zapisywania.', + 'reservations.departureDate': 'Wylot', + 'reservations.arrivalDate': 'Przylot', + 'reservations.departureTime': 'Godz. wylotu', + 'reservations.arrivalTime': 'Godz. przylotu', + 'reservations.pickupDate': 'Odbiór', + 'reservations.returnDate': 'Zwrot', + 'reservations.pickupTime': 'Godz. odbioru', + 'reservations.returnTime': 'Godz. zwrotu', + 'reservations.endDate': 'Data końca', + 'reservations.meta.departureTimezone': 'TZ wylotu', + 'reservations.meta.arrivalTimezone': 'TZ przylotu', + 'reservations.span.departure': 'Wylot', + 'reservations.span.arrival': 'Przylot', + 'reservations.span.inTransit': 'W tranzycie', + 'reservations.span.pickup': 'Odbiór', + 'reservations.span.return': 'Zwrot', + 'reservations.span.active': 'Aktywny', + 'reservations.span.start': 'Start', + 'reservations.span.end': 'Koniec', + 'reservations.span.ongoing': 'W trakcie', + 'reservations.validation.endBeforeStart': + 'Data/godzina zakończenia musi być późniejsza niż data/godzina rozpoczęcia', + 'reservations.addBooking': 'Dodaj rezerwację', +}; +export default reservations; diff --git a/shared/src/i18n/pl/settings.ts b/shared/src/i18n/pl/settings.ts new file mode 100644 index 00000000..8b1c49b1 --- /dev/null +++ b/shared/src/i18n/pl/settings.ts @@ -0,0 +1,300 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Ustawienia', + 'settings.subtitle': 'Skonfiguruj swoje ustawienia', + 'settings.tabs.display': 'Wygląd', + 'settings.tabs.map': 'Mapa', + 'settings.tabs.notifications': 'Powiadomienia', + 'settings.tabs.integrations': 'Integracje', + 'settings.tabs.account': 'Konto', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'O aplikacji', + 'settings.map': 'Mapa', + 'settings.mapTemplate': 'Szablon mapy', + 'settings.mapTemplatePlaceholder.select': 'Wybierz szablon...', + 'settings.mapDefaultHint': 'Pozostaw puste dla OpenStreetMap (domyślnie)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'Szablon URL dla kafelków mapy', + 'settings.mapProvider': 'Dostawca mapy', + 'settings.mapProviderHint': + 'Dotyczy map Trip Planner i Journey. Atlas zawsze używa Leaflet.', + 'settings.mapLeafletSubtitle': 'Klasyczne 2D, dowolne kafelki rastrowe', + 'settings.mapMapboxSubtitle': 'Kafelki wektorowe, budynki 3D i teren', + 'settings.mapExperimental': 'Eksperymentalne', + 'settings.mapMapboxToken': 'Token dostępu Mapbox', + 'settings.mapMapboxTokenHint': 'Token publiczny (pk.*) z', + 'settings.mapMapboxTokenLink': 'mapbox.com → Tokeny dostępu', + 'settings.mapStyle': 'Styl mapy', + 'settings.mapStylePlaceholder': 'Wybierz styl Mapbox', + 'settings.mapStyleHint': 'Preset lub własny URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': 'Budynki 3D i teren', + 'settings.map3dHint': + 'Nachylenie + prawdziwe wytłaczanie budynków 3D — działa w każdym stylu, także satelitarnym.', + 'settings.mapHighQuality': 'Tryb wysokiej jakości', + 'settings.mapHighQualityHint': + 'Antialiasing + projekcja globusa dla ostrzejszych krawędzi i realistycznego widoku świata.', + 'settings.mapHighQualityWarning': + 'Może wpływać na wydajność na słabszych urządzeniach.', + 'settings.mapTipLabel': 'Wskazówka:', + 'settings.mapTip': + 'Kliknij prawym przyciskiem i przeciągnij, aby obrócić/pochylić mapę. Środkowy przycisk dodaje miejsce (prawy jest zarezerwowany dla obrotu).', + 'settings.latitude': 'Szerokość', + 'settings.longitude': 'Długość', + 'settings.saveMap': 'Zapisz mapę', + 'settings.apiKeys': 'Klucze API', + 'settings.mapsKey': 'Klucz Google Maps API', + 'settings.mapsKeyHint': + 'Do wyszukiwania miejsc. Wymaga Places API (New). Uzyskaj dostęp na console.cloud.google.com', + 'settings.weatherKey': 'Klucz OpenWeatherMap API', + 'settings.weatherKeyHint': + 'Do danych pogodowych. Bezpłatnie na openweathermap.org/api', + 'settings.keyPlaceholder': 'Podaj klucz...', + 'settings.configured': 'Skonfigurowano', + 'settings.saveKeys': 'Zapisz klucze', + 'settings.display': 'Preferencje', + 'settings.colorMode': 'Motyw', + 'settings.light': 'Jasny', + 'settings.dark': 'Ciemny', + 'settings.auto': 'Automatyczny', + 'settings.language': 'Język', + 'settings.temperature': 'Jednostka temperatury', + 'settings.timeFormat': 'Format czasu', + 'settings.blurBookingCodes': 'Rozmyj kody rezerwacji', + 'settings.notifications': 'Powiadomienia', + 'settings.notifyTripInvite': 'Zaproszenia do podróży', + 'settings.notifyBookingChange': 'Zmiany w rezerwacjach', + 'settings.notifyTripReminder': 'Przypomnienia o podróżach', + 'settings.notifyTodoDue': 'Zadanie z terminem', + 'settings.notifyVacayInvite': 'Zaproszenia do połączenia kalendarzy', + 'settings.notifyPhotosShared': 'Udostępnione zdjęcia (Immich)', + 'settings.notifyCollabMessage': 'Wiadomości czatu (Collab)', + 'settings.notifyPackingTagged': 'Lista pakowania: przypisania', + 'settings.notifyWebhook': 'Powiadomienia Webhook', + 'settings.notifyVersionAvailable': 'Nowa wersja dostępna', + 'settings.on': 'Włączone', + 'settings.off': 'Wyłączone', + 'settings.mcp.title': 'Konfiguracja MCP', + 'settings.mcp.endpoint': 'Endpoint MCP', + 'settings.mcp.clientConfig': 'Konfiguracja klienta', + 'settings.mcp.clientConfigHint': + 'Zastąp tokenem API z listy poniżej. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Zastąp i danymi uwierzytelniającymi z klienta OAuth 2.1 utworzonego powyżej. mcp-remote otworzy przeglądarkę, aby dokończyć autoryzację przy pierwszym połączeniu. Ścieżka do npx może wymagać dostosowania do Twojego systemu (np. C:\\PROGRA~1\\nodejs\\npx.cmd w systemie Windows).', + 'settings.mcp.copy': 'Kopiuj', + 'settings.mcp.copied': 'Skopiowano!', + 'settings.mcp.apiTokens': 'Tokeny API', + 'settings.mcp.createToken': 'Utwórz nowy token', + 'settings.mcp.noTokens': + 'Brak tokenów. Utwórz go, aby połączyć klientów MCP.', + 'settings.mcp.tokenCreatedAt': 'Utworzono', + 'settings.mcp.tokenUsedAt': 'Użyto', + 'settings.mcp.deleteTokenTitle': 'Usuń token', + 'settings.mcp.deleteTokenMessage': + 'Ten token przestanie działać natychmiastowo. Każdy klient MCP używający go straci dostęp.', + 'settings.mcp.modal.createTitle': 'Utwórz token API', + 'settings.mcp.modal.tokenName': 'Nazwa tokenu', + 'settings.mcp.modal.tokenNamePlaceholder': + 'np. Claude Desktop, Laptop służbowy', + 'settings.mcp.modal.creating': 'Tworzenie...', + 'settings.mcp.modal.create': 'Utwórz token', + 'settings.mcp.modal.createdTitle': 'Token został utworzony', + 'settings.mcp.modal.createdWarning': + 'Ten token zostanie wyświetlony tylko raz. Skopiuj i zapisz go — nie będzie można go zobaczyć ponownie.', + 'settings.mcp.modal.done': 'Gotowe', + 'settings.mcp.toast.created': 'Token został utworzony', + 'settings.mcp.toast.createError': 'Nie udało się utworzyć tokenu', + 'settings.mcp.toast.deleted': 'Token został usunięty', + 'settings.mcp.toast.deleteError': 'Nie udało się usunąć tokenu', + 'settings.mcp.apiTokensDeprecated': + 'Tokeny API są przestarzałe i zostaną usunięte w przyszłej wersji. Użyj zamiast tego klientów OAuth 2.1.', + 'settings.oauth.clients': 'Klienci OAuth 2.1', + 'settings.oauth.clientsHint': + 'Zarejestruj klientów OAuth 2.1, aby zewnętrzne aplikacje MCP (Claude Web, Cursor itp.) mogły się łączyć bez statycznych tokenów.', + 'settings.oauth.createClient': 'Nowy klient', + 'settings.oauth.noClients': 'Brak zarejestrowanych klientów OAuth.', + 'settings.oauth.clientId': 'ID klienta', + 'settings.oauth.clientSecret': 'Sekret klienta', + 'settings.oauth.deleteClient': 'Usuń klienta', + 'settings.oauth.deleteClientMessage': + 'Ten klient i wszystkie aktywne sesje zostaną trwale usunięte. Każda aplikacja, która go używa, natychmiast utraci dostęp.', + 'settings.oauth.rotateSecret': 'Odnów sekret', + 'settings.oauth.rotateSecretMessage': + 'Zostanie wygenerowany nowy sekret klienta, a wszystkie istniejące sesje zostaną natychmiast unieważnione. Zaktualizuj aplikację przed zamknięciem tego okna.', + 'settings.oauth.rotateSecretConfirm': 'Odnów', + 'settings.oauth.rotateSecretConfirming': 'Odnawianie…', + 'settings.oauth.rotateSecretDoneTitle': 'Wygenerowano nowy sekret', + 'settings.oauth.rotateSecretDoneWarning': + 'Ten sekret jest wyświetlany tylko raz. Skopiuj go teraz i zaktualizuj aplikację — wszystkie poprzednie sesje zostały unieważnione.', + 'settings.oauth.activeSessions': 'Aktywne sesje OAuth', + 'settings.oauth.sessionScopes': 'Uprawnienia', + 'settings.oauth.sessionExpires': 'Wygasa', + 'settings.oauth.revoke': 'Unieważnij', + 'settings.oauth.revokeSession': 'Unieważnij sesję', + 'settings.oauth.revokeSessionMessage': + 'Spowoduje to natychmiastowe unieważnienie dostępu dla tej sesji OAuth.', + 'settings.oauth.modal.createTitle': 'Zarejestruj klienta OAuth', + 'settings.oauth.modal.presets': 'Szybkie ustawienia', + 'settings.oauth.modal.clientName': 'Nazwa aplikacji', + 'settings.oauth.modal.clientNamePlaceholder': + 'np. Claude Web, Moja aplikacja MCP', + 'settings.oauth.modal.redirectUris': 'URI przekierowania', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Jeden URI na linię. Wymagane HTTPS (localhost zwolniony). Wymagana dokładna zgodność.', + 'settings.oauth.modal.scopes': 'Dozwolone uprawnienia', + 'settings.oauth.modal.scopesHint': + 'list_trips i get_trip_summary są zawsze dostępne — bez wymaganych uprawnień. Umożliwiają AI odkrycie potrzebnych ID podróży.', + 'settings.oauth.modal.selectAll': 'Zaznacz wszystko', + 'settings.oauth.modal.deselectAll': 'Odznacz wszystko', + 'settings.oauth.modal.creating': 'Rejestrowanie…', + 'settings.oauth.modal.create': 'Zarejestruj klienta', + 'settings.oauth.modal.createdTitle': 'Klient zarejestrowany', + 'settings.oauth.modal.createdWarning': + 'Sekret klienta jest wyświetlany tylko raz. Skopiuj go teraz — nie można go odzyskać.', + 'settings.oauth.toast.createError': + 'Nie udało się zarejestrować klienta OAuth', + 'settings.oauth.toast.deleted': 'Klient OAuth usunięty', + 'settings.oauth.toast.deleteError': 'Nie udało się usunąć klienta OAuth', + 'settings.oauth.toast.revoked': 'Sesja unieważniona', + 'settings.oauth.toast.revokeError': 'Nie udało się unieważnić sesji', + 'settings.oauth.toast.rotateError': 'Nie udało się odnowić sekretu klienta', + 'settings.oauth.modal.machineClient': + 'Klient maszynowy (bez logowania przez przeglądarkę)', + 'settings.oauth.modal.machineClientHint': + 'Używa grantu client_credentials — nie są potrzebne URI przekierowania. Token jest wystawiany bezpośrednio przez client_id + client_secret i działa w Twoim imieniu w ramach wybranych zakresów.', + 'settings.oauth.modal.machineClientUsage': + 'Pobierz token: POST /oauth/token z grant_type=client_credentials, client_id i client_secret. Bez przeglądarki, bez tokenu odświeżania.', + 'settings.oauth.badge.machine': 'maszynowy', + 'settings.account': 'Konto', + 'settings.about': 'O aplikacji', + 'settings.about.reportBug': 'Zgłoś błąd', + 'settings.about.reportBugHint': 'Znalazłeś problem? Daj nam znać', + 'settings.about.featureRequest': 'Zaproponuj funkcję', + 'settings.about.featureRequestHint': 'Zaproponuj nową funkcję', + 'settings.about.wikiHint': 'Dokumentacja i poradniki', + 'settings.about.supporters.badge': 'Miesięczni Patroni', + 'settings.about.supporters.title': 'Towarzystwo podróży dla TREK', + 'settings.about.supporters.subtitle': + 'Gdy planujesz kolejną trasę, te osoby planują razem ze mną przyszłość TREK. Ich comiesięczny wkład idzie bezpośrednio na rozwój i realnie przepracowane godziny — aby TREK pozostał Open Source.', + 'settings.about.supporters.since': 'patron od {date}', + 'settings.about.supporters.tierEmpty': 'Bądź pierwszy', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK to samodzielnie hostowany planer podróży, który pomaga organizować wyprawy od pierwszego pomysłu po ostatnie wspomnienie. Planowanie dzienne, budżet, listy pakowania, zdjęcia i wiele więcej — wszystko w jednym miejscu, na własnym serwerze.', + 'settings.about.madeWith': 'Stworzone z', + 'settings.about.madeBy': "przez Maurice'a i rosnącą społeczność open-source.", + 'settings.username': 'Nazwa użytkownika', + 'settings.email': 'E-mail', + 'settings.role': 'Rola', + 'settings.roleAdmin': 'Administrator', + 'settings.oidcLinked': 'Połączono z', + 'settings.changePassword': 'Zmień hasło', + 'settings.currentPassword': 'Aktualne hasło', + 'settings.currentPasswordRequired': 'Aktualne hasło jest wymagane', + 'settings.newPassword': 'Nowe hasło', + 'settings.confirmPassword': 'Potwierdź nowe hasło', + 'settings.updatePassword': 'Zaktualizuj hasło', + 'settings.passwordRequired': 'Proszę podać aktualne i nowe hasło', + 'settings.passwordTooShort': 'Hasło musi mieć co najmniej 8 znaków', + 'settings.passwordMismatch': 'Hasła nie są identyczne', + 'settings.passwordWeak': + 'Hasło musi zawierać wielką literę, małą literę i cyfrę', + 'settings.passwordChanged': 'Hasło zostało zmienione pomyślnie', + 'settings.deleteAccount': 'Usuń konto', + 'settings.deleteAccountTitle': 'Usunąć twoje konto?', + 'settings.deleteAccountWarning': + 'Twoje konto i wszystkie twoje podróże, miejsca i pliki zostaną trwale usunięte. Tej akcji nie można cofnąć.', + 'settings.deleteAccountConfirm': 'Usuń na zawsze', + 'settings.deleteBlockedTitle': 'Nie można usunąć konta', + 'settings.deleteBlockedMessage': + 'Jesteś jedynym administratorem. Wyznacz innego użytkownika na administratora przed usunięciem konta.', + 'settings.roleUser': 'Użytkownik', + 'settings.saveProfile': 'Zapisz profil', + 'settings.toast.mapSaved': 'Ustawienia mapy zostały zapisane', + 'settings.toast.keysSaved': 'Klucze API zostały zapisane', + 'settings.toast.displaySaved': 'Preferencje zostały zapisane', + 'settings.toast.profileSaved': 'Profil został zapisany', + 'settings.uploadAvatar': 'Prześlij zdjęcie profilowe', + 'settings.removeAvatar': 'Usuń zdjęcie profilowe', + 'settings.avatarUploaded': 'Zdjęcie profilowe zostało zaktualizowane', + 'settings.avatarRemoved': 'Zdjęcie profilowe zostało usunięte', + 'settings.avatarError': 'Przesyłanie nie powiodło się', + 'settings.mfa.title': 'Uwierzytelnianie dwuskładnikowe (2FA)', + 'settings.mfa.description': + 'Dodaje drugi krok, kiedy logujesz się e-mailem i hasłem. Użyj aplikacji uwierzytelniającej (Google Authenticator, Authy, itp.).', + 'settings.mfa.requiredByPolicy': + 'Twój administrator wymaga uwierzytelniania dwuskładnikowego. Skonfiguruj aplikację uwierzytelniającą poniżej, zanim przejdziesz dalej.', + 'settings.mfa.backupTitle': 'Kody zapasowe', + 'settings.mfa.backupDescription': + 'Użyj tych jednorazowych kodów zapasowych, jeżeli stracisz dostęp do swojej aplikacji uwierzytelniającej.', + 'settings.mfa.backupWarning': + 'Zapisz te kody. Każdy z nich może być wykorzystany tylko raz.', + 'settings.mfa.backupCopy': 'Kopiuj kody', + 'settings.mfa.backupDownload': 'Pobierz TXT', + 'settings.mfa.backupPrint': 'Drukuj / PDF', + 'settings.mfa.backupCopied': 'Kody zapasowe zostały skopiowane', + 'settings.mfa.enabled': '2FA jest włączone dla Twojego konta.', + 'settings.mfa.disabled': '2FA jest wyłączone.', + 'settings.mfa.setup': 'Skonfiguruj aplikację uwierzytelniającą', + 'settings.mfa.scanQr': + 'Zeskanuj ten kod QR za pomocą aplikacji lub wprowadź klucz ręcznie.', + 'settings.mfa.secretLabel': 'Tajny klucz (wprowadź ręcznie)', + 'settings.mfa.codePlaceholder': '6-cyfrowy kod', + 'settings.mfa.enable': 'Włącz 2FA', + 'settings.mfa.cancelSetup': 'Anuluj', + 'settings.mfa.disableTitle': 'Wyłącz 2FA', + 'settings.mfa.disableHint': + 'Podaj hasło do konta i aktualny kod z aplikacji uwierzytelniającej.', + 'settings.mfa.disable': 'Wyłącz 2FA', + 'settings.mfa.toastEnabled': + 'Uwierzytelnianie dwuskładnikowe zostało włączone', + 'settings.mfa.toastDisabled': + 'Uwierzytelnianie dwuskładnikowe zostało wyłączone', + 'settings.mfa.demoBlocked': 'Niedostępne w trybie demonstracyjnym', + 'settings.bookingLabels': 'Etykiety tras rezerwacji', + 'settings.bookingLabelsHint': + 'Pokazuje nazwy stacji / lotnisk na mapie. Gdy wyłączone, wyświetlana jest tylko ikona.', + 'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.', + 'settings.notificationPreferences.noChannels': + 'Brak skonfigurowanych kanałów powiadomień. Poproś administratora o skonfigurowanie powiadomień e-mail lub webhook.', + 'settings.webhookUrl.label': 'URL webhooka', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.', + 'settings.webhookUrl.saved': 'URL webhooka zapisany', + 'settings.webhookUrl.test': 'Testuj', + 'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie', + 'settings.webhookUrl.testFailed': + 'Wysyłanie testowego webhooka nie powiodło się', + 'settings.ntfyUrl.topicLabel': 'Temat Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL serwera Ntfy (opcjonalne)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Wprowadź swój temat Ntfy, aby otrzymywać powiadomienia push. Pozostaw pole serwera puste, aby użyć domyślnego ustawienia skonfigurowanego przez administratora.', + 'settings.ntfyUrl.tokenLabel': 'Token dostępu (opcjonalne)', + 'settings.ntfyUrl.tokenHint': 'Wymagane dla tematów chronionych hasłem.', + 'settings.ntfyUrl.saved': 'Ustawienia Ntfy zapisane', + 'settings.ntfyUrl.test': 'Testuj', + 'settings.ntfyUrl.testSuccess': + 'Testowe powiadomienie Ntfy wysłane pomyślnie', + 'settings.ntfyUrl.testFailed': 'Testowe powiadomienie Ntfy nie powiodło się', + 'settings.ntfyUrl.tokenCleared': 'Token dostępu wyczyszczony', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationsActive': 'Aktywny kanał', + 'settings.notificationsManagedByAdmin': + 'Zdarzenia konfigurowane przez administratora.', + 'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.', +}; +export default settings; diff --git a/shared/src/i18n/pl/share.ts b/shared/src/i18n/pl/share.ts new file mode 100644 index 00000000..33ec51c3 --- /dev/null +++ b/shared/src/i18n/pl/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Publiczny link', + 'share.linkHint': + 'Utwórz link umożliwiający przeglądanie tej podróży bez logowania. Tylko do odczytu — bez możliwości edycji.', + 'share.createLink': 'Utwórz link', + 'share.deleteLink': 'Usuń link', + 'share.createError': 'Nie udało się utworzyć linku', + 'share.permMap': 'Mapa i plan', + 'share.permBookings': 'Rezerwacje', + 'share.permPacking': 'Lista pakowania', + 'share.permBudget': 'Budżet', + 'share.permCollab': 'Czat', +}; +export default share; diff --git a/shared/src/i18n/pl/shared.ts b/shared/src/i18n/pl/shared.ts new file mode 100644 index 00000000..69b577b2 --- /dev/null +++ b/shared/src/i18n/pl/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link wygasł lub jest nieprawidłowy', + 'shared.expiredHint': 'Ten link do podróży jest już nieaktywny.', + 'shared.readOnly': 'Widok udostępniony tylko do odczytu', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Rezerwacje', + 'shared.tabPacking': 'Lista pakowania', + 'shared.tabBudget': 'Budżet', + 'shared.tabChat': 'Czat', + 'shared.days': 'dni', + 'shared.places': 'miejsca', + 'shared.other': 'Inne', + 'shared.totalBudget': 'Całkowity budżet', + 'shared.messages': 'wiadomości', + 'shared.sharedVia': 'Udostępnione przez', + 'shared.confirmed': 'Potwierdzone', + 'shared.pending': 'Oczekujące', +}; +export default shared; diff --git a/shared/src/i18n/pl/stats.ts b/shared/src/i18n/pl/stats.ts new file mode 100644 index 00000000..55a72bdd --- /dev/null +++ b/shared/src/i18n/pl/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Kraje', + 'stats.cities': 'Miasta', + 'stats.trips': 'Podróże', + 'stats.places': 'Miejsca', + 'stats.worldProgress': 'Postęp', + 'stats.visited': 'odwiedzone', + 'stats.remaining': 'pozostałe', + 'stats.visitedCountries': 'Odwiedzone kraje', +}; +export default stats; diff --git a/shared/src/i18n/pl/system_notice.ts b/shared/src/i18n/pl/system_notice.ts new file mode 100644 index 00000000..14bc23bb --- /dev/null +++ b/shared/src/i18n/pl/system_notice.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Witaj w TREK', + 'system_notice.welcome_v1.body': + 'Twój kompleksowy planer podróży. Twórz trasy, dziel się wycieczkami ze znajomymi i bądź zorganizowany — online i offline.', + 'system_notice.welcome_v1.cta_label': 'Zaplanuj podróż', + 'system_notice.welcome_v1.hero_alt': + 'Malownicze miejsce z interfejsem planowania TREK', + 'system_notice.welcome_v1.highlight_plan': 'Trasy dzień po dniu', + 'system_notice.welcome_v1.highlight_share': 'Współpraca z partnerami podróży', + 'system_notice.welcome_v1.highlight_offline': 'Działa offline na telefonie', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Poprzednie powiadomienie', + 'system_notice.pager.next': 'Następne powiadomienie', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Przejdź do powiadomienia {n}', + 'system_notice.pager.position': 'Powiadomienie {current} z {total}', + 'system_notice.v3_photos.title': 'Zdjęcia zostały przeniesione w 3.0', + 'system_notice.v3_photos.body': + '**Zdjęcia** w Planerze Podróży zostały usunięte. Twoje zdjęcia są bezpieczne — TREK nigdy nie modyfikował Twojej biblioteki Immich lub Synology.\n\nZdjęcia są teraz dostępne w dodatku **Journey**. Journey jest opcjonalny — jeśli jeszcze nie jest dostępny, poproś administratora o jego włączenie w Admin → Dodatki.', + 'system_notice.v3_journey.title': 'Poznaj Journey — dziennik podróży', + 'system_notice.v3_journey.body': + 'Dokumentuj swoje podróże jako bogatrze opowieści z osami czasu, galeriami i mapami interaktywnymi.', + 'system_notice.v3_journey.cta_label': 'Otwórz Journey', + 'system_notice.v3_journey.highlight_timeline': 'Dzienna oś czasu i galeria', + 'system_notice.v3_journey.highlight_photos': 'Import z Immich lub Synology', + 'system_notice.v3_journey.highlight_share': + 'Udostępnij publicznie — bez logowania', + 'system_notice.v3_journey.highlight_export': + 'Eksportuj jako książkę fotograficzną PDF', + 'system_notice.v3_features.title': 'Więcej nowości w 3.0', + 'system_notice.v3_features.body': + 'Kilka innych rzeczy wartych uwagi w tym wydaniu.', + 'system_notice.v3_features.highlight_dashboard': + 'Przeprojektowany pulpit mobile-first', + 'system_notice.v3_features.highlight_offline': 'Pełny tryb offline jako PWA', + 'system_notice.v3_features.highlight_search': + 'Autouzupełnianie wyszukiwania miejsc', + 'system_notice.v3_features.highlight_import': + 'Import miejsc z plików KMZ/KML', + 'system_notice.v3_mcp.title': 'MCP: aktualizacja OAuth 2.1', + 'system_notice.v3_mcp.body': + 'Integracja MCP została całkowicie przeprojektowana. OAuth 2.1 jest teraz zalecaną metodą uwierzytelniania. Statyczne tokeny (trek_…) są przestarzałe i zostaną usunięte w przyszłej wersji.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 zalecany (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 szczegółowe zakresy uprawnień', + 'system_notice.v3_mcp.highlight_deprecated': + 'Statyczne tokeny trek_ przestarzałe', + 'system_notice.v3_mcp.highlight_tools': + 'Rozszerzony zestaw narzędzi i promptów', + 'system_notice.v3_thankyou.title': 'Osobiste słowo ode mnie', + 'system_notice.v3_thankyou.body': + 'Zanim pójdziesz dalej — chcę się na chwilę zatrzymać.\n\nTREK zaczął się jako poboczny projekt, który zbudowałem na własne podróże. Nigdy nie wyobrażałem sobie, że wyrośnie na coś, czemu 4000 z was ufa przy planowaniu swoich przygód. Każda gwiazdka, każdy issue, każda prośba o funkcję — czytam je wszystkie i to one trzymają mnie na nogach podczas późnych nocy między pracą na pełny etat a uczelnią.\n\nChcę, żebyście wiedzieli: TREK zawsze będzie open source, zawsze self-hosted, zawsze wasz. Bez śledzenia, bez subskrypcji, bez haczyków. Po prostu narzędzie zbudowane przez kogoś, kto kocha podróżowanie tak samo jak wy.\n\nSzczególne podziękowania dla [jubnl](https://github.com/jubnl) — stałeś się niesamowitym współpracownikiem. Tak wiele z tego, co czyni wersję 3.0 wspaniałą, nosi twój ślad. Dziękuję, że uwierzyłeś w ten projekt, gdy był jeszcze surowy.\n\nI każdemu z was, kto zgłosił błąd, przetłumaczył tekst, podzielił się TREK z przyjacielem lub po prostu użył go do zaplanowania podróży — **dziękuję**. To wy jesteście powodem, dla którego to istnieje.\n\nZa wiele kolejnych wspólnych przygód.\n\n— Maurice\n\n---\n\n[Dołącz do społeczności na Discordzie](https://discord.gg/7Q6M6jDwzf)\n\nJeśli TREK sprawia, że Twoje podróże są lepsze, [mała kawa](https://ko-fi.com/mauriceboe) zawsze pomaga utrzymać światła włączone.', + 'system_notice.v3014_whitespace_collision.title': + 'Wymagane działanie: konflikt konta użytkownika', + 'system_notice.v3014_whitespace_collision.body': + 'Aktualizacja 3.0.14 wykryła jeden lub więcej konfliktów nazwy użytkownika lub adresu e-mail spowodowanych spacjami na początku lub końcu przechowywanych wartości. Dotknięte konta zostały automatycznie przemianowane. Sprawdź logi serwera pod kątem wierszy zaczynających się od **[migration] WHITESPACE COLLISION**, aby zidentyfikować konta wymagające przeglądu.', +}; +export default system_notice; diff --git a/shared/src/i18n/pl/todo.ts b/shared/src/i18n/pl/todo.ts new file mode 100644 index 00000000..e655895b --- /dev/null +++ b/shared/src/i18n/pl/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Lista pakowania', + 'todo.subtab.todo': 'Do zrobienia', + 'todo.completed': 'ukończono', + 'todo.filter.all': 'Wszystkie', + 'todo.filter.open': 'Otwarte', + 'todo.filter.done': 'Gotowe', + 'todo.uncategorized': 'Bez kategorii', + 'todo.namePlaceholder': 'Nazwa zadania', + 'todo.descriptionPlaceholder': 'Opis (opcjonalnie)', + 'todo.unassigned': 'Nieprzypisane', + 'todo.noCategory': 'Brak kategorii', + 'todo.hasDescription': 'Ma opis', + 'todo.addItem': 'Nowe zadanie', + 'todo.sidebar.sortBy': 'Sortuj wg', + 'todo.priority': 'Priorytet', + 'todo.newCategoryLabel': 'nowa', + 'todo.newCategory': 'Nazwa kategorii', + 'todo.addCategory': 'Dodaj kategorię', + 'todo.newItem': 'Nowe zadanie', + 'todo.empty': 'Brak zadań. Dodaj zadanie, aby zacząć!', + 'todo.filter.my': 'Moje zadania', + 'todo.filter.overdue': 'Przeterminowane', + 'todo.sidebar.tasks': 'Zadania', + 'todo.sidebar.categories': 'Kategorie', + 'todo.detail.title': 'Zadanie', + 'todo.detail.description': 'Opis', + 'todo.detail.category': 'Kategoria', + 'todo.detail.dueDate': 'Termin', + 'todo.detail.assignedTo': 'Przypisano do', + 'todo.detail.delete': 'Usuń', + 'todo.detail.save': 'Zapisz zmiany', + 'todo.detail.create': 'Utwórz zadanie', + 'todo.detail.priority': 'Priorytet', + 'todo.detail.noPriority': 'Brak', + 'todo.sortByPrio': 'Priorytet', +}; +export default todo; diff --git a/shared/src/i18n/pl/transport.ts b/shared/src/i18n/pl/transport.ts new file mode 100644 index 00000000..208702ca --- /dev/null +++ b/shared/src/i18n/pl/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Dodaj transport', + 'transport.modalTitle.create': 'Dodaj transport', + 'transport.modalTitle.edit': 'Edytuj transport', + 'transport.title': 'Transport', + 'transport.addManual': 'Ręczny transport', +}; +export default transport; diff --git a/shared/src/i18n/pl/trip.ts b/shared/src/i18n/pl/trip.ts new file mode 100644 index 00000000..c8fc07c0 --- /dev/null +++ b/shared/src/i18n/pl/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transport', + 'trip.tabs.reservations': 'Rezerwacje', + 'trip.tabs.reservationsShort': 'Rezerwacje', + 'trip.tabs.packing': 'Lista pakowania', + 'trip.tabs.packingShort': 'Pakowanie', + 'trip.tabs.lists': 'Listy', + 'trip.tabs.listsShort': 'Listy', + 'trip.tabs.budget': 'Budżet', + 'trip.tabs.files': 'Pliki', + 'trip.loading': 'Ładowanie podróży...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Miejsca', + 'trip.toast.placeUpdated': 'Miejsce zostało zaktualizowane', + 'trip.toast.placeAdded': 'Miejsce zostało dodane', + 'trip.toast.placeDeleted': 'Miejsce zostało usunięte', + 'trip.toast.selectDay': 'Proszę najpierw wybrać dzień', + 'trip.toast.assignedToDay': 'Miejsce przypisane do dnia', + 'trip.toast.reorderError': 'Nie udało się zmienić kolejności', + 'trip.toast.reservationUpdated': 'Rezerwacja została zaktualizowana', + 'trip.toast.reservationAdded': 'Rezerwacja została dodana', + 'trip.toast.deleted': 'Usunięto', + 'trip.confirm.deletePlace': 'Czy na pewno chcesz usunąć to miejsce?', + 'trip.confirm.deletePlaces': 'Usunąć {count} miejsc?', + 'trip.toast.placesDeleted': '{count} miejsc usunięto', + 'trip.loadingPhotos': 'Ładowanie zdjęć...', +}; +export default trip; diff --git a/shared/src/i18n/pl/trips.ts b/shared/src/i18n/pl/trips.ts new file mode 100644 index 00000000..ef0701fa --- /dev/null +++ b/shared/src/i18n/pl/trips.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} usunięty', + 'trips.memberRemoveError': 'Nie udało się usunąć', + 'trips.memberAdded': '{username} dodany', + 'trips.memberAddError': 'Nie udało się dodać', + 'trips.reminder': 'Przypomnienie', + 'trips.reminderNone': 'Brak', + 'trips.reminderDay': 'dzień', + 'trips.reminderDays': 'dni', + 'trips.reminderCustom': 'Niestandardowe', + 'trips.reminderDaysBefore': 'dni przed wyjazdem', + 'trips.reminderDisabledHint': 'Przypomnienia o podróżach są wyłączone.', +}; +export default trips; diff --git a/shared/src/i18n/pl/undo.ts b/shared/src/i18n/pl/undo.ts new file mode 100644 index 00000000..5b968a03 --- /dev/null +++ b/shared/src/i18n/pl/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Cofnij', + 'undo.tooltip': 'Cofnij: {action}', + 'undo.assignPlace': 'Miejsce przypisane do dnia', + 'undo.removeAssignment': 'Miejsce usunięte z dnia', + 'undo.reorder': 'Kolejność zmieniona', + 'undo.optimize': 'Trasa zoptymalizowana', + 'undo.deletePlace': 'Miejsce usunięte', + 'undo.deletePlaces': 'Miejsca usunięte', + 'undo.moveDay': 'Miejsce przeniesione', + 'undo.lock': 'Blokada przełączona', + 'undo.importGpx': 'Import GPX', + 'undo.importKeyholeMarkup': 'Import KMZ/KML', + 'undo.importGoogleList': 'Import Google Maps', + 'undo.importNaverList': 'Import Naver Maps', + 'undo.addPlace': 'Miejsce dodane', + 'undo.done': 'Cofnięto: {action}', +}; +export default undo; diff --git a/shared/src/i18n/pl/vacay.ts b/shared/src/i18n/pl/vacay.ts new file mode 100644 index 00000000..b6600a07 --- /dev/null +++ b/shared/src/i18n/pl/vacay.ts @@ -0,0 +1,106 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Planuj i zarządzaj dniami urlopu', + 'vacay.settings': 'Ustawienia', + 'vacay.year': 'Rok', + 'vacay.addYear': 'Dodaj następny rok', + 'vacay.addPrevYear': 'Dodaj poprzedni rok', + 'vacay.removeYear': 'Usuń rok', + 'vacay.removeYearConfirm': 'Usunąć {year}?', + 'vacay.removeYearHint': + 'Wszystkie wpisy dotyczące urlopów oraz dni wolnych w tym roku zostaną trwale usunięte.', + 'vacay.remove': 'Usuń', + 'vacay.persons': 'Osoby', + 'vacay.noPersons': 'Nie dodano osób', + 'vacay.addPerson': 'Dodaj osobę', + 'vacay.editPerson': 'Edytuj osobę', + 'vacay.removePerson': 'Usuń osobę', + 'vacay.removePersonConfirm': 'Usunąć {name}?', + 'vacay.removePersonHint': + 'Wszystkie wpisy dotyczące urlopów dla tej osoby zostaną trwale usunięte.', + 'vacay.personName': 'Imię', + 'vacay.personNamePlaceholder': 'Podaj imię', + 'vacay.color': 'Kolor', + 'vacay.add': 'Dodaj', + 'vacay.legend': 'Legenda', + 'vacay.publicHoliday': 'Święto państwowe', + 'vacay.companyHoliday': 'Urlop firmowy', + 'vacay.weekend': 'Weekendowy', + 'vacay.modeVacation': 'Urlop', + 'vacay.modeCompany': 'Urlop firmowy', + 'vacay.entitlement': 'Wymiar', + 'vacay.entitlementDays': 'Dni', + 'vacay.used': 'Wykorzystane', + 'vacay.remaining': 'Pozostało', + 'vacay.carriedOver': 'z {year}', + 'vacay.blockWeekends': 'Blokuj weekendy', + 'vacay.blockWeekendsHint': 'Zapobiegaj wpisywaniu urlopów w weekendy', + 'vacay.weekendDays': 'Dni weekendowe', + 'vacay.mon': 'Pon', + 'vacay.tue': 'Wt', + 'vacay.wed': 'Śr', + 'vacay.thu': 'Czw', + 'vacay.fri': 'Pt', + 'vacay.sat': 'Sob', + 'vacay.sun': 'Nd', + 'vacay.publicHolidays': 'Święta państwowe', + 'vacay.publicHolidaysHint': 'Oznacz święta państwowe w kalendarzu', + 'vacay.selectCountry': 'Wybierz kraj', + 'vacay.selectRegion': 'Wybierz region (opcjonalnie)', + 'vacay.addCalendar': 'Dodaj kalendarz', + 'vacay.calendarLabel': 'Etykieta (opcjonalnie)', + 'vacay.calendarColor': 'Kolor', + 'vacay.noCalendars': 'Nie dodano jeszcze kalendarzy świąt', + 'vacay.companyHolidays': 'Urlopy firmowe', + 'vacay.companyHolidaysHint': + 'Pozwala oznaczać dni wolne od pracy w kalendarzu', + 'vacay.companyHolidaysNoDeduct': + 'Urlopy firmowe nie są odejmowane od puli dni urlopowych.', + 'vacay.weekStart': 'Tydzień zaczyna się w', + 'vacay.weekStartHint': + 'Wybierz czy tydzień zaczyna się w poniedziałek czy niedzielę', + 'vacay.carryOver': 'Przeniesienie na kolejny rok', + 'vacay.carryOverHint': + 'Automatycznie przenosi pozostałe dni urlopowe na kolejny rok', + 'vacay.sharing': 'Udostępnianie', + 'vacay.sharingHint': 'Udostępnij swój plan urlopów innym użytkownikom TREK', + 'vacay.owner': 'Właściciel', + 'vacay.shareEmailPlaceholder': 'E-mail użytkownika TREK', + 'vacay.shareSuccess': 'Plan został udostępniony pomyślnie', + 'vacay.shareError': 'Nie udało się udostępnić planu', + 'vacay.dissolve': 'Rozłącz kalendarze', + 'vacay.dissolveHint': + 'Rozłącz kalendarze ponownie. Twoje wpisy zostaną zachowane.', + 'vacay.dissolveAction': 'Rozłącz', + 'vacay.dissolved': 'Kalendarz został rozłączony', + 'vacay.fusedWith': 'Połączono z', + 'vacay.you': 'ty', + 'vacay.noData': 'Brak danych', + 'vacay.changeColor': 'Zmień kolor', + 'vacay.inviteUser': 'Zaproś użytkownika', + 'vacay.inviteHint': + 'Zaproś innego użytkownika TREK do wspólnego kalendarza urlopów.', + 'vacay.selectUser': 'Wybierz użytkownika', + 'vacay.sendInvite': 'Wyślij zaproszenie', + 'vacay.inviteSent': 'Zaproszenie zostało wysłane', + 'vacay.inviteError': 'Nie udało się wysłać zaproszenia', + 'vacay.pending': 'oczekujące', + 'vacay.noUsersAvailable': 'Brak dostępnych użytkowników', + 'vacay.accept': 'Akceptuj', + 'vacay.decline': 'Odrzuć', + 'vacay.acceptFusion': 'Akceptuj i połącz', + 'vacay.inviteTitle': 'Zaproszenie do połączenia', + 'vacay.inviteWantsToFuse': 'chce udostępnić kalendarz urlopów.', + 'vacay.fuseInfo1': + 'Obie strony będą widzieć wszystkie wpisy urlopowe w jednym wspólnym kalendarzu.', + 'vacay.fuseInfo2': + 'Obie strony mogą tworzyć i edytować wpisy dla drugiej strony.', + 'vacay.fuseInfo3': + 'Obie strony mogą usuwać wpisy i zmieniać pulę dni urlopowych.', + 'vacay.fuseInfo4': + 'Ustawienia, takie jak święta państwowe i urlopy firmowe, są współdzielone.', + 'vacay.fuseInfo5': + 'Połączenie może zostać rozwiązane w dowolnym momencie przez każdą ze stron. Twoje wpisy zostaną zachowane.', +}; +export default vacay; diff --git a/shared/src/i18n/ru/admin.ts b/shared/src/i18n/ru/admin.ts new file mode 100644 index 00000000..6bab660f --- /dev/null +++ b/shared/src/i18n/ru/admin.ts @@ -0,0 +1,370 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Уведомления', + 'admin.notifications.hint': + 'Выберите канал уведомлений. Одновременно может быть активен только один.', + 'admin.notifications.none': 'Отключено', + 'admin.notifications.email': 'Эл. почта (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Сохранить настройки уведомлений', + 'admin.notifications.saved': 'Настройки уведомлений сохранены', + 'admin.notifications.testWebhook': 'Отправить тестовый вебхук', + 'admin.notifications.testWebhookSuccess': 'Тестовый вебхук успешно отправлен', + 'admin.notifications.testWebhookFailed': 'Ошибка отправки тестового вебхука', + 'admin.smtp.title': 'Почта и уведомления', + 'admin.smtp.hint': + 'Конфигурация SMTP для отправки уведомлений по электронной почте.', + 'admin.smtp.testButton': 'Отправить тестовое письмо', + 'admin.webhook.hint': + 'Отправлять уведомления через внешний webhook (Discord, Slack и т.д.).', + 'admin.smtp.testSuccess': 'Тестовое письмо успешно отправлено', + 'admin.smtp.testFailed': 'Ошибка отправки тестового письма', + 'admin.title': 'Администрирование', + 'admin.subtitle': 'Управление пользователями и системные настройки', + 'admin.tabs.users': 'Пользователи', + 'admin.tabs.categories': 'Категории', + 'admin.tabs.backup': 'Резервная копия', + 'admin.tabs.audit': 'Аудит', + 'admin.stats.users': 'Пользователи', + 'admin.stats.trips': 'Поездки', + 'admin.stats.places': 'Места', + 'admin.stats.photos': 'Фото', + 'admin.stats.files': 'Файлы', + 'admin.table.user': 'Пользователь', + 'admin.table.email': 'Эл. почта', + 'admin.table.role': 'Роль', + 'admin.table.created': 'Создан', + 'admin.table.lastLogin': 'Последний вход', + 'admin.table.actions': 'Действия', + 'admin.you': '(Вы)', + 'admin.editUser': 'Редактировать пользователя', + 'admin.newPassword': 'Новый пароль', + 'admin.newPasswordHint': 'Оставьте пустым, чтобы сохранить текущий пароль', + 'admin.deleteUser': + 'Удалить пользователя «{name}»? Все поездки будут безвозвратно удалены.', + 'admin.deleteUserTitle': 'Удалить пользователя', + 'admin.newPasswordPlaceholder': 'Введите новый пароль…', + 'admin.toast.loadError': 'Не удалось загрузить данные администрирования', + 'admin.toast.userUpdated': 'Пользователь обновлён', + 'admin.toast.updateError': 'Ошибка обновления', + 'admin.toast.userDeleted': 'Пользователь удалён', + 'admin.toast.deleteError': 'Ошибка удаления', + 'admin.toast.cannotDeleteSelf': 'Нельзя удалить собственный аккаунт', + 'admin.toast.userCreated': 'Пользователь создан', + 'admin.toast.createError': 'Ошибка создания пользователя', + 'admin.toast.fieldsRequired': + 'Имя пользователя, эл. почта и пароль обязательны', + 'admin.createUser': 'Создать пользователя', + 'admin.invite.title': 'Ссылки-приглашения', + 'admin.invite.subtitle': 'Создание одноразовых ссылок для регистрации', + 'admin.invite.create': 'Создать ссылку', + 'admin.invite.createAndCopy': 'Создать и скопировать', + 'admin.invite.empty': 'Ссылки-приглашения ещё не созданы', + 'admin.invite.maxUses': 'Макс. использований', + 'admin.invite.expiry': 'Действует', + 'admin.invite.uses': 'использовано', + 'admin.invite.expiresAt': 'истекает', + 'admin.invite.createdBy': 'от', + 'admin.invite.active': 'Активна', + 'admin.invite.expired': 'Истекла', + 'admin.invite.usedUp': 'Исчерпана', + 'admin.invite.copied': 'Ссылка-приглашение скопирована', + 'admin.invite.copyLink': 'Копировать ссылку', + 'admin.invite.deleted': 'Ссылка-приглашение удалена', + 'admin.invite.createError': 'Ошибка при создании ссылки', + 'admin.invite.deleteError': 'Ошибка при удалении ссылки', + 'admin.tabs.settings': 'Настройки', + 'admin.allowRegistration': 'Разрешить регистрацию', + 'admin.allowRegistrationHint': + 'Новые пользователи могут регистрироваться самостоятельно', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Требовать двухфакторную аутентификацию (2FA)', + 'admin.requireMfaHint': + 'Пользователи без 2FA должны завершить настройку в разделе «Настройки» перед использованием приложения.', + 'admin.apiKeys': 'API-ключи', + 'admin.apiKeysHint': + 'Необязательно. Включает расширенные данные о местах, такие как фото и погода.', + 'admin.mapsKey': 'API-ключ Google Maps', + 'admin.mapsKeyHint': + 'Необходим для поиска мест. Получите на console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Без API-ключа используется OpenStreetMap для поиска мест. С ключом Google API можно загружать фото, рейтинги и часы работы. Получите ключ на console.cloud.google.com.', + 'admin.recommended': 'Рекомендуется', + 'admin.weatherKey': 'API-ключ OpenWeatherMap', + 'admin.weatherKeyHint': + 'Для данных о погоде. Бесплатно на openweathermap.org', + 'admin.validateKey': 'Проверить', + 'admin.keyValid': 'Подключено', + 'admin.keyInvalid': 'Недействителен', + 'admin.keySaved': 'API-ключи сохранены', + 'admin.oidcTitle': 'Единый вход (OIDC)', + 'admin.oidcSubtitle': + 'Разрешить вход через внешних провайдеров, таких как Google, Apple, Authentik или Keycloak.', + 'admin.oidcDisplayName': 'Отображаемое имя', + 'admin.oidcIssuer': 'URL издателя', + 'admin.oidcIssuerHint': + 'URL издателя OpenID Connect провайдера. Напр. https://accounts.google.com', + 'admin.oidcSaved': 'Конфигурация OIDC сохранена', + 'admin.oidcOnlyMode': 'Отключить вход по паролю', + 'admin.oidcOnlyModeHint': + 'При включении разрешён только вход через SSO. Вход и регистрация по паролю будут заблокированы.', + 'admin.fileTypes': 'Разрешённые типы файлов', + 'admin.fileTypesHint': + 'Настройте, какие типы файлов могут загружать пользователи.', + 'admin.fileTypesFormat': + 'Расширения через запятую (напр. jpg,png,pdf,doc). Используйте * для разрешения всех типов.', + 'admin.fileTypesSaved': 'Настройки типов файлов сохранены', + 'admin.placesPhotos.title': 'Фотографии мест', + 'admin.placesPhotos.subtitle': + 'Загрузка фотографий из Google Places API. Отключите для экономии квоты API. Фотографии Wikimedia не затронуты.', + 'admin.placesAutocomplete.title': 'Автодополнение мест', + 'admin.placesAutocomplete.subtitle': + 'Использование Google Places API для поисковых подсказок. Отключите для экономии квоты API.', + 'admin.placesDetails.title': 'Сведения о месте', + 'admin.placesDetails.subtitle': + 'Загрузка подробной информации о месте (часы работы, рейтинг, веб-сайт) из Google Places API. Отключите для экономии квоты API.', + 'admin.bagTracking.title': 'Отслеживание багажа', + 'admin.bagTracking.subtitle': 'Включить вес и привязку к багажу для вещей', + 'admin.collab.chat.title': 'Чат', + 'admin.collab.chat.subtitle': 'Обмен сообщениями для совместной работы', + 'admin.collab.notes.title': 'Заметки', + 'admin.collab.notes.subtitle': 'Общие заметки и документы', + 'admin.collab.polls.title': 'Опросы', + 'admin.collab.polls.subtitle': 'Групповые опросы и голосования', + 'admin.collab.whatsnext.title': 'Что дальше', + 'admin.collab.whatsnext.subtitle': 'Предложения активностей и следующие шаги', + 'admin.tabs.config': 'Персонализация', + 'admin.tabs.defaults': 'Настройки по умолчанию', + 'admin.defaultSettings.title': 'Настройки пользователей по умолчанию', + 'admin.defaultSettings.description': + 'Задайте значения по умолчанию для всего экземпляра. Пользователи, не изменившие параметр, увидят эти значения. Их собственные изменения всегда имеют приоритет.', + 'admin.defaultSettings.saved': 'Значение по умолчанию сохранено', + 'admin.defaultSettings.reset': 'Сбросить до встроенного значения', + 'admin.defaultSettings.resetToBuiltIn': 'сбросить', + 'admin.tabs.templates': 'Шаблоны упаковки', + 'admin.packingTemplates.title': 'Шаблоны упаковки', + 'admin.packingTemplates.subtitle': + 'Создавайте многоразовые списки вещей для поездок', + 'admin.packingTemplates.create': 'Новый шаблон', + 'admin.packingTemplates.namePlaceholder': + 'Название шаблона (напр. Пляжный отдых)', + 'admin.packingTemplates.empty': 'Шаблоны ещё не созданы', + 'admin.packingTemplates.items': 'вещей', + 'admin.packingTemplates.categories': 'категорий', + 'admin.packingTemplates.itemName': 'Название вещи', + 'admin.packingTemplates.itemCategory': 'Категория', + 'admin.packingTemplates.categoryName': 'Название категории (напр. Одежда)', + 'admin.packingTemplates.addCategory': 'Добавить категорию', + 'admin.packingTemplates.created': 'Шаблон создан', + 'admin.packingTemplates.deleted': 'Шаблон удалён', + 'admin.packingTemplates.loadError': 'Ошибка загрузки шаблонов', + 'admin.packingTemplates.createError': 'Ошибка создания шаблона', + 'admin.packingTemplates.deleteError': 'Ошибка удаления шаблона', + 'admin.packingTemplates.saveError': 'Ошибка сохранения', + 'admin.tabs.addons': 'Дополнения', + 'admin.addons.title': 'Дополнения', + 'admin.addons.subtitle': + 'Включайте или отключайте функции для настройки TREK под себя.', + 'admin.addons.catalog.memories.name': 'Фото (Immich)', + 'admin.addons.catalog.memories.description': + 'Делитесь фотографиями из поездок через Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Протокол контекста модели для интеграции с ИИ-ассистентами', + 'admin.addons.catalog.packing.name': 'Списки', + 'admin.addons.catalog.packing.description': + 'Списки вещей и задачи для ваших поездок', + 'admin.addons.catalog.budget.name': 'Бюджет', + 'admin.addons.catalog.budget.description': + 'Отслеживайте расходы и планируйте бюджет поездки', + 'admin.addons.catalog.documents.name': 'Документы', + 'admin.addons.catalog.documents.description': + 'Храните и управляйте документами для путешествий', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Личный планировщик отпусков с календарём', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Карта мира с посещёнными странами и статистикой путешествий', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Заметки в реальном времени, опросы и чат для планирования поездок', + 'admin.addons.subtitleBefore': + 'Включайте или отключайте функции для настройки ', + 'admin.addons.subtitleAfter': ' под себя.', + 'admin.addons.enabled': 'Включено', + 'admin.addons.disabled': 'Отключено', + 'admin.addons.type.trip': 'Поездка', + 'admin.addons.type.global': 'Глобально', + 'admin.addons.type.integration': 'Интеграция', + 'admin.addons.tripHint': 'Доступно как вкладка внутри каждой поездки', + 'admin.addons.globalHint': + 'Доступно как отдельный раздел в основной навигации', + 'admin.addons.integrationHint': + 'Фоновые сервисы и API-интеграции без отдельной страницы', + 'admin.addons.toast.updated': 'Дополнение обновлено', + 'admin.addons.toast.error': 'Не удалось обновить дополнение', + 'admin.addons.noAddons': 'Нет доступных дополнений', + 'admin.weather.title': 'Данные о погоде', + 'admin.weather.badge': 'С 24 марта 2026', + 'admin.weather.description': + 'TREK использует Open-Meteo как источник данных о погоде. Open-Meteo — бесплатный сервис с открытым кодом, API-ключ не требуется.', + 'admin.weather.forecast': 'Прогноз на 16 дней', + 'admin.weather.forecastDesc': 'Ранее 5 дней (OpenWeatherMap)', + 'admin.weather.climate': 'Исторические климатические данные', + 'admin.weather.climateDesc': + 'Средние значения за последние 85 лет для дней за пределами 16-дневного прогноза', + 'admin.weather.requests': '10 000 запросов / день', + 'admin.weather.requestsDesc': 'Бесплатно, API-ключ не требуется', + 'admin.weather.locationHint': + 'Погода основана на первом месте с координатами в каждом дне. Если ни одно место не назначено на день, в качестве ориентира используется любое место из списка.', + 'admin.tabs.mcpTokens': 'MCP-доступ', + 'admin.mcpTokens.title': 'MCP-доступ', + 'admin.mcpTokens.subtitle': + 'Управление OAuth-сессиями и API-токенами всех пользователей', + 'admin.mcpTokens.sectionTitle': 'API-токены', + 'admin.mcpTokens.owner': 'Владелец', + 'admin.mcpTokens.tokenName': 'Название токена', + 'admin.mcpTokens.created': 'Создан', + 'admin.mcpTokens.lastUsed': 'Последнее использование', + 'admin.mcpTokens.never': 'Никогда', + 'admin.mcpTokens.empty': 'MCP-токены ещё не созданы', + 'admin.mcpTokens.deleteTitle': 'Удалить токен', + 'admin.mcpTokens.deleteMessage': + 'Токен будет немедленно отозван. Пользователь потеряет доступ к MCP через этот токен.', + 'admin.mcpTokens.deleteSuccess': 'Токен удалён', + 'admin.mcpTokens.deleteError': 'Не удалось удалить токен', + 'admin.mcpTokens.loadError': 'Не удалось загрузить токены', + 'admin.oauthSessions.sectionTitle': 'OAuth-сессии', + 'admin.oauthSessions.clientName': 'Клиент', + 'admin.oauthSessions.owner': 'Владелец', + 'admin.oauthSessions.scopes': 'Права доступа', + 'admin.oauthSessions.created': 'Создано', + 'admin.oauthSessions.empty': 'Нет активных OAuth-сессий', + 'admin.oauthSessions.revokeTitle': 'Отозвать сессию', + 'admin.oauthSessions.revokeMessage': + 'Эта OAuth-сессия будет немедленно отозвана. Клиент потеряет доступ к MCP.', + 'admin.oauthSessions.revokeSuccess': 'Сессия отозвана', + 'admin.oauthSessions.revokeError': 'Не удалось отозвать сессию', + 'admin.oauthSessions.loadError': 'Не удалось загрузить OAuth-сессии', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'События, связанные с безопасностью и администрированием (резервные копии, пользователи, MFA, настройки).', + 'admin.audit.empty': 'Записей аудита пока нет.', + 'admin.audit.refresh': 'Обновить', + 'admin.audit.loadMore': 'Загрузить ещё', + 'admin.audit.showing': 'Загружено: {count} · всего {total}', + 'admin.audit.col.time': 'Время', + 'admin.audit.col.user': 'Пользователь', + 'admin.audit.col.action': 'Действие', + 'admin.audit.col.resource': 'Объект', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Подробности', + 'admin.github.title': 'История релизов', + 'admin.github.subtitle': 'Последние обновления из {repo}', + 'admin.github.latest': 'Последний', + 'admin.github.prerelease': 'Пре-релиз', + 'admin.github.showDetails': 'Показать подробности', + 'admin.github.hideDetails': 'Скрыть подробности', + 'admin.github.loadMore': 'Загрузить ещё', + 'admin.github.loading': 'Загрузка...', + 'admin.github.support': 'Помогает продолжать разработку TREK', + 'admin.github.error': 'Не удалось загрузить релизы', + 'admin.github.by': 'от', + 'admin.update.available': 'Доступно обновление', + 'admin.update.text': + 'Доступна версия TREK {version}. У вас установлена {current}.', + 'admin.update.button': 'Посмотреть на GitHub', + 'admin.update.install': 'Установить обновление', + 'admin.update.confirmTitle': 'Установить обновление?', + 'admin.update.confirmText': + 'TREK будет обновлён с {current} до {version}. Сервер перезапустится автоматически.', + 'admin.update.dataInfo': + 'Все ваши данные (поездки, пользователи, API-ключи, загрузки, Vacay, Atlas, бюджеты) будут сохранены.', + 'admin.update.warning': + 'Приложение будет кратковременно недоступно во время перезапуска.', + 'admin.update.confirm': 'Обновить сейчас', + 'admin.update.installing': 'Обновление…', + 'admin.update.success': 'Обновление установлено! Сервер перезапускается…', + 'admin.update.failed': 'Ошибка обновления', + 'admin.update.backupHint': + 'Рекомендуем создать резервную копию перед обновлением.', + 'admin.update.backupLink': 'Перейти к резервным копиям', + 'admin.update.howTo': 'Как обновить', + 'admin.update.dockerText': + 'Ваш экземпляр TREK работает в Docker. Для обновления до {version} выполните следующие команды на сервере:', + 'admin.update.reloadHint': 'Перезагрузите страницу через несколько секунд.', + 'admin.tabs.permissions': 'Разрешения', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Уведомления в приложении всегда активны и не могут быть отключены глобально.', + 'admin.notifications.adminWebhookPanel.title': 'Вебхук администратора', + 'admin.notifications.adminWebhookPanel.hint': + 'Этот вебхук используется исключительно для уведомлений администратора (например, оповещения о версиях). Он независим от пользовательских вебхуков и отправляется автоматически при наличии URL.', + 'admin.notifications.adminWebhookPanel.saved': + 'URL вебхука администратора сохранён', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Тестовый вебхук успешно отправлен', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Ошибка тестового вебхука', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Вебхук администратора отправляется автоматически при наличии URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Позволяет пользователям настраивать собственные темы ntfy для push-уведомлений. Установите сервер по умолчанию ниже, чтобы предварительно заполнить настройки пользователей.', + 'admin.notifications.testNtfy': 'Отправить тестовое Ntfy', + 'admin.notifications.testNtfySuccess': 'Тестовое Ntfy успешно отправлено', + 'admin.notifications.testNtfyFailed': 'Ошибка отправки тестового Ntfy', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy администратора', + 'admin.notifications.adminNtfyPanel.hint': + 'Эта тема Ntfy используется исключительно для уведомлений администратора (например, оповещения о версиях). Она независима от тем пользователей и всегда отправляется при наличии настройки.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Также используется как сервер по умолчанию для ntfy-уведомлений пользователей. Оставьте пустым, чтобы использовать ntfy.sh. Пользователи могут изменить это в своих настройках.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема администратора', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': + 'Токен доступа (необязательно)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Токен доступа администратора очищен', + 'admin.notifications.adminNtfyPanel.saved': + 'Настройки Ntfy администратора сохранены', + 'admin.notifications.adminNtfyPanel.test': 'Отправить тестовое Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Тестовое Ntfy успешно отправлено', + 'admin.notifications.adminNtfyPanel.testFailed': + 'Ошибка отправки тестового Ntfy', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Ntfy администратора всегда отправляется при наличии настроенной темы', + 'admin.notifications.adminNotificationsHint': + 'Настройте, какие каналы доставляют уведомления администратора (например, оповещения о версиях). Вебхук отправляется автоматически, если задан URL вебхука администратора.', + 'admin.notifications.tripReminders.title': 'Напоминания о поездках', + 'admin.notifications.tripReminders.hint': + 'Отправляет напоминание перед началом поездки (необходимо указать дни напоминания в параметрах поездки).', + 'admin.notifications.tripReminders.enabled': + 'Напоминания о поездках включены', + 'admin.notifications.tripReminders.disabled': + 'Напоминания о поездках отключены', + 'admin.tabs.notifications': 'Уведомления', + 'admin.addons.catalog.journey.name': 'Путешествие', + 'admin.addons.catalog.journey.description': + 'Отслеживание поездок и дневник путешествий с отметками, фото и ежедневными историями', +}; +export default admin; diff --git a/shared/src/i18n/ru/airport.ts b/shared/src/i18n/ru/airport.ts new file mode 100644 index 00000000..9ba11f16 --- /dev/null +++ b/shared/src/i18n/ru/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Код аэропорта или город (напр. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/ru/atlas.ts b/shared/src/i18n/ru/atlas.ts new file mode 100644 index 00000000..e7c1db8d --- /dev/null +++ b/shared/src/i18n/ru/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Ваш след путешествий по всему миру', + 'atlas.countries': 'Страны', + 'atlas.trips': 'Поездки', + 'atlas.places': 'Места', + 'atlas.days': 'Дни', + 'atlas.visitedCountries': 'Посещённые страны', + 'atlas.cities': 'Города', + 'atlas.noData': 'Данных о поездках пока нет', + 'atlas.noDataHint': + 'Создайте поездку и добавьте места, чтобы увидеть карту мира', + 'atlas.lastTrip': 'Последняя поездка', + 'atlas.nextTrip': 'Следующая поездка', + 'atlas.daysLeft': 'дней осталось', + 'atlas.streak': 'Серия', + 'atlas.year': 'год', + 'atlas.years': 'лет', + 'atlas.yearInRow': 'год подряд', + 'atlas.yearsInRow': 'лет подряд', + 'atlas.tripIn': 'поездка в', + 'atlas.tripsIn': 'поездок в', + 'atlas.since': 'с', + 'atlas.europe': 'Европа', + 'atlas.asia': 'Азия', + 'atlas.northAmerica': 'Сев. Америка', + 'atlas.southAmerica': 'Юж. Америка', + 'atlas.africa': 'Африка', + 'atlas.oceania': 'Океания', + 'atlas.other': 'Другое', + 'atlas.firstVisit': 'Первая поездка', + 'atlas.lastVisitLabel': 'Последняя поездка', + 'atlas.tripSingular': 'Поездка', + 'atlas.tripPlural': 'Поездки', + 'atlas.placeVisited': 'Посещённое место', + 'atlas.placesVisited': 'Посещённые места', + 'atlas.statsTab': 'Статистика', + 'atlas.bucketTab': 'Список желаний', + 'atlas.addBucket': 'Добавить в список желаний', + 'atlas.bucketNamePlaceholder': 'Место или направление...', + 'atlas.bucketNotesPlaceholder': 'Заметки (необязательно)', + 'atlas.bucketEmpty': 'Ваш список желаний пуст', + 'atlas.bucketEmptyHint': 'Добавьте места, которые мечтаете посетить', + 'atlas.unmark': 'Удалить', + 'atlas.confirmMark': 'Отметить эту страну как посещённую?', + 'atlas.confirmUnmark': 'Удалить эту страну из списка посещённых?', + 'atlas.confirmUnmarkRegion': 'Удалить этот регион из списка посещённых?', + 'atlas.markVisited': 'Отметить как посещённую', + 'atlas.markVisitedHint': 'Добавить эту страну в список посещённых', + 'atlas.markRegionVisitedHint': 'Добавить этот регион в список посещённых', + 'atlas.addToBucket': 'В список желаний', + 'atlas.addPoi': 'Добавить место', + 'atlas.searchCountry': 'Поиск страны...', + 'atlas.month': 'Месяц', + 'atlas.addToBucketHint': 'Сохранить как место для посещения', + 'atlas.bucketWhen': 'Когда вы планируете поехать?', +}; +export default atlas; diff --git a/shared/src/i18n/ru/backup.ts b/shared/src/i18n/ru/backup.ts new file mode 100644 index 00000000..ab880398 --- /dev/null +++ b/shared/src/i18n/ru/backup.ts @@ -0,0 +1,78 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Резервная копия', + 'backup.subtitle': 'База данных и все загруженные файлы', + 'backup.refresh': 'Обновить', + 'backup.upload': 'Загрузить копию', + 'backup.uploading': 'Загрузка…', + 'backup.create': 'Создать копию', + 'backup.creating': 'Создание…', + 'backup.empty': 'Резервных копий нет', + 'backup.createFirst': 'Создать первую копию', + 'backup.download': 'Скачать', + 'backup.restore': 'Восстановить', + 'backup.confirm.restore': + 'Восстановить копию «{name}»?\n\nВсе текущие данные будут заменены данными из копии.', + 'backup.confirm.uploadRestore': + 'Загрузить и восстановить файл копии «{name}»?\n\nВсе текущие данные будут перезаписаны.', + 'backup.confirm.delete': 'Удалить копию «{name}»?', + 'backup.toast.loadError': 'Не удалось загрузить резервные копии', + 'backup.toast.created': 'Резервная копия создана', + 'backup.toast.createError': 'Не удалось создать резервную копию', + 'backup.toast.restored': 'Копия восстановлена. Страница перезагрузится…', + 'backup.toast.restoreError': 'Ошибка восстановления', + 'backup.toast.uploadError': 'Ошибка загрузки', + 'backup.toast.deleted': 'Резервная копия удалена', + 'backup.toast.deleteError': 'Ошибка удаления', + 'backup.toast.downloadError': 'Ошибка скачивания', + 'backup.toast.settingsSaved': 'Настройки автокопирования сохранены', + 'backup.toast.settingsError': 'Не удалось сохранить настройки', + 'backup.auto.title': 'Автокопирование', + 'backup.auto.subtitle': 'Автоматическое резервное копирование по расписанию', + 'backup.auto.enable': 'Включить автокопирование', + 'backup.auto.enableHint': + 'Резервные копии будут создаваться автоматически по выбранному расписанию', + 'backup.auto.interval': 'Интервал', + 'backup.auto.hour': 'Запуск в час', + 'backup.auto.hourHint': 'Местное время сервера (формат {format})', + 'backup.auto.dayOfWeek': 'День недели', + 'backup.auto.dayOfMonth': 'День месяца', + 'backup.auto.dayOfMonthHint': + 'Ограничено 1–28 для совместимости со всеми месяцами', + 'backup.auto.scheduleSummary': 'Расписание', + 'backup.auto.summaryDaily': 'Каждый день в {hour}:00', + 'backup.auto.summaryWeekly': 'Каждый {day} в {hour}:00', + 'backup.auto.summaryMonthly': '{day}-го числа каждого месяца в {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Автокопирование настроено через переменные окружения Docker. Чтобы изменить параметры, обновите docker-compose.yml и перезапустите контейнер.', + 'backup.auto.copyEnv': 'Скопировать переменные окружения Docker', + 'backup.auto.envCopied': + 'Переменные окружения Docker скопированы в буфер обмена', + 'backup.auto.keepLabel': 'Удалять старые копии через', + 'backup.dow.sunday': 'Вс', + 'backup.dow.monday': 'Пн', + 'backup.dow.tuesday': 'Вт', + 'backup.dow.wednesday': 'Ср', + 'backup.dow.thursday': 'Чт', + 'backup.dow.friday': 'Пт', + 'backup.dow.saturday': 'Сб', + 'backup.interval.hourly': 'Каждый час', + 'backup.interval.daily': 'Ежедневно', + 'backup.interval.weekly': 'Еженедельно', + 'backup.interval.monthly': 'Ежемесячно', + 'backup.keep.1day': '1 день', + 'backup.keep.3days': '3 дня', + 'backup.keep.7days': '7 дней', + 'backup.keep.14days': '14 дней', + 'backup.keep.30days': '30 дней', + 'backup.keep.forever': 'Хранить вечно', + 'backup.restoreConfirmTitle': 'Восстановить копию?', + 'backup.restoreWarning': + 'Все текущие данные (поездки, места, пользователи, загрузки) будут безвозвратно заменены данными из копии. Это действие нельзя отменить.', + 'backup.restoreTip': + 'Совет: создайте резервную копию текущего состояния перед восстановлением.', + 'backup.restoreConfirm': 'Да, восстановить', +}; +export default backup; diff --git a/shared/src/i18n/ru/budget.ts b/shared/src/i18n/ru/budget.ts new file mode 100644 index 00000000..7f93b297 --- /dev/null +++ b/shared/src/i18n/ru/budget.ts @@ -0,0 +1,44 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Бюджет', + 'budget.exportCsv': 'Экспорт CSV', + 'budget.emptyTitle': 'Бюджет ещё не создан', + 'budget.emptyText': + 'Создайте категории и записи для планирования бюджета поездки', + 'budget.emptyPlaceholder': 'Введите название категории...', + 'budget.createCategory': 'Создать категорию', + 'budget.category': 'Категория', + 'budget.categoryName': 'Название категории', + 'budget.table.name': 'Название', + 'budget.table.total': 'Итого', + 'budget.table.persons': 'Человек', + 'budget.table.days': 'Дней', + 'budget.table.perPerson': 'На человека', + 'budget.table.perDay': 'В день', + 'budget.table.perPersonDay': 'Чел. / день', + 'budget.table.note': 'Заметка', + 'budget.table.date': 'Дата', + 'budget.newEntry': 'Новая запись', + 'budget.defaultEntry': 'Новая запись', + 'budget.defaultCategory': 'Новая категория', + 'budget.total': 'Итого', + 'budget.totalBudget': 'Общий бюджет', + 'budget.byCategory': 'По категориям', + 'budget.editTooltip': 'Нажмите для редактирования', + 'budget.linkedToReservation': + 'Связано с бронированием — редактируйте название там', + 'budget.confirm.deleteCategory': + 'Вы уверены, что хотите удалить категорию «{name}» с {count} записями?', + 'budget.deleteCategory': 'Удалить категорию', + 'budget.perPerson': 'На человека', + 'budget.paid': 'Оплачено', + 'budget.open': 'Не оплачено', + 'budget.noMembers': 'Участники не назначены', + 'budget.settlement': 'Взаиморасчёт', + 'budget.settlementInfo': + 'Нажмите на аватар участника в строке бюджета, чтобы отметить его зелёным — это значит, что он заплатил. Взаиморасчёт покажет, кто кому и сколько должен.', + 'budget.netBalances': 'Чистые балансы', + 'budget.categoriesLabel': 'категорий', +}; +export default budget; diff --git a/shared/src/i18n/ru/categories.ts b/shared/src/i18n/ru/categories.ts new file mode 100644 index 00000000..f0bc09de --- /dev/null +++ b/shared/src/i18n/ru/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Категории', + 'categories.subtitle': 'Управление категориями мест', + 'categories.new': 'Новая категория', + 'categories.empty': 'Категорий пока нет', + 'categories.namePlaceholder': 'Название категории', + 'categories.icon': 'Иконка', + 'categories.color': 'Цвет', + 'categories.customColor': 'Выбрать свой цвет', + 'categories.preview': 'Предпросмотр', + 'categories.defaultName': 'Категория', + 'categories.update': 'Обновить', + 'categories.create': 'Создать', + 'categories.confirm.delete': + 'Удалить категорию? Места в этой категории не будут удалены.', + 'categories.toast.loadError': 'Не удалось загрузить категории', + 'categories.toast.nameRequired': 'Введите название', + 'categories.toast.updated': 'Категория обновлена', + 'categories.toast.created': 'Категория создана', + 'categories.toast.saveError': 'Ошибка сохранения', + 'categories.toast.deleted': 'Категория удалена', + 'categories.toast.deleteError': 'Ошибка удаления', +}; +export default categories; diff --git a/shared/src/i18n/ru/collab.ts b/shared/src/i18n/ru/collab.ts new file mode 100644 index 00000000..6111dc9c --- /dev/null +++ b/shared/src/i18n/ru/collab.ts @@ -0,0 +1,75 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Чат', + 'collab.tabs.notes': 'Заметки', + 'collab.tabs.polls': 'Опросы', + 'collab.whatsNext.title': 'Что дальше', + 'collab.whatsNext.today': 'Сегодня', + 'collab.whatsNext.tomorrow': 'Завтра', + 'collab.whatsNext.empty': 'Нет предстоящих активностей', + 'collab.whatsNext.until': 'до', + 'collab.whatsNext.emptyHint': + 'Активности со временем будут отображаться здесь', + 'collab.chat.send': 'Отправить', + 'collab.chat.placeholder': 'Введите сообщение...', + 'collab.chat.empty': 'Начните разговор', + 'collab.chat.emptyHint': 'Сообщения видны всем участникам поездки', + 'collab.chat.emptyDesc': + 'Делитесь идеями, планами и новостями с вашей группой', + 'collab.chat.today': 'Сегодня', + 'collab.chat.yesterday': 'Вчера', + 'collab.chat.deletedMessage': 'удалил(а) сообщение', + 'collab.chat.reply': 'Ответить', + 'collab.chat.loadMore': 'Загрузить старые сообщения', + 'collab.chat.justNow': 'только что', + 'collab.chat.minutesAgo': '{n} мин. назад', + 'collab.chat.hoursAgo': '{n} ч. назад', + 'collab.notes.title': 'Заметки', + 'collab.notes.new': 'Новая заметка', + 'collab.notes.empty': 'Заметок пока нет', + 'collab.notes.emptyHint': 'Начните записывать идеи и планы', + 'collab.notes.all': 'Все', + 'collab.notes.titlePlaceholder': 'Название заметки', + 'collab.notes.contentPlaceholder': 'Напишите что-нибудь...', + 'collab.notes.categoryPlaceholder': 'Категория', + 'collab.notes.newCategory': 'Новая категория...', + 'collab.notes.category': 'Категория', + 'collab.notes.noCategory': 'Без категории', + 'collab.notes.color': 'Цвет', + 'collab.notes.save': 'Сохранить', + 'collab.notes.cancel': 'Отмена', + 'collab.notes.edit': 'Редактировать', + 'collab.notes.delete': 'Удалить', + 'collab.notes.pin': 'Закрепить', + 'collab.notes.unpin': 'Открепить', + 'collab.notes.daysAgo': '{n} дн. назад', + 'collab.notes.categorySettings': 'Управление категориями', + 'collab.notes.create': 'Создать', + 'collab.notes.website': 'Сайт', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Прикрепить файлы', + 'collab.notes.noCategoriesYet': 'Категорий пока нет', + 'collab.notes.emptyDesc': 'Создайте заметку, чтобы начать', + 'collab.polls.title': 'Опросы', + 'collab.polls.new': 'Новый опрос', + 'collab.polls.empty': 'Опросов пока нет', + 'collab.polls.emptyHint': 'Задайте вопрос группе и голосуйте вместе', + 'collab.polls.question': 'Вопрос', + 'collab.polls.questionPlaceholder': 'Что нам делать?', + 'collab.polls.addOption': '+ Добавить вариант', + 'collab.polls.optionPlaceholder': 'Вариант {n}', + 'collab.polls.create': 'Создать опрос', + 'collab.polls.close': 'Закрыть', + 'collab.polls.closed': 'Закрыт', + 'collab.polls.votes': '{n} голосов', + 'collab.polls.vote': '{n} голос', + 'collab.polls.multipleChoice': 'Множественный выбор', + 'collab.polls.multiChoice': 'Множественный выбор', + 'collab.polls.deadline': 'Срок', + 'collab.polls.option': 'Вариант', + 'collab.polls.options': 'Варианты', + 'collab.polls.delete': 'Удалить', + 'collab.polls.closedSection': 'Закрытые', +}; +export default collab; diff --git a/shared/src/i18n/ru/common.ts b/shared/src/i18n/ru/common.ts new file mode 100644 index 00000000..73c255fd --- /dev/null +++ b/shared/src/i18n/ru/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Сохранить', + 'common.showMore': 'Показать больше', + 'common.showLess': 'Показать меньше', + 'common.cancel': 'Отмена', + 'common.clear': 'Очистить', + 'common.delete': 'Удалить', + 'common.edit': 'Редактировать', + 'common.add': 'Добавить', + 'common.loading': 'Загрузка...', + 'common.import': 'Импорт', + 'common.select': 'Выбрать', + 'common.selectAll': 'Выбрать всё', + 'common.deselectAll': 'Снять выделение со всех', + 'common.error': 'Ошибка', + 'common.unknownError': 'Неизвестная ошибка', + 'common.tooManyAttempts': 'Слишком много попыток. Попробуйте позже.', + 'common.back': 'Назад', + 'common.all': 'Все', + 'common.close': 'Закрыть', + 'common.open': 'Открыть', + 'common.upload': 'Загрузить', + 'common.search': 'Поиск', + 'common.confirm': 'Подтвердить', + 'common.ok': 'ОК', + 'common.yes': 'Да', + 'common.no': 'Нет', + 'common.or': 'или', + 'common.none': 'Нет', + 'common.date': 'Дата', + 'common.rename': 'Переименовать', + 'common.discardChanges': 'Отменить изменения', + 'common.discard': 'Отменить', + 'common.name': 'Имя', + 'common.email': 'Эл. почта', + 'common.password': 'Пароль', + 'common.saving': 'Сохранение...', + 'common.saved': 'Сохранено', + 'common.expand': 'Развернуть', + 'common.collapse': 'Свернуть', + 'common.update': 'Обновить', + 'common.change': 'Изменить', + 'common.uploading': 'Загрузка…', + 'common.backToPlanning': 'Вернуться к планированию', + 'common.reset': 'Сбросить', + 'common.copy': 'Копировать', + 'common.copied': 'Скопировано', + 'common.justNow': 'только что', + 'common.hoursAgo': '{count} ч назад', + 'common.daysAgo': '{count} д назад', +}; +export default common; diff --git a/shared/src/i18n/ru/dashboard.ts b/shared/src/i18n/ru/dashboard.ts new file mode 100644 index 00000000..0a924635 --- /dev/null +++ b/shared/src/i18n/ru/dashboard.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Мои поездки', + 'dashboard.subtitle.loading': 'Загрузка поездок...', + 'dashboard.subtitle.trips': '{count} поездок ({archived} в архиве)', + 'dashboard.subtitle.empty': 'Начните свою первую поездку', + 'dashboard.subtitle.activeOne': '{count} активная поездка', + 'dashboard.subtitle.activeMany': '{count} активных поездок', + 'dashboard.subtitle.archivedSuffix': ' · {count} в архиве', + 'dashboard.newTrip': 'Новая поездка', + 'dashboard.gridView': 'Плитка', + 'dashboard.listView': 'Список', + 'dashboard.currency': 'Валюта', + 'dashboard.timezone': 'Часовые пояса', + 'dashboard.localTime': 'Местное', + 'dashboard.timezoneCustomTitle': 'Свой часовой пояс', + 'dashboard.timezoneCustomLabelPlaceholder': 'Название (необязательно)', + 'dashboard.timezoneCustomTzPlaceholder': 'напр. America/New_York', + 'dashboard.timezoneCustomAdd': 'Добавить', + 'dashboard.timezoneCustomErrorEmpty': 'Введите идентификатор часового пояса', + 'dashboard.timezoneCustomErrorInvalid': + 'Неверный часовой пояс. Используйте формат Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Уже добавлен', + 'dashboard.emptyTitle': 'Нет поездок', + 'dashboard.emptyText': 'Создайте свою первую поездку и начните планировать!', + 'dashboard.emptyButton': 'Создать первую поездку', + 'dashboard.nextTrip': 'Следующая поездка', + 'dashboard.shared': 'Общая', + 'dashboard.sharedBy': 'Поделился {name}', + 'dashboard.days': 'Дни', + 'dashboard.places': 'Места', + 'dashboard.members': 'Попутчики', + 'dashboard.archive': 'Архивировать', + 'dashboard.copyTrip': 'Копировать', + 'dashboard.copySuffix': 'копия', + 'dashboard.restore': 'Восстановить', + 'dashboard.archived': 'В архиве', + 'dashboard.status.ongoing': 'В процессе', + 'dashboard.status.today': 'Сегодня', + 'dashboard.status.tomorrow': 'Завтра', + 'dashboard.status.past': 'Прошло', + 'dashboard.status.daysLeft': 'осталось {count} дн.', + 'dashboard.toast.loadError': 'Не удалось загрузить поездки', + 'dashboard.toast.created': 'Поездка создана!', + 'dashboard.toast.createError': 'Не удалось создать поездку', + 'dashboard.toast.updated': 'Поездка обновлена!', + 'dashboard.toast.updateError': 'Не удалось обновить поездку', + 'dashboard.toast.deleted': 'Поездка удалена', + 'dashboard.toast.deleteError': 'Не удалось удалить поездку', + 'dashboard.toast.archived': 'Поездка архивирована', + 'dashboard.toast.archiveError': 'Не удалось архивировать поездку', + 'dashboard.toast.restored': 'Поездка восстановлена', + 'dashboard.toast.restoreError': 'Не удалось восстановить поездку', + 'dashboard.toast.copied': 'Поездка скопирована!', + 'dashboard.toast.copyError': 'Не удалось скопировать поездку', + 'dashboard.confirm.delete': + 'Удалить поездку «{title}»? Все места и планы будут безвозвратно удалены.', + 'dashboard.editTrip': 'Редактировать поездку', + 'dashboard.createTrip': 'Создать новую поездку', + 'dashboard.tripTitle': 'Название', + 'dashboard.tripTitlePlaceholder': 'напр. Лето в Японии', + 'dashboard.tripDescription': 'Описание', + 'dashboard.tripDescriptionPlaceholder': 'О чём эта поездка?', + 'dashboard.startDate': 'Дата начала', + 'dashboard.endDate': 'Дата окончания', + 'dashboard.dayCount': 'Количество дней', + 'dashboard.dayCountHint': + 'Сколько дней планировать, если даты поездки не указаны.', + 'dashboard.noDateHint': + 'Дата не указана — будет создано 7 дней по умолчанию. Вы можете изменить это в любое время.', + 'dashboard.coverImage': 'Обложка', + 'dashboard.addCoverImage': 'Добавить обложку', + 'dashboard.addMembers': 'Попутчики', + 'dashboard.addMember': 'Добавить участника', + 'dashboard.coverSaved': 'Обложка сохранена', + 'dashboard.coverUploadError': 'Ошибка загрузки', + 'dashboard.coverRemoveError': 'Ошибка удаления', + 'dashboard.titleRequired': 'Название обязательно', + 'dashboard.endDateError': 'Дата окончания должна быть позже даты начала', + 'dashboard.greeting.morning': 'Доброе утро,', + 'dashboard.greeting.afternoon': 'Добрый день,', + 'dashboard.greeting.evening': 'Добрый вечер,', + 'dashboard.mobile.liveNow': 'Сейчас в пути', + 'dashboard.mobile.tripProgress': 'Прогресс поездки', + 'dashboard.mobile.daysLeft': 'осталось {count} дн.', + 'dashboard.mobile.places': 'Места', + 'dashboard.mobile.buddies': 'Попутчики', + 'dashboard.mobile.newTrip': 'Новая поездка', + 'dashboard.mobile.currency': 'Валюта', + 'dashboard.mobile.timezone': 'Часовой пояс', + 'dashboard.mobile.upcomingTrips': 'Предстоящие поездки', + 'dashboard.mobile.yourTrips': 'Ваши поездки', + 'dashboard.mobile.trips': 'поездок', + 'dashboard.mobile.starts': 'Начало', + 'dashboard.mobile.duration': 'Продолжительность', + 'dashboard.mobile.day': 'день', + 'dashboard.mobile.days': 'дней', + 'dashboard.mobile.ongoing': 'В процессе', + 'dashboard.mobile.startsToday': 'Начинается сегодня', + 'dashboard.mobile.tomorrow': 'Завтра', + 'dashboard.mobile.inDays': 'Через {count} дн.', + 'dashboard.mobile.inMonths': 'Через {count} мес.', + 'dashboard.mobile.completed': 'Завершено', + 'dashboard.mobile.currencyConverter': 'Конвертер валют', +}; +export default dashboard; diff --git a/shared/src/i18n/ru/day.ts b/shared/src/i18n/ru/day.ts new file mode 100644 index 00000000..39354320 --- /dev/null +++ b/shared/src/i18n/ru/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Вероятность осадков', + 'day.precipitation': 'Осадки', + 'day.wind': 'Ветер', + 'day.sunrise': 'Восход', + 'day.sunset': 'Закат', + 'day.hourlyForecast': 'Почасовой прогноз', + 'day.climateHint': + 'Исторические средние — реальный прогноз доступен за 16 дней до этой даты.', + 'day.noWeather': 'Данные о погоде недоступны. Добавьте место с координатами.', + 'day.overview': 'Обзор дня', + 'day.accommodation': 'Жильё', + 'day.addAccommodation': 'Добавить жильё', + 'day.hotelDayRange': 'Применить к дням', + 'day.noPlacesForHotel': 'Сначала добавьте места в поездку', + 'day.allDays': 'Все', + 'day.checkIn': 'Заезд', + 'day.checkInUntil': 'До', + 'day.checkOut': 'Выезд', + 'day.confirmation': 'Подтверждение', + 'day.editAccommodation': 'Редактировать жильё', + 'day.reservations': 'Бронирования', +}; +export default day; diff --git a/shared/src/i18n/ru/dayplan.ts b/shared/src/i18n/ru/dayplan.ts new file mode 100644 index 00000000..5ffb7fb6 --- /dev/null +++ b/shared/src/i18n/ru/dayplan.ts @@ -0,0 +1,46 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Экспорт календаря (ICS)', + 'dayplan.emptyDay': 'На этот день мест не запланировано', + 'dayplan.addNote': 'Добавить заметку', + 'dayplan.editNote': 'Редактировать заметку', + 'dayplan.noteAdd': 'Добавить заметку', + 'dayplan.noteEdit': 'Редактировать заметку', + 'dayplan.noteTitle': 'Заметка', + 'dayplan.noteSubtitle': 'Заметка на день', + 'dayplan.totalCost': 'Общая стоимость', + 'dayplan.days': 'Дни', + 'dayplan.dayN': 'День {n}', + 'dayplan.calculating': 'Расчёт...', + 'dayplan.route': 'Маршрут', + 'dayplan.optimize': 'Оптимизировать', + 'dayplan.optimized': 'Маршрут оптимизирован', + 'dayplan.routeError': 'Не удалось рассчитать маршрут', + 'dayplan.toast.needTwoPlaces': + 'Для оптимизации маршрута нужно минимум два места', + 'dayplan.toast.routeOptimized': 'Маршрут оптимизирован', + 'dayplan.toast.noGeoPlaces': + 'Не найдено мест с координатами для расчёта маршрута', + 'dayplan.confirmed': 'Подтверждено', + 'dayplan.pendingRes': 'Ожидание', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Экспортировать план дня в PDF', + 'dayplan.pdfError': 'Ошибка экспорта PDF', + 'dayplan.cannotReorderTransport': + 'Бронирования с фиксированным временем нельзя перемещать', + 'dayplan.confirmRemoveTimeTitle': 'Удалить время?', + 'dayplan.confirmRemoveTimeBody': + 'У этого места фиксированное время ({time}). При перемещении время будет удалено, и станет доступна свободная сортировка.', + 'dayplan.confirmRemoveTimeAction': 'Удалить время и переместить', + 'dayplan.cannotDropOnTimed': + 'Элементы нельзя размещать между записями с фиксированным временем', + 'dayplan.cannotBreakChronology': + 'Это нарушит хронологический порядок запланированных элементов и бронирований', + 'dayplan.mobile.addPlace': 'Добавить место', + 'dayplan.mobile.searchPlaces': 'Поиск мест...', + 'dayplan.mobile.allAssigned': 'Все места распределены', + 'dayplan.mobile.noMatch': 'Нет совпадений', + 'dayplan.mobile.createNew': 'Создать новое место', +}; +export default dayplan; diff --git a/shared/src/i18n/ru/externalNotifications.ts b/shared/src/i18n/ru/externalNotifications.ts new file mode 100644 index 00000000..4202da2d --- /dev/null +++ b/shared/src/i18n/ru/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const ru: NotificationLocale = { + email: { + footer: 'Вы получили это, потому что у вас включены уведомления в TREK.', + manage: 'Управление настройками', + madeWith: 'Made with', + openTrek: 'Открыть TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Приглашение в "${p.trip}"`, + body: `${p.actor} пригласил ${p.invitee || 'участника'} в поездку "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Новое бронирование: ${p.booking}`, + body: `${p.actor} добавил бронирование "${p.booking}" (${p.type}) в "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Напоминание: ${p.trip}`, + body: `Ваша поездка "${p.trip}" скоро начнётся!`, + }), + todo_due: (p) => ({ + title: `Задача к сроку: ${p.todo}`, + body: `"${p.todo}" в поездке "${p.trip}" — срок ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Приглашение Vacay Fusion', + body: `${p.actor} приглашает вас объединить планы отпуска. Откройте TREK для подтверждения.`, + }), + photos_shared: (p) => ({ + title: `${p.count} фото`, + body: `${p.actor} поделился ${p.count} фото в "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Новое сообщение в "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Список вещей: ${p.category}`, + body: `${p.actor} назначил вас в категорию "${p.category}" в "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Доступна новая версия TREK', + body: `TREK ${p.version} теперь доступен. Перейдите в панель администратора для обновления.`, + }), + synology_session_cleared: () => ({ + title: 'Сессия Synology сброшена', + body: 'Ваш аккаунт или URL Synology изменился. Вы вышли из Synology Photos.', + }), + }, + passwordReset: { + subject: 'Сброс пароля', + greeting: 'Здравствуйте', + body: 'Мы получили запрос на сброс пароля вашего аккаунта TREK. Нажмите кнопку ниже, чтобы установить новый пароль.', + ctaIntro: 'Сбросить пароль', + expiry: 'Ссылка действительна 60 минут.', + ignore: + 'Если вы не запрашивали сброс — просто проигнорируйте это письмо, пароль останется прежним.', + }, +}; + +export default ru; diff --git a/shared/src/i18n/ru/files.ts b/shared/src/i18n/ru/files.ts new file mode 100644 index 00000000..f7068069 --- /dev/null +++ b/shared/src/i18n/ru/files.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Файлы', + 'files.pageTitle': 'Файлы и документы', + 'files.subtitle': '{count} файлов для {trip}', + 'files.download': 'Скачать', + 'files.openError': 'Не удалось открыть файл', + 'files.downloadPdf': 'Скачать PDF', + 'files.count': '{count} файлов', + 'files.countSingular': '1 файл', + 'files.uploaded': '{count} загружено', + 'files.uploadError': 'Ошибка загрузки', + 'files.dropzone': 'Перетащите файлы сюда', + 'files.dropzoneHint': 'или нажмите для выбора', + 'files.allowedTypes': + 'Изображения, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Макс. 50 МБ', + 'files.uploading': 'Загрузка...', + 'files.filterAll': 'Все', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Изображения', + 'files.filterDocs': 'Документы', + 'files.filterCollab': 'Заметки Collab', + 'files.sourceCollab': 'Из заметок Collab', + 'files.empty': 'Файлов пока нет', + 'files.emptyHint': 'Загрузите файлы, чтобы прикрепить их к поездке', + 'files.openTab': 'Открыть в новой вкладке', + 'files.confirm.delete': 'Вы уверены, что хотите удалить этот файл?', + 'files.toast.deleted': 'Файл удалён', + 'files.toast.deleteError': 'Не удалось удалить файл', + 'files.sourcePlan': 'План дня', + 'files.sourceBooking': 'Бронирование', + 'files.sourceTransport': 'Транспорт', + 'files.attach': 'Прикрепить', + 'files.pasteHint': + 'Также можно вставить изображения из буфера обмена (Ctrl+V)', + 'files.trash': 'Корзина', + 'files.trashEmpty': 'Корзина пуста', + 'files.emptyTrash': 'Очистить корзину', + 'files.restore': 'Восстановить', + 'files.star': 'В избранное', + 'files.unstar': 'Из избранного', + 'files.assign': 'Назначить', + 'files.assignTitle': 'Назначить файл', + 'files.assignPlace': 'Место', + 'files.assignBooking': 'Бронирование', + 'files.assignTransport': 'Транспорт', + 'files.unassigned': 'Не назначен', + 'files.unlink': 'Удалить связь', + 'files.toast.trashed': 'Перемещено в корзину', + 'files.toast.restored': 'Файл восстановлен', + 'files.toast.trashEmptied': 'Корзина очищена', + 'files.toast.assigned': 'Файл назначен', + 'files.toast.assignError': 'Ошибка назначения', + 'files.toast.restoreError': 'Ошибка восстановления', + 'files.confirm.permanentDelete': + 'Безвозвратно удалить этот файл? Это действие нельзя отменить.', + 'files.confirm.emptyTrash': + 'Безвозвратно удалить все файлы из корзины? Это действие нельзя отменить.', + 'files.noteLabel': 'Заметка', + 'files.notePlaceholder': 'Добавить заметку...', +}; +export default files; diff --git a/shared/src/i18n/ru/index.ts b/shared/src/i18n/ru/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/ru/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/ru/inspector.ts b/shared/src/i18n/ru/inspector.ts new file mode 100644 index 00000000..51bd2aa1 --- /dev/null +++ b/shared/src/i18n/ru/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Открыто', + 'inspector.closed': 'Закрыто', + 'inspector.openingHours': 'Часы работы', + 'inspector.showHours': 'Показать часы работы', + 'inspector.files': 'Файлы', + 'inspector.filesCount': '{count} файлов', + 'inspector.removeFromDay': 'Убрать из дня', + 'inspector.remove': 'Удалить', + 'inspector.addToDay': 'Добавить в день', + 'inspector.confirmedRes': 'Подтверждённое бронирование', + 'inspector.pendingRes': 'Ожидающее бронирование', + 'inspector.google': 'Открыть в Google Maps', + 'inspector.website': 'Открыть сайт', + 'inspector.addRes': 'Бронирование', + 'inspector.editRes': 'Редактировать бронирование', + 'inspector.participants': 'Участники', + 'inspector.trackStats': 'Данные маршрута', +}; +export default inspector; diff --git a/shared/src/i18n/ru/journey.ts b/shared/src/i18n/ru/journey.ts new file mode 100644 index 00000000..f8ef154e --- /dev/null +++ b/shared/src/i18n/ru/journey.ts @@ -0,0 +1,240 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Поиск путешествий…', + 'journey.search.noResults': 'Путешествий по запросу «{query}» не найдено', + 'journey.title': 'Путешествие', + 'journey.subtitle': 'Отслеживайте свои путешествия в реальном времени', + 'journey.new': 'Новое путешествие', + 'journey.create': 'Создать', + 'journey.titlePlaceholder': 'Куда вы едете?', + 'journey.empty': 'Пока нет путешествий', + 'journey.emptyHint': 'Начните документировать свою следующую поездку', + 'journey.deleted': 'Путешествие удалено', + 'journey.createError': 'Не удалось создать путешествие', + 'journey.deleteError': 'Не удалось удалить путешествие', + 'journey.deleteConfirmTitle': 'Удалить', + 'journey.deleteConfirmMessage': + 'Удалить «{title}»? Это действие нельзя отменить.', + 'journey.deleteConfirmGeneric': 'Вы уверены, что хотите удалить это?', + 'journey.notFound': 'Путешествие не найдено', + 'journey.photos': 'Фото', + 'journey.timelineEmpty': 'Пока нет остановок', + 'journey.timelineEmptyHint': 'Добавьте отметку или напишите запись в дневник', + 'journey.status.draft': 'Черновик', + 'journey.status.active': 'Активно', + 'journey.status.completed': 'Завершено', + 'journey.status.upcoming': 'Предстоящее', + 'journey.status.archived': 'В архиве', + 'journey.checkin.add': 'Отметиться', + 'journey.checkin.namePlaceholder': 'Название места', + 'journey.checkin.notesPlaceholder': 'Заметки (необязательно)', + 'journey.checkin.save': 'Сохранить', + 'journey.checkin.error': 'Не удалось сохранить отметку', + 'journey.entry.add': 'Дневник', + 'journey.entry.edit': 'Редактировать запись', + 'journey.entry.titlePlaceholder': 'Заголовок (необязательно)', + 'journey.entry.bodyPlaceholder': 'Что произошло сегодня?', + 'journey.entry.save': 'Сохранить', + 'journey.entry.error': 'Не удалось сохранить запись', + 'journey.photo.add': 'Фото', + 'journey.photo.uploadError': 'Загрузка не удалась', + 'journey.share.share': 'Поделиться', + 'journey.share.public': 'Публичный', + 'journey.share.linkCopied': 'Публичная ссылка скопирована', + 'journey.share.disabled': 'Публичный доступ отключён', + 'journey.editor.titlePlaceholder': 'Дайте название этому моменту...', + 'journey.editor.bodyPlaceholder': 'Расскажите историю этого дня...', + 'journey.editor.placePlaceholder': 'Местоположение (необязательно)', + 'journey.editor.tagsPlaceholder': + 'Теги: скрытая жемчужина, лучшая еда, стоит вернуться...', + 'journey.visibility.private': 'Приватный', + 'journey.visibility.shared': 'Общий', + 'journey.visibility.public': 'Публичный', + 'journey.emptyState.title': 'Ваша история начинается здесь', + 'journey.emptyState.subtitle': + 'Отметьтесь в месте или напишите первую запись в дневник', + 'journey.frontpage.subtitle': + 'Превращайте поездки в истории, которые вы никогда не забудете', + 'journey.frontpage.createJourney': 'Создать путешествие', + 'journey.frontpage.activeJourney': 'Активное путешествие', + 'journey.frontpage.allJourneys': 'Все путешествия', + 'journey.frontpage.journeys': 'путешествий', + 'journey.frontpage.createNew': 'Создать новое путешествие', + 'journey.frontpage.createNewSub': + 'Выберите поездки, пишите истории, делитесь приключениями', + 'journey.frontpage.live': 'В эфире', + 'journey.frontpage.synced': 'Синхронизировано', + 'journey.frontpage.continueWriting': 'Продолжить писать', + 'journey.frontpage.updated': 'Обновлено {time}', + 'journey.frontpage.suggestionLabel': 'Поездка только что завершилась', + 'journey.frontpage.suggestionText': + 'Превратите {title} в путешествие', + 'journey.frontpage.dismiss': 'Скрыть', + 'journey.frontpage.journeyName': 'Название путешествия', + 'journey.frontpage.namePlaceholder': 'напр. Юго-Восточная Азия 2026', + 'journey.frontpage.selectTrips': 'Выбрать поездки', + 'journey.frontpage.tripsSelected': 'поездок выбрано', + 'journey.frontpage.trips': 'поездок', + 'journey.frontpage.placesImported': 'мест будет импортировано', + 'journey.frontpage.places': 'мест', + 'journey.detail.backToJourney': 'Назад к путешествию', + 'journey.detail.syncedWithTrips': 'Синхронизировано с поездками', + 'journey.detail.addEntry': 'Добавить запись', + 'journey.detail.newEntry': 'Новая запись', + 'journey.detail.editEntry': 'Редактировать запись', + 'journey.detail.noEntries': 'Пока нет записей', + 'journey.detail.noEntriesHint': + 'Добавьте поездку, чтобы начать с шаблонных записей', + 'journey.detail.noPhotos': 'Пока нет фото', + 'journey.detail.noPhotosHint': + 'Загрузите фото в записи или просмотрите библиотеку Immich/Synology', + 'journey.detail.journeyStats': 'Статистика путешествия', + 'journey.detail.syncedTrips': 'Синхронизированные поездки', + 'journey.detail.noTripsLinked': 'Пока нет привязанных поездок', + 'journey.detail.contributors': 'Участники', + 'journey.detail.readMore': 'Читать далее', + 'journey.detail.prosCons': 'Плюсы и минусы', + 'journey.detail.photos': 'фото', + 'journey.detail.day': 'День {number}', + 'journey.detail.places': 'мест', + 'journey.stats.days': 'Дней', + 'journey.stats.cities': 'Городов', + 'journey.stats.entries': 'Записей', + 'journey.stats.photos': 'Фото', + 'journey.stats.places': 'Мест', + 'journey.skeletons.show': 'Показать предложения', + 'journey.skeletons.hide': 'Скрыть предложения', + 'journey.verdict.lovedIt': 'Понравилось', + 'journey.verdict.couldBeBetter': 'Могло быть лучше', + 'journey.synced.places': 'мест', + 'journey.synced.synced': 'синхронизировано', + 'journey.editor.discardChangesConfirm': + 'У вас есть несохранённые изменения. Отменить?', + 'journey.editor.uploadFailed': 'Не удалось загрузить фото', + 'journey.editor.uploadPhotos': 'Загрузить фото', + 'journey.editor.uploading': 'Загрузка...', + 'journey.editor.uploadingProgress': 'Загрузка {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{failed} из {total} фото не удалось загрузить — сохраните снова для повтора', + 'journey.editor.fromGallery': 'Из галереи', + 'journey.editor.allPhotosAdded': 'Все фото уже добавлены', + 'journey.editor.writeStory': 'Напишите свою историю...', + 'journey.editor.prosCons': 'Плюсы и минусы', + 'journey.editor.pros': 'Плюсы', + 'journey.editor.cons': 'Минусы', + 'journey.editor.proPlaceholder': 'Что-то отличное...', + 'journey.editor.conPlaceholder': 'Не очень хорошо...', + 'journey.editor.addAnother': 'Добавить ещё', + 'journey.editor.date': 'Дата', + 'journey.editor.location': 'Местоположение', + 'journey.editor.searchLocation': 'Поиск местоположения...', + 'journey.editor.mood': 'Настроение', + 'journey.editor.weather': 'Погода', + 'journey.editor.photoFirst': '1-е', + 'journey.editor.makeFirst': 'Сделать 1-м', + 'journey.editor.searching': 'Поиск...', + 'journey.mood.amazing': 'Потрясающе', + 'journey.mood.good': 'Хорошо', + 'journey.mood.neutral': 'Нейтрально', + 'journey.mood.rough': 'Тяжело', + 'journey.weather.sunny': 'Солнечно', + 'journey.weather.partly': 'Переменная облачность', + 'journey.weather.cloudy': 'Облачно', + 'journey.weather.rainy': 'Дождливо', + 'journey.weather.stormy': 'Гроза', + 'journey.weather.cold': 'Снежно', + 'journey.trips.linkTrip': 'Привязать поездку', + 'journey.trips.searchTrip': 'Поиск поездки', + 'journey.trips.searchPlaceholder': 'Название поездки или направление...', + 'journey.trips.noTripsAvailable': 'Нет доступных поездок', + 'journey.trips.link': 'Привязать', + 'journey.trips.tripLinked': 'Поездка привязана', + 'journey.trips.linkFailed': 'Не удалось привязать поездку', + 'journey.trips.addTrip': 'Добавить поездку', + 'journey.trips.unlinkTrip': 'Отвязать поездку', + 'journey.trips.unlinkMessage': + 'Отвязать «{title}»? Все синхронизированные записи и фото из этой поездки будут безвозвратно удалены. Это действие нельзя отменить.', + 'journey.trips.unlink': 'Отвязать', + 'journey.trips.tripUnlinked': 'Поездка отвязана', + 'journey.trips.unlinkFailed': 'Не удалось отвязать поездку', + 'journey.trips.noTripsLinkedSettings': 'Нет привязанных поездок', + 'journey.contributors.invite': 'Пригласить участника', + 'journey.contributors.searchUser': 'Поиск пользователя', + 'journey.contributors.searchPlaceholder': 'Имя пользователя или email...', + 'journey.contributors.noUsers': 'Пользователи не найдены', + 'journey.contributors.role': 'Роль', + 'journey.contributors.added': 'Участник добавлен', + 'journey.contributors.addFailed': 'Не удалось добавить участника', + 'journey.share.publicShare': 'Публичный доступ', + 'journey.share.createLink': 'Создать ссылку для общего доступа', + 'journey.share.linkCreated': 'Ссылка создана', + 'journey.share.createFailed': 'Не удалось создать ссылку', + 'journey.share.copy': 'Копировать', + 'journey.share.copied': 'Скопировано!', + 'journey.share.timeline': 'Хронология', + 'journey.share.gallery': 'Галерея', + 'journey.share.map': 'Карта', + 'journey.share.removeLink': 'Удалить ссылку', + 'journey.share.linkDeleted': 'Ссылка удалена', + 'journey.share.deleteFailed': 'Не удалось удалить', + 'journey.share.updateFailed': 'Не удалось обновить', + 'journey.invite.role': 'Роль', + 'journey.invite.viewer': 'Наблюдатель', + 'journey.invite.editor': 'Редактор', + 'journey.invite.invite': 'Пригласить', + 'journey.invite.inviting': 'Приглашаем...', + 'journey.settings.title': 'Настройки путешествия', + 'journey.settings.coverImage': 'Обложка', + 'journey.settings.changeCover': 'Сменить обложку', + 'journey.settings.addCover': 'Добавить обложку', + 'journey.settings.name': 'Название', + 'journey.settings.subtitle': 'Подзаголовок', + 'journey.settings.subtitlePlaceholder': 'напр. Таиланд, Вьетнам и Камбоджа', + 'journey.settings.endJourney': 'Архивировать путешествие', + 'journey.settings.reopenJourney': 'Восстановить путешествие', + 'journey.settings.archived': 'Путешествие архивировано', + 'journey.settings.reopened': 'Путешествие возобновлено', + 'journey.settings.endDescription': + 'Скрывает значок «В эфире». Вы можете возобновить в любое время.', + 'journey.settings.delete': 'Удалить', + 'journey.settings.deleteJourney': 'Удалить путешествие', + 'journey.settings.deleteMessage': + 'Удалить «{title}»? Все записи и фото будут потеряны.', + 'journey.settings.saved': 'Настройки сохранены', + 'journey.settings.saveFailed': 'Не удалось сохранить', + 'journey.settings.coverUpdated': 'Обложка обновлена', + 'journey.settings.coverFailed': 'Загрузка не удалась', + 'journey.settings.failedToDelete': 'Не удалось удалить', + 'journey.entries.deleteTitle': 'Удалить запись', + 'journey.photosUploaded': '{count} фото загружено', + 'journey.photosUploadFailed': 'Некоторые фото не удалось загрузить', + 'journey.photosAdded': '{count} фото добавлено', + 'journey.public.notFound': 'Не найдено', + 'journey.public.notFoundMessage': + 'Это путешествие не существует или ссылка устарела.', + 'journey.public.readOnly': 'Только для чтения · Публичное путешествие', + 'journey.public.tagline': + 'Инструмент планирования и исследования путешествий', + 'journey.public.sharedVia': 'Опубликовано через', + 'journey.public.madeWith': 'Сделано с помощью', + 'journey.pdf.journeyBook': 'Книга путешествия', + 'journey.pdf.madeWith': 'Сделано с помощью TREK', + 'journey.pdf.day': 'День', + 'journey.pdf.theEnd': 'Конец', + 'journey.pdf.saveAsPdf': 'Сохранить как PDF', + 'journey.pdf.pages': 'страниц', + 'journey.picker.tripPeriod': 'Период поездки', + 'journey.picker.dateRange': 'Диапазон дат', + 'journey.picker.allPhotos': 'Все фото', + 'journey.picker.albums': 'Альбомы', + 'journey.picker.selected': 'выбрано', + 'journey.picker.addTo': 'Добавить в', + 'journey.picker.newGallery': 'Новая галерея', + 'journey.picker.selectAll': 'Выбрать все', + 'journey.picker.deselectAll': 'Снять выбор', + 'journey.picker.noAlbums': 'Альбомы не найдены', + 'journey.picker.selectDate': 'Выберите дату', + 'journey.picker.search': 'Поиск', +}; +export default journey; diff --git a/shared/src/i18n/ru/login.ts b/shared/src/i18n/ru/login.ts new file mode 100644 index 00000000..7e17c63d --- /dev/null +++ b/shared/src/i18n/ru/login.ts @@ -0,0 +1,97 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Ошибка входа. Проверьте свои учётные данные.', + 'login.tagline': 'Ваши поездки.\nВаш план.', + 'login.description': + 'Планируйте поездки совместно с интерактивными картами, бюджетами и синхронизацией в реальном времени.', + 'login.features.maps': 'Интерактивные карты', + 'login.features.mapsDesc': 'Google Places, маршруты и кластеризация', + 'login.features.realtime': 'Синхронизация в реальном времени', + 'login.features.realtimeDesc': 'Планируйте вместе через WebSocket', + 'login.features.budget': 'Контроль бюджета', + 'login.features.budgetDesc': 'Категории, графики и расходы на человека', + 'login.features.collab': 'Совместная работа', + 'login.features.collabDesc': 'Многопользовательский режим с общими поездками', + 'login.features.packing': 'Списки вещей', + 'login.features.packingDesc': 'Категории, прогресс и подсказки', + 'login.features.bookings': 'Бронирования', + 'login.features.bookingsDesc': 'Авиабилеты, отели, рестораны и многое другое', + 'login.features.files': 'Документы', + 'login.features.filesDesc': 'Загружайте и управляйте документами', + 'login.features.routes': 'Умные маршруты', + 'login.features.routesDesc': 'Автооптимизация и экспорт в Google Maps', + 'login.selfHosted': + 'Самостоятельный хостинг · Открытый код · Ваши данные остаются у вас', + 'login.title': 'Вход', + 'login.subtitle': 'С возвращением', + 'login.signingIn': 'Вход…', + 'login.signIn': 'Войти', + 'login.createAdmin': 'Создать аккаунт администратора', + 'login.createAdminHint': 'Настройте первый аккаунт администратора для TREK.', + 'login.setNewPassword': 'Установить новый пароль', + 'login.setNewPasswordHint': + 'Вы должны сменить пароль, прежде чем продолжить.', + 'login.createAccount': 'Создать аккаунт', + 'login.createAccountHint': 'Зарегистрируйте новый аккаунт.', + 'login.creating': 'Создание…', + 'login.noAccount': 'Нет аккаунта?', + 'login.hasAccount': 'Уже есть аккаунт?', + 'login.register': 'Регистрация', + 'login.emailPlaceholder': 'ваш@email.com', + 'login.username': 'Имя пользователя', + 'login.oidc.registrationDisabled': + 'Регистрация отключена. Обратитесь к администратору.', + 'login.oidc.noEmail': 'Провайдер не предоставил адрес эл. почты.', + 'login.mfaTitle': 'Двухфакторная аутентификация', + 'login.mfaSubtitle': 'Введите 6-значный код из приложения-аутентификатора.', + 'login.mfaCodeLabel': 'Код подтверждения', + 'login.mfaCodeRequired': 'Введите код из приложения-аутентификатора.', + 'login.mfaHint': + 'Откройте Google Authenticator, Authy или другое TOTP-приложение.', + 'login.mfaBack': '← Назад к входу', + 'login.mfaVerify': 'Подтвердить', + 'login.invalidInviteLink': 'Недействительная или истёкшая ссылка-приглашение', + 'login.oidcFailed': 'Ошибка входа через OIDC', + 'login.usernameRequired': 'Имя пользователя обязательно', + 'login.passwordMinLength': 'Пароль должен содержать не менее 8 символов', + 'login.forgotPassword': 'Забыли пароль?', + 'login.forgotPasswordTitle': 'Сброс пароля', + 'login.forgotPasswordBody': + 'Введите e-mail, с которым вы регистрировались. Если аккаунт найдём — отправим ссылку для сброса.', + 'login.forgotPasswordSubmit': 'Отправить ссылку', + 'login.forgotPasswordSentTitle': 'Проверьте почту', + 'login.forgotPasswordSentBody': + 'Если аккаунт существует, ссылка для сброса уже летит к вам. Она действительна 60 минут.', + 'login.forgotPasswordSmtpHintOff': + 'Обратите внимание: администратор не настроил SMTP, поэтому ссылка для сброса будет записана в консоль сервера, а не отправлена по почте.', + 'login.backToLogin': 'Вернуться ко входу', + 'login.newPassword': 'Новый пароль', + 'login.confirmPassword': 'Подтвердите новый пароль', + 'login.passwordsDontMatch': 'Пароли не совпадают', + 'login.mfaCode': 'Код 2FA', + 'login.resetPasswordTitle': 'Задайте новый пароль', + 'login.resetPasswordBody': + 'Выберите надёжный пароль, который вы здесь ещё не использовали. Минимум 8 символов.', + 'login.resetPasswordMfaBody': + 'Введите код 2FA или резервный код, чтобы завершить сброс.', + 'login.resetPasswordSubmit': 'Сбросить пароль', + 'login.resetPasswordVerify': 'Проверить и сбросить', + 'login.resetPasswordSuccessTitle': 'Пароль обновлён', + 'login.resetPasswordSuccessBody': 'Теперь вы можете войти с новым паролем.', + 'login.resetPasswordInvalidLink': 'Неверная ссылка сброса', + 'login.resetPasswordInvalidLinkBody': + 'Ссылка отсутствует или повреждена. Запросите новую, чтобы продолжить.', + 'login.resetPasswordFailed': + 'Сброс не удался. Возможно, срок действия ссылки истёк.', + 'login.oidc.tokenFailed': 'Аутентификация не удалась.', + 'login.oidc.invalidState': 'Недействительная сессия. Попробуйте снова.', + 'login.demoFailed': 'Ошибка демо-входа', + 'login.oidcSignIn': 'Войти через {name}', + 'login.oidcOnly': + 'Вход по паролю отключён. Используйте вашего провайдера SSO для входа.', + 'login.oidcLoggedOut': + 'Вы вышли из системы. Войдите снова через вашего провайдера SSO.', + 'login.demoHint': 'Попробуйте демо — регистрация не требуется', +}; +export default login; diff --git a/shared/src/i18n/ru/map.ts b/shared/src/i18n/ru/map.ts new file mode 100644 index 00000000..67751f55 --- /dev/null +++ b/shared/src/i18n/ru/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Соединения', + 'map.showConnections': 'Показать маршруты бронирований', + 'map.hideConnections': 'Скрыть маршруты бронирований', +}; +export default map; diff --git a/shared/src/i18n/ru/members.ts b/shared/src/i18n/ru/members.ts new file mode 100644 index 00000000..237d0556 --- /dev/null +++ b/shared/src/i18n/ru/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Поделиться поездкой', + 'members.inviteUser': 'Пригласить пользователя', + 'members.selectUser': 'Выберите пользователя…', + 'members.invite': 'Пригласить', + 'members.allHaveAccess': 'У всех пользователей уже есть доступ.', + 'members.access': 'Доступ', + 'members.person': 'человек', + 'members.persons': 'человек', + 'members.you': 'вы', + 'members.owner': 'Владелец', + 'members.leaveTrip': 'Покинуть поездку', + 'members.removeAccess': 'Отозвать доступ', + 'members.confirmLeave': 'Покинуть поездку? Вы потеряете доступ.', + 'members.confirmRemove': 'Отозвать доступ у этого пользователя?', + 'members.loadError': 'Не удалось загрузить участников', + 'members.added': 'добавлен', + 'members.addError': 'Ошибка добавления', + 'members.removed': 'Участник удалён', + 'members.removeError': 'Ошибка удаления', +}; +export default members; diff --git a/shared/src/i18n/ru/memories.ts b/shared/src/i18n/ru/memories.ts new file mode 100644 index 00000000..6d375f36 --- /dev/null +++ b/shared/src/i18n/ru/memories.ts @@ -0,0 +1,80 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Фото', + 'memories.notConnected': 'Immich не подключён', + 'memories.notConnectedHint': + 'Подключите Immich в настройках, чтобы видеть фотографии из поездок.', + 'memories.notConnectedMultipleHint': + 'Подключите одного из этих фотопровайдеров: {provider_names} в Настройках, чтобы добавлять фотографии к этому путешествию.', + 'memories.noDates': 'Добавьте даты поездки для загрузки фотографий.', + 'memories.noPhotos': 'Фотографии не найдены', + 'memories.noPhotosHint': 'В Immich нет фотографий за период этой поездки.', + 'memories.photosFound': 'фото', + 'memories.fromOthers': 'от других', + 'memories.sharePhotos': 'Поделиться фото', + 'memories.sharing': 'Общий доступ', + 'memories.reviewTitle': 'Проверьте ваши фото', + 'memories.reviewHint': + 'Нажмите на фото, чтобы исключить его из общего доступа.', + 'memories.shareCount': 'Поделиться ({count} фото)', + 'memories.providerUrl': 'URL сервера', + 'memories.providerApiKey': 'API-ключ', + 'memories.providerUsername': 'Имя пользователя', + 'memories.providerPassword': 'Пароль', + 'memories.providerOTP': 'Код MFA (если включён)', + 'memories.skipSSLVerification': 'Пропустить проверку SSL-сертификата', + 'memories.immichAutoUpload': 'Дублировать фото journey в Immich при загрузке', + 'memories.providerUrlHintSynology': + 'Включите путь приложения Photos в URL, например https://nas:5001/photo', + 'memories.testConnection': 'Проверить подключение', + 'memories.testShort': 'Проверить', + 'memories.testFirst': 'Сначала проверьте подключение', + 'memories.connected': 'Подключено', + 'memories.disconnected': 'Не подключено', + 'memories.connectionSuccess': 'Подключение к Immich установлено', + 'memories.connectionError': 'Не удалось подключиться к Immich', + 'memories.saved': 'Настройки {provider_name} сохранены', + 'memories.providerDisconnectedBanner': + 'Соединение с {provider_name} потеряно. Переподключитесь в Настройках для просмотра фотографий.', + 'memories.saveError': 'Не удалось сохранить настройки {provider_name}', + 'memories.oldest': 'Сначала старые', + 'memories.newest': 'Сначала новые', + 'memories.allLocations': 'Все места', + 'memories.addPhotos': 'Добавить фото', + 'memories.linkAlbum': 'Привязать альбом', + 'memories.selectAlbum': 'Выбрать альбом Immich', + 'memories.selectAlbumMultiple': 'Выбрать альбом', + 'memories.noAlbums': 'Альбомы не найдены', + 'memories.syncAlbum': 'Синхронизировать', + 'memories.unlinkAlbum': 'Отвязать', + 'memories.photos': 'фото', + 'memories.selectPhotos': 'Выбрать фото из Immich', + 'memories.selectPhotosMultiple': 'Выбрать фотографии', + 'memories.selectHint': 'Нажмите на фото, чтобы выбрать их.', + 'memories.selected': 'выбрано', + 'memories.addSelected': 'Добавить {count} фото', + 'memories.alreadyAdded': 'Добавлено', + 'memories.private': 'Приватное', + 'memories.stopSharing': 'Прекратить доступ', + 'memories.tripDates': 'Даты поездки', + 'memories.allPhotos': 'Все фото', + 'memories.confirmShareTitle': 'Поделиться с участниками поездки?', + 'memories.confirmShareHint': + '{count} фото станут видны всем участникам этой поездки. Вы сможете сделать отдельные фото приватными позже.', + 'memories.confirmShareButton': 'Поделиться фото', + 'memories.error.loadAlbums': 'Не удалось загрузить альбомы', + 'memories.error.linkAlbum': 'Не удалось привязать альбом', + 'memories.error.unlinkAlbum': 'Не удалось отвязать альбом', + 'memories.error.syncAlbum': 'Не удалось синхронизировать альбом', + 'memories.error.loadPhotos': 'Не удалось загрузить фотографии', + 'memories.error.addPhotos': 'Не удалось добавить фотографии', + 'memories.error.removePhoto': 'Не удалось удалить фотографию', + 'memories.error.toggleSharing': 'Не удалось обновить настройки доступа', + 'memories.saveRouteNotConfigured': + 'Маршрут сохранения не настроен для этого провайдера', + 'memories.testRouteNotConfigured': + 'Маршрут тестирования не настроен для этого провайдера', + 'memories.fillRequiredFields': 'Пожалуйста, заполните все обязательные поля', +}; +export default memories; diff --git a/shared/src/i18n/ru/nav.ts b/shared/src/i18n/ru/nav.ts new file mode 100644 index 00000000..c5f9a462 --- /dev/null +++ b/shared/src/i18n/ru/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Поездка', + 'nav.share': 'Поделиться', + 'nav.settings': 'Настройки', + 'nav.admin': 'Админ', + 'nav.logout': 'Выйти', + 'nav.lightMode': 'Светлая тема', + 'nav.darkMode': 'Тёмная тема', + 'nav.autoMode': 'Авто', + 'nav.administrator': 'Администратор', + 'nav.myTrips': 'Мои поездки', + 'nav.profile': 'Профиль', + 'nav.bottomSettings': 'Настройки', + 'nav.bottomAdmin': 'Администрирование', + 'nav.bottomLogout': 'Выйти', + 'nav.bottomAdminBadge': 'Админ', +}; +export default nav; diff --git a/shared/src/i18n/ru/notif.ts b/shared/src/i18n/ru/notif.ts new file mode 100644 index 00000000..33d89d76 --- /dev/null +++ b/shared/src/i18n/ru/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Тест] Уведомление', + 'notif.test.simple.text': 'Это простое тестовое уведомление.', + 'notif.test.boolean.text': 'Вы принимаете это тестовое уведомление?', + 'notif.test.navigate.text': 'Нажмите ниже для перехода на панель управления.', + 'notif.trip_invite.title': 'Приглашение в поездку', + 'notif.trip_invite.text': '{actor} пригласил вас в {trip}', + 'notif.booking_change.title': 'Бронирование обновлено', + 'notif.booking_change.text': '{actor} обновил бронирование в {trip}', + 'notif.trip_reminder.title': 'Напоминание о поездке', + 'notif.trip_reminder.text': 'Ваша поездка {trip} скоро начнётся!', + 'notif.todo_due.title': 'Задача к сроку', + 'notif.todo_due.text': '{todo} в {trip} — срок {due}', + 'notif.vacay_invite.title': 'Приглашение Vacay Fusion', + 'notif.vacay_invite.text': '{actor} приглашает вас объединить планы отпуска', + 'notif.photos_shared.title': 'Фото опубликованы', + 'notif.photos_shared.text': '{actor} поделился {count} фото в {trip}', + 'notif.collab_message.title': 'Новое сообщение', + 'notif.collab_message.text': '{actor} отправил сообщение в {trip}', + 'notif.packing_tagged.title': 'Задание для упаковки', + 'notif.packing_tagged.text': '{actor} назначил вас в {category} в {trip}', + 'notif.version_available.title': 'Доступна новая версия', + 'notif.version_available.text': 'TREK {version} теперь доступен', + 'notif.action.view_trip': 'Открыть поездку', + 'notif.action.view_collab': 'Открыть сообщения', + 'notif.action.view_packing': 'Открыть упаковку', + 'notif.action.view_photos': 'Открыть фото', + 'notif.action.view_vacay': 'Открыть Vacay', + 'notif.action.view_admin': 'Перейти в админ', + 'notif.action.view': 'Открыть', + 'notif.action.accept': 'Принять', + 'notif.action.decline': 'Отклонить', + 'notif.generic.title': 'Уведомление', + 'notif.generic.text': 'У вас новое уведомление', + 'notif.dev.unknown_event.title': '[DEV] Неизвестное событие', + 'notif.dev.unknown_event.text': + 'Тип события "{event}" не зарегистрирован в EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/ru/notifications.ts b/shared/src/i18n/ru/notifications.ts new file mode 100644 index 00000000..c9ed124d --- /dev/null +++ b/shared/src/i18n/ru/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Уведомления', + 'notifications.markAllRead': 'Отметить все прочитанными', + 'notifications.deleteAll': 'Удалить все', + 'notifications.showAll': 'Показать все уведомления', + 'notifications.empty': 'Нет уведомлений', + 'notifications.emptyDescription': 'Вы в курсе всех событий!', + 'notifications.all': 'Все', + 'notifications.unreadOnly': 'Непрочитанные', + 'notifications.markRead': 'Отметить как прочитанное', + 'notifications.markUnread': 'Отметить как непрочитанное', + 'notifications.delete': 'Удалить', + 'notifications.system': 'Система', + 'notifications.synologySessionCleared.title': 'Synology Photos отключено', + 'notifications.synologySessionCleared.text': + 'Ваш сервер или аккаунт изменился — перейдите в Настройки, чтобы проверить соединение снова.', + 'notifications.test.title': 'Тестовое уведомление от {actor}', + 'notifications.test.text': 'Это простое тестовое уведомление.', + 'notifications.test.booleanTitle': '{actor} запрашивает подтверждение', + 'notifications.test.booleanText': 'Тестовое уведомление с выбором.', + 'notifications.test.accept': 'Подтвердить', + 'notifications.test.decline': 'Отклонить', + 'notifications.test.navigateTitle': 'Посмотрите на это', + 'notifications.test.navigateText': 'Тестовое уведомление с переходом.', + 'notifications.test.goThere': 'Перейти', + 'notifications.test.adminTitle': 'Рассылка администратора', + 'notifications.test.adminText': + '{actor} отправил тестовое уведомление всем администраторам.', + 'notifications.test.tripTitle': '{actor} написал в вашей поездке', + 'notifications.test.tripText': 'Тестовое уведомление для поездки "{trip}".', + 'notifications.versionAvailable.title': 'Доступно обновление', + 'notifications.versionAvailable.text': 'TREK {version} теперь доступен.', + 'notifications.versionAvailable.button': 'Подробнее', +}; +export default notifications; diff --git a/shared/src/i18n/ru/oauth.ts b/shared/src/i18n/ru/oauth.ts new file mode 100644 index 00000000..8454d68c --- /dev/null +++ b/shared/src/i18n/ru/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Поездки', + 'oauth.scope.group.places': 'Места', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Вещи', + 'oauth.scope.group.todos': 'Задачи', + 'oauth.scope.group.budget': 'Бюджет', + 'oauth.scope.group.reservations': 'Бронирования', + 'oauth.scope.group.collab': 'Сотрудничество', + 'oauth.scope.group.notifications': 'Уведомления', + 'oauth.scope.group.vacay': 'Отпуск', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Погода', + 'oauth.scope.group.journey': 'Путешествия', + 'oauth.scope.trips:read.label': 'Просмотр поездок и маршрутов', + 'oauth.scope.trips:read.description': + 'Чтение поездок, дней, заметок и участников', + 'oauth.scope.trips:write.label': 'Редактирование поездок и маршрутов', + 'oauth.scope.trips:write.description': + 'Создание и обновление поездок, дней, заметок и управление участниками', + 'oauth.scope.trips:delete.label': 'Удаление поездок', + 'oauth.scope.trips:delete.description': + 'Безвозвратное удаление поездок — это действие необратимо', + 'oauth.scope.trips:share.label': 'Управление ссылками на совместный доступ', + 'oauth.scope.trips:share.description': + 'Создание, обновление и отзыв публичных ссылок на поездки', + 'oauth.scope.places:read.label': 'Просмотр мест и данных карты', + 'oauth.scope.places:read.description': + 'Чтение мест, назначений по дням, тегов и категорий', + 'oauth.scope.places:write.label': 'Управление местами', + 'oauth.scope.places:write.description': + 'Создание, обновление и удаление мест, назначений и тегов', + 'oauth.scope.atlas:read.label': 'Просмотр Atlas', + 'oauth.scope.atlas:read.description': + 'Чтение посещённых стран, регионов и списка желаний', + 'oauth.scope.atlas:write.label': 'Управление Atlas', + 'oauth.scope.atlas:write.description': + 'Отмечать посещённые страны и регионы, управлять списком желаний', + 'oauth.scope.packing:read.label': 'Просмотр списков вещей', + 'oauth.scope.packing:read.description': + 'Чтение вещей, сумок и назначений категорий', + 'oauth.scope.packing:write.label': 'Управление списками вещей', + 'oauth.scope.packing:write.description': + 'Добавление, обновление, удаление, отметка и переупорядочивание вещей и сумок', + 'oauth.scope.todos:read.label': 'Просмотр списков задач', + 'oauth.scope.todos:read.description': + 'Чтение задач поездки и назначений категорий', + 'oauth.scope.todos:write.label': 'Управление списками задач', + 'oauth.scope.todos:write.description': + 'Создание, обновление, отметка, удаление и переупорядочивание задач', + 'oauth.scope.budget:read.label': 'Просмотр бюджета', + 'oauth.scope.budget:read.description': + 'Чтение статей бюджета и разбивки расходов', + 'oauth.scope.budget:write.label': 'Управление бюджетом', + 'oauth.scope.budget:write.description': + 'Создание, обновление и удаление статей бюджета', + 'oauth.scope.reservations:read.label': 'Просмотр бронирований', + 'oauth.scope.reservations:read.description': + 'Чтение бронирований и сведений о проживании', + 'oauth.scope.reservations:write.label': 'Управление бронированиями', + 'oauth.scope.reservations:write.description': + 'Создание, обновление, удаление и переупорядочивание бронирований', + 'oauth.scope.collab:read.label': 'Просмотр совместной работы', + 'oauth.scope.collab:read.description': + 'Чтение совместных заметок, опросов и сообщений', + 'oauth.scope.collab:write.label': 'Управление совместной работой', + 'oauth.scope.collab:write.description': + 'Создание, обновление и удаление заметок, опросов и сообщений', + 'oauth.scope.notifications:read.label': 'Просмотр уведомлений', + 'oauth.scope.notifications:read.description': + 'Чтение уведомлений в приложении и количества непрочитанных', + 'oauth.scope.notifications:write.label': 'Управление уведомлениями', + 'oauth.scope.notifications:write.description': + 'Отмечать уведомления как прочитанные и отвечать на них', + 'oauth.scope.vacay:read.label': 'Просмотр планов отпуска', + 'oauth.scope.vacay:read.description': + 'Чтение данных планирования отпуска, записей и статистики', + 'oauth.scope.vacay:write.label': 'Управление планами отпуска', + 'oauth.scope.vacay:write.description': + 'Создание и управление записями отпуска, праздниками и командными планами', + 'oauth.scope.geo:read.label': 'Карты и геокодирование', + 'oauth.scope.geo:read.description': + 'Поиск мест, разрешение URL карт и обратное геокодирование координат', + 'oauth.scope.weather:read.label': 'Прогнозы погоды', + 'oauth.scope.weather:read.description': + 'Получение прогнозов погоды для мест и дат поездки', + 'oauth.scope.journey:read.label': 'Просмотр путешествий', + 'oauth.scope.journey:read.description': + 'Чтение путешествий, записей и списка участников', + 'oauth.scope.journey:write.label': 'Управление путешествиями', + 'oauth.scope.journey:write.description': + 'Создание, обновление и удаление путешествий и их записей', + 'oauth.scope.journey:share.label': 'Управление ссылками на путешествия', + 'oauth.scope.journey:share.description': + 'Создание, обновление и отзыв публичных ссылок для путешествий', +}; +export default oauth; diff --git a/shared/src/i18n/ru/packing.ts b/shared/src/i18n/ru/packing.ts new file mode 100644 index 00000000..a6ca1892 --- /dev/null +++ b/shared/src/i18n/ru/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Список вещей', + 'packing.empty': 'Список вещей пуст', + 'packing.import': 'Импорт', + 'packing.importTitle': 'Импорт списка вещей', + 'packing.importHint': + 'Один предмет на строку. Категория и количество — через запятую, точку с запятой или табуляцию: Название, Категория, Количество', + 'packing.importPlaceholder': + 'Зубная щётка\nСолнцезащитный крем, Гигиена\nФутболки, Одежда, 5\nПаспорт, Документы', + 'packing.importCsv': 'Загрузить CSV/TXT', + 'packing.importAction': 'Импортировать {count}', + 'packing.importSuccess': '{count} предметов импортировано', + 'packing.importError': 'Ошибка импорта', + 'packing.importEmpty': 'Нет предметов для импорта', + 'packing.progress': '{packed} из {total} собрано ({percent}%)', + 'packing.clearChecked': 'Удалить {count} отмеченных', + 'packing.clearCheckedShort': 'Удалить {count}', + 'packing.suggestions': 'Подсказки', + 'packing.suggestionsTitle': 'Добавить подсказки', + 'packing.allSuggested': 'Все подсказки добавлены', + 'packing.allPacked': 'Всё собрано!', + 'packing.addPlaceholder': 'Добавить вещь...', + 'packing.categoryPlaceholder': 'Категория...', + 'packing.filterAll': 'Все', + 'packing.filterOpen': 'Не собрано', + 'packing.filterDone': 'Собрано', + 'packing.emptyTitle': 'Список вещей пуст', + 'packing.emptyHint': 'Добавьте вещи или используйте подсказки', + 'packing.emptyFiltered': 'Нет вещей, соответствующих фильтру', + 'packing.menuRename': 'Переименовать', + 'packing.menuCheckAll': 'Отметить все', + 'packing.menuUncheckAll': 'Снять отметки', + 'packing.menuDeleteCat': 'Удалить категорию', + 'packing.addItem': 'Добавить вещь', + 'packing.addItemPlaceholder': 'Название...', + 'packing.addCategory': 'Добавить категорию', + 'packing.newCategoryPlaceholder': 'Название категории (напр. Одежда)', + 'packing.applyTemplate': 'Применить шаблон', + 'packing.template': 'Шаблон', + 'packing.templateApplied': '{count} вещей добавлено из шаблона', + 'packing.templateError': 'Ошибка применения шаблона', + 'packing.saveAsTemplate': 'Сохранить как шаблон', + 'packing.templateName': 'Название шаблона', + 'packing.templateSaved': 'Список вещей сохранён как шаблон', + 'packing.noMembers': 'Нет участников', + 'packing.bags': 'Багаж', + 'packing.noBag': 'Не назначено', + 'packing.totalWeight': 'Общий вес', + 'packing.bagName': 'Название...', + 'packing.addBag': 'Добавить багаж', + 'packing.changeCategory': 'Изменить категорию', + 'packing.confirm.clearChecked': + 'Вы уверены, что хотите удалить {count} отмеченных вещей?', + 'packing.confirm.deleteCat': + 'Вы уверены, что хотите удалить категорию «{name}» с {count} вещами?', + 'packing.defaultCategory': 'Другое', + 'packing.toast.saveError': 'Ошибка сохранения', + 'packing.toast.deleteError': 'Ошибка удаления', + 'packing.toast.renameError': 'Ошибка переименования', + 'packing.toast.addError': 'Ошибка добавления', + 'packing.suggestions.items': [ + { + name: 'Паспорт', + category: 'Документы', + }, + { + name: 'Удостоверение личности', + category: 'Документы', + }, + { + name: 'Страховка', + category: 'Документы', + }, + { + name: 'Авиабилеты', + category: 'Документы', + }, + { + name: 'Банковская карта', + category: 'Финансы', + }, + { + name: 'Наличные', + category: 'Финансы', + }, + { + name: 'Виза', + category: 'Документы', + }, + { + name: 'Футболки', + category: 'Одежда', + }, + { + name: 'Брюки', + category: 'Одежда', + }, + { + name: 'Нижнее бельё', + category: 'Одежда', + }, + { + name: 'Носки', + category: 'Одежда', + }, + { + name: 'Куртка', + category: 'Одежда', + }, + { + name: 'Пижама', + category: 'Одежда', + }, + { + name: 'Купальник', + category: 'Одежда', + }, + { + name: 'Дождевик', + category: 'Одежда', + }, + { + name: 'Удобная обувь', + category: 'Одежда', + }, + { + name: 'Зубная щётка', + category: 'Гигиена', + }, + { + name: 'Зубная паста', + category: 'Гигиена', + }, + { + name: 'Шампунь', + category: 'Гигиена', + }, + { + name: 'Дезодорант', + category: 'Гигиена', + }, + { + name: 'Солнцезащитный крем', + category: 'Гигиена', + }, + { + name: 'Бритва', + category: 'Гигиена', + }, + { + name: 'Зарядное устройство', + category: 'Электроника', + }, + { + name: 'Внешний аккумулятор', + category: 'Электроника', + }, + { + name: 'Наушники', + category: 'Электроника', + }, + { + name: 'Адаптер для розеток', + category: 'Электроника', + }, + { + name: 'Фотоаппарат', + category: 'Электроника', + }, + { + name: 'Обезболивающее', + category: 'Здоровье', + }, + { + name: 'Пластыри', + category: 'Здоровье', + }, + { + name: 'Антисептик', + category: 'Здоровье', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/ru/pdf.ts b/shared/src/i18n/ru/pdf.ts new file mode 100644 index 00000000..a8c02d05 --- /dev/null +++ b/shared/src/i18n/ru/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'План поездки', + 'pdf.planned': 'Запланировано', + 'pdf.costLabel': 'Стоимость EUR', + 'pdf.preview': 'Предпросмотр PDF', + 'pdf.saveAsPdf': 'Сохранить как PDF', +}; +export default pdf; diff --git a/shared/src/i18n/ru/perm.ts b/shared/src/i18n/ru/perm.ts new file mode 100644 index 00000000..e7148f62 --- /dev/null +++ b/shared/src/i18n/ru/perm.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Настройки разрешений', + 'perm.subtitle': 'Управляйте тем, кто может выполнять действия в приложении', + 'perm.saved': 'Настройки разрешений сохранены', + 'perm.resetDefaults': 'Сбросить по умолчанию', + 'perm.customized': 'изменено', + 'perm.level.admin': 'Только администратор', + 'perm.level.tripOwner': 'Владелец поездки', + 'perm.level.tripMember': 'Участники поездки', + 'perm.level.everybody': 'Все', + 'perm.cat.trip': 'Управление поездками', + 'perm.cat.members': 'Управление участниками', + 'perm.cat.files': 'Файлы', + 'perm.cat.content': 'Контент и расписание', + 'perm.cat.extras': 'Бюджет, сборы и совместная работа', + 'perm.action.trip_create': 'Создавать поездки', + 'perm.action.trip_edit': 'Редактировать детали поездки', + 'perm.action.trip_delete': 'Удалять поездки', + 'perm.action.trip_archive': 'Архивировать / разархивировать поездки', + 'perm.action.trip_cover_upload': 'Загружать обложку', + 'perm.action.member_manage': 'Добавлять / удалять участников', + 'perm.action.file_upload': 'Загружать файлы', + 'perm.action.file_edit': 'Редактировать метаданные файлов', + 'perm.action.file_delete': 'Удалять файлы', + 'perm.action.place_edit': 'Добавлять / редактировать / удалять места', + 'perm.action.day_edit': 'Редактировать дни, заметки и назначения', + 'perm.action.reservation_edit': 'Управлять бронированиями', + 'perm.action.budget_edit': 'Управлять бюджетом', + 'perm.action.packing_edit': 'Управлять списками вещей', + 'perm.action.collab_edit': 'Совместная работа (заметки, опросы, чат)', + 'perm.action.share_manage': 'Управлять ссылками для обмена', + 'perm.actionHint.trip_create': 'Кто может создавать новые поездки', + 'perm.actionHint.trip_edit': + 'Кто может менять название, даты, описание и валюту поездки', + 'perm.actionHint.trip_delete': 'Кто может безвозвратно удалить поездку', + 'perm.actionHint.trip_archive': + 'Кто может архивировать или разархивировать поездку', + 'perm.actionHint.trip_cover_upload': 'Кто может загружать или менять обложку', + 'perm.actionHint.member_manage': + 'Кто может приглашать или удалять участников поездки', + 'perm.actionHint.file_upload': 'Кто может загружать файлы в поездку', + 'perm.actionHint.file_edit': + 'Кто может редактировать описания и ссылки файлов', + 'perm.actionHint.file_delete': + 'Кто может перемещать файлы в корзину или безвозвратно удалять', + 'perm.actionHint.place_edit': + 'Кто может добавлять, редактировать или удалять места', + 'perm.actionHint.day_edit': + 'Кто может редактировать дни, заметки к дням и назначения мест', + 'perm.actionHint.reservation_edit': + 'Кто может создавать, редактировать или удалять бронирования', + 'perm.actionHint.budget_edit': + 'Кто может создавать, редактировать или удалять статьи бюджета', + 'perm.actionHint.packing_edit': + 'Кто может управлять вещами для сборов и сумками', + 'perm.actionHint.collab_edit': + 'Кто может создавать заметки, опросы и отправлять сообщения', + 'perm.actionHint.share_manage': + 'Кто может создавать или удалять публичные ссылки для обмена', +}; +export default perm; diff --git a/shared/src/i18n/ru/photos.ts b/shared/src/i18n/ru/photos.ts new file mode 100644 index 00000000..eef31d03 --- /dev/null +++ b/shared/src/i18n/ru/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Фотографии', + 'photos.subtitle': '{count} фото для {trip}', + 'photos.dropHere': 'Перетащите фото сюда...', + 'photos.dropHereActive': 'Перетащите фото сюда', + 'photos.captionForAll': 'Подпись (для всех)', + 'photos.captionPlaceholder': 'Необязательная подпись...', + 'photos.addCaption': 'Добавить подпись...', + 'photos.allDays': 'Все дни', + 'photos.noPhotos': 'Фото пока нет', + 'photos.uploadHint': 'Загрузите фото из путешествия', + 'photos.clickToSelect': 'или нажмите для выбора', + 'photos.linkPlace': 'Привязать место', + 'photos.noPlace': 'Без места', + 'photos.uploadN': '{n} фото загружено', + 'photos.linkDay': 'Связать день', + 'photos.noDay': 'Нет дня', + 'photos.dayLabel': 'День {number}', + 'photos.photoSelected': 'Фото выбрано', + 'photos.photosSelected': 'Фото выбраны', + 'photos.fileTypeHint': 'JPG, PNG, WebP · макс. 10 МБ · до 30 фото', +}; +export default photos; diff --git a/shared/src/i18n/ru/places.ts b/shared/src/i18n/ru/places.ts new file mode 100644 index 00000000..34b8b4d9 --- /dev/null +++ b/shared/src/i18n/ru/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Добавить место/активность', + 'places.importFile': 'Импортировать файл', + 'places.sidebarDrop': 'Отпустите для импорта', + 'places.importFileHint': + 'Импортируйте файлы .gpx, .kml или .kmz из инструментов, таких как Google My Maps, Google Earth или GPS-трекер.', + 'places.importFileDropHere': + 'Нажмите для выбора файла или перетащите его сюда', + 'places.importFileDropActive': 'Отпустите файл для выбора', + 'places.importFileUnsupported': + 'Неподдерживаемый тип файла. Используйте .gpx, .kml или .kmz.', + 'places.importFileTooLarge': + 'Файл слишком большой. Максимальный размер загрузки — {maxMb} MB.', + 'places.importFileError': 'Ошибка импорта', + 'places.importAllSkipped': 'Все места уже были в поездке.', + 'places.gpxImported': '{count} мест импортировано из GPX', + 'places.gpxImportTypes': 'Что импортировать?', + 'places.gpxImportWaypoints': 'Путевые точки', + 'places.gpxImportRoutes': 'Маршруты', + 'places.gpxImportTracks': 'Треки (с геометрией пути)', + 'places.gpxImportNoneSelected': 'Выберите хотя бы один тип для импорта.', + 'places.kmlImportTypes': 'Что вы хотите импортировать?', + 'places.kmlImportPoints': 'Точки (Placemarks)', + 'places.kmlImportPaths': 'Маршруты (LineStrings)', + 'places.kmlImportNoneSelected': 'Выберите хотя бы один тип.', + 'places.selectionCount': '{count} выбрано', + 'places.deleteSelected': 'Удалить выбранные', + 'places.kmlKmzImported': '{count} мест импортировано из KMZ/KML', + 'places.urlResolved': 'Место импортировано из URL', + 'places.importList': 'Импорт списка', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Импортировано: {created} • Пропущено: {skipped}', + 'places.importGoogleList': 'Список Google', + 'places.importNaverList': 'Список Naver', + 'places.googleListHint': + 'Вставьте ссылку на общий список Google Maps для импорта всех мест.', + 'places.googleListImported': '{count} мест импортировано из "{list}"', + 'places.googleListError': 'Не удалось импортировать список Google Maps', + 'places.naverListHint': + 'Вставьте ссылку на общий список Naver Maps для импорта всех мест.', + 'places.naverListImported': '{count} мест импортировано из "{list}"', + 'places.naverListError': 'Не удалось импортировать список Naver Maps', + 'places.viewDetails': 'Подробности', + 'places.assignToDay': 'Добавить в какой день?', + 'places.all': 'Все', + 'places.unplanned': 'Незапланированные', + 'places.filterTracks': 'Треки', + 'places.search': 'Поиск мест...', + 'places.allCategories': 'Все категории', + 'places.categoriesSelected': 'категорий', + 'places.clearFilter': 'Сбросить фильтр', + 'places.count': '{count} мест', + 'places.countSingular': '1 место', + 'places.allPlanned': 'Все места запланированы', + 'places.noneFound': 'Места не найдены', + 'places.editPlace': 'Редактировать место', + 'places.formName': 'Название', + 'places.formNamePlaceholder': 'напр. Эйфелева башня', + 'places.formDescription': 'Описание', + 'places.formDescriptionPlaceholder': 'Краткое описание...', + 'places.formAddress': 'Адрес', + 'places.formAddressPlaceholder': 'Улица, город, страна', + 'places.formLat': 'Широта (напр. 48.8566)', + 'places.formLng': 'Долгота (напр. 2.3522)', + 'places.formCategory': 'Категория', + 'places.noCategory': 'Без категории', + 'places.categoryNamePlaceholder': 'Название категории', + 'places.formTime': 'Время', + 'places.startTime': 'Начало', + 'places.endTime': 'Конец', + 'places.endTimeBeforeStart': 'Время окончания раньше времени начала', + 'places.timeCollision': 'Пересечение по времени с:', + 'places.formWebsite': 'Сайт', + 'places.formNotes': 'Заметки', + 'places.formNotesPlaceholder': 'Личные заметки...', + 'places.formReservation': 'Бронирование', + 'places.reservationNotesPlaceholder': + 'Заметки о бронировании, номер подтверждения...', + 'places.mapsSearchPlaceholder': 'Поиск мест...', + 'places.mapsSearchError': 'Ошибка поиска мест.', + 'places.loadingDetails': 'Загрузка данных о месте…', + 'places.osmHint': + 'Поиск через OpenStreetMap (без фото, часов работы и рейтингов). Добавьте API-ключ Google в настройках для полной информации.', + 'places.osmActive': + 'Поиск через OpenStreetMap (без фото, рейтингов и часов работы). Добавьте API-ключ Google в настройках для расширенных данных.', + 'places.categoryCreateError': 'Не удалось создать категорию', + 'places.nameRequired': 'Введите название', + 'places.saveError': 'Ошибка сохранения', +}; +export default places; diff --git a/shared/src/i18n/ru/planner.ts b/shared/src/i18n/ru/planner.ts new file mode 100644 index 00000000..54ce59f8 --- /dev/null +++ b/shared/src/i18n/ru/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Места', + 'planner.bookings': 'Бронирования', + 'planner.packingList': 'Список вещей', + 'planner.documents': 'Документы', + 'planner.dayPlan': 'План дня', + 'planner.reservations': 'Бронирования', + 'planner.minTwoPlaces': 'Нужно минимум 2 места с координатами', + 'planner.noGeoPlaces': 'Нет мест с координатами', + 'planner.routeCalculated': 'Маршрут рассчитан', + 'planner.routeCalcFailed': 'Не удалось рассчитать маршрут', + 'planner.routeError': 'Ошибка расчёта маршрута', + 'planner.icsExportFailed': 'Не удалось экспортировать ICS', + 'planner.routeOptimized': 'Маршрут оптимизирован', + 'planner.reservationUpdated': 'Бронирование обновлено', + 'planner.reservationAdded': 'Бронирование добавлено', + 'planner.confirmDeleteReservation': 'Удалить бронирование?', + 'planner.reservationDeleted': 'Бронирование удалено', + 'planner.days': 'Дни', + 'planner.allPlaces': 'Все места', + 'planner.totalPlaces': 'Всего {n} мест', + 'planner.noDaysPlanned': 'Дни ещё не запланированы', + 'planner.editTrip': 'Редактировать поездку →', + 'planner.placeOne': '1 место', + 'planner.placeN': '{n} мест', + 'planner.addNote': 'Добавить заметку', + 'planner.noEntries': 'На этот день записей нет', + 'planner.addPlace': 'Добавить место/активность', + 'planner.addPlaceShort': '+ Добавить место/активность', + 'planner.resPending': 'Бронирование ожидает · ', + 'planner.resConfirmed': 'Бронирование подтверждено · ', + 'planner.notePlaceholder': 'Заметка…', + 'planner.noteTimePlaceholder': 'Время (необязательно)', + 'planner.noteExamplePlaceholder': + 'напр. S3 в 14:30 с вокзала, паром с причала 7, обеденный перерыв…', + 'planner.totalCost': 'Общая стоимость', + 'planner.searchPlaces': 'Поиск мест…', + 'planner.allCategories': 'Все категории', + 'planner.noPlacesFound': 'Места не найдены', + 'planner.addFirstPlace': 'Добавить первое место', + 'planner.noReservations': 'Нет бронирований', + 'planner.addFirstReservation': 'Добавить первое бронирование', + 'planner.new': 'Новое', + 'planner.addToDay': '+ День', + 'planner.calculating': 'Расчёт…', + 'planner.route': 'Маршрут', + 'planner.optimize': 'Оптимизировать', + 'planner.openGoogleMaps': 'Открыть в Google Maps', + 'planner.selectDayHint': + 'Выберите день из списка слева для просмотра плана дня', + 'planner.noPlacesForDay': 'На этот день мест пока нет', + 'planner.addPlacesLink': 'Добавить места →', + 'planner.minTotal': 'мин. всего', + 'planner.noReservation': 'Нет бронирования', + 'planner.removeFromDay': 'Убрать из дня', + 'planner.addToThisDay': 'Добавить в день', + 'planner.overview': 'Обзор', + 'planner.noDays': 'Дней нет', + 'planner.editTripToAddDays': 'Отредактируйте поездку для добавления дней', + 'planner.dayCount': '{n} дней', + 'planner.clickToUnlock': 'Нажмите для разблокировки', + 'planner.keepPosition': 'Сохранить позицию при оптимизации маршрута', + 'planner.dayDetails': 'Подробности дня', + 'planner.dayN': 'День {n}', +}; +export default planner; diff --git a/shared/src/i18n/ru/register.ts b/shared/src/i18n/ru/register.ts new file mode 100644 index 00000000..c4b90990 --- /dev/null +++ b/shared/src/i18n/ru/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Пароли не совпадают', + 'register.passwordTooShort': 'Пароль должен содержать не менее 8 символов', + 'register.failed': 'Ошибка регистрации', + 'register.getStarted': 'Начать', + 'register.subtitle': 'Создайте аккаунт и начните планировать поездки мечты.', + 'register.feature1': 'Неограниченные планы поездок', + 'register.feature2': 'Интерактивная карта', + 'register.feature3': 'Управление местами и категориями', + 'register.feature4': 'Отслеживание бронирований', + 'register.feature5': 'Создание списков вещей', + 'register.feature6': 'Хранение фото и файлов', + 'register.createAccount': 'Создать аккаунт', + 'register.startPlanning': 'Начните планировать свои поездки', + 'register.minChars': 'Мин. 6 символов', + 'register.confirmPassword': 'Подтвердите пароль', + 'register.repeatPassword': 'Повторите пароль', + 'register.registering': 'Регистрация...', + 'register.register': 'Зарегистрироваться', + 'register.hasAccount': 'Уже есть аккаунт?', + 'register.signIn': 'Войти', +}; +export default register; diff --git a/shared/src/i18n/ru/reservations.ts b/shared/src/i18n/ru/reservations.ts new file mode 100644 index 00000000..ebec10b1 --- /dev/null +++ b/shared/src/i18n/ru/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Бронирования', + 'reservations.empty': 'Пока нет бронирований', + 'reservations.emptyHint': + 'Добавьте бронирования на авиабилеты, отели и другое', + 'reservations.add': 'Добавить бронирование', + 'reservations.addManual': 'Ручное бронирование', + 'reservations.placeHint': + 'Совет: бронирования лучше создавать прямо из места, чтобы связать их с планом дня.', + 'reservations.confirmed': 'Подтверждено', + 'reservations.pending': 'Ожидание', + 'reservations.summary': '{confirmed} подтв., {pending} ожид.', + 'reservations.fromPlan': 'Из плана', + 'reservations.showFiles': 'Показать файлы', + 'reservations.editTitle': 'Редактировать бронирование', + 'reservations.status': 'Статус', + 'reservations.datetime': 'Дата и время', + 'reservations.startTime': 'Время начала', + 'reservations.endTime': 'Время окончания', + 'reservations.date': 'Дата', + 'reservations.time': 'Время', + 'reservations.timeAlt': 'Время (альтернативное, напр. 19:30)', + 'reservations.notes': 'Заметки', + 'reservations.notesPlaceholder': 'Дополнительные заметки...', + 'reservations.meta.airline': 'Авиакомпания', + 'reservations.meta.flightNumber': 'Номер рейса', + 'reservations.meta.from': 'Откуда', + 'reservations.meta.to': 'Куда', + 'reservations.needsReview': 'Проверить', + 'reservations.needsReviewHint': + 'Аэропорт не удалось определить автоматически — подтвердите местоположение.', + 'reservations.searchLocation': 'Искать станцию, порт, адрес...', + 'reservations.meta.trainNumber': 'Номер поезда', + 'reservations.meta.platform': 'Платформа', + 'reservations.meta.seat': 'Место', + 'reservations.meta.checkIn': 'Заезд', + 'reservations.meta.checkInUntil': 'Заселение до', + 'reservations.meta.checkOut': 'Выезд', + 'reservations.meta.linkAccommodation': 'Жильё', + 'reservations.meta.pickAccommodation': 'Привязать к жилью', + 'reservations.meta.noAccommodation': 'Нет', + 'reservations.meta.hotelPlace': 'Жильё', + 'reservations.meta.pickHotel': 'Выбрать жильё', + 'reservations.meta.fromDay': 'С', + 'reservations.meta.toDay': 'По', + 'reservations.meta.selectDay': 'Выбрать день', + 'reservations.type.flight': 'Авиабилет', + 'reservations.type.hotel': 'Жильё', + 'reservations.type.restaurant': 'Ресторан', + 'reservations.type.train': 'Поезд', + 'reservations.type.car': 'Автомобиль', + 'reservations.type.cruise': 'Круиз', + 'reservations.type.event': 'Мероприятие', + 'reservations.type.tour': 'Экскурсия', + 'reservations.type.other': 'Другое', + 'reservations.confirm.delete': + 'Вы уверены, что хотите удалить бронирование «{name}»?', + 'reservations.confirm.deleteTitle': 'Удалить бронирование?', + 'reservations.confirm.deleteBody': '«{name}» будет удалено навсегда.', + 'reservations.toast.updated': 'Бронирование обновлено', + 'reservations.toast.removed': 'Бронирование удалено', + 'reservations.toast.fileUploaded': 'Файл загружен', + 'reservations.toast.uploadError': 'Ошибка загрузки', + 'reservations.newTitle': 'Новое бронирование', + 'reservations.bookingType': 'Тип бронирования', + 'reservations.titleLabel': 'Название', + 'reservations.titlePlaceholder': 'напр. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Местоположение / Адрес', + 'reservations.locationPlaceholder': 'Адрес, аэропорт, отель...', + 'reservations.confirmationCode': 'Код бронирования', + 'reservations.confirmationPlaceholder': 'напр. ABC12345', + 'reservations.day': 'День', + 'reservations.noDay': 'Без дня', + 'reservations.place': 'Место', + 'reservations.noPlace': 'Без места', + 'reservations.pendingSave': 'будет сохранено…', + 'reservations.uploading': 'Загрузка...', + 'reservations.attachFile': 'Прикрепить файл', + 'reservations.linkExisting': 'Привязать существующий файл', + 'reservations.toast.saveError': 'Ошибка сохранения', + 'reservations.toast.updateError': 'Ошибка обновления', + 'reservations.toast.deleteError': 'Ошибка удаления', + 'reservations.confirm.remove': 'Удалить бронирование для «{name}»?', + 'reservations.linkAssignment': 'Привязать к назначению дня', + 'reservations.pickAssignment': 'Выберите назначение из вашего плана...', + 'reservations.noAssignment': 'Без привязки (самостоятельное)', + 'reservations.price': 'Цена', + 'reservations.budgetCategory': 'Категория бюджета', + 'reservations.budgetCategoryPlaceholder': 'напр. Транспорт, Проживание', + 'reservations.budgetCategoryAuto': 'Авто (по типу бронирования)', + 'reservations.budgetHint': + 'При сохранении будет автоматически создана запись бюджета.', + 'reservations.departureDate': 'Вылет', + 'reservations.arrivalDate': 'Прилёт', + 'reservations.departureTime': 'Время вылета', + 'reservations.arrivalTime': 'Время прилёта', + 'reservations.pickupDate': 'Получение', + 'reservations.returnDate': 'Возврат', + 'reservations.pickupTime': 'Время получения', + 'reservations.returnTime': 'Время возврата', + 'reservations.endDate': 'Дата окончания', + 'reservations.meta.departureTimezone': 'TZ вылета', + 'reservations.meta.arrivalTimezone': 'TZ прилёта', + 'reservations.span.departure': 'Вылет', + 'reservations.span.arrival': 'Прилёт', + 'reservations.span.inTransit': 'В пути', + 'reservations.span.pickup': 'Получение', + 'reservations.span.return': 'Возврат', + 'reservations.span.active': 'Активно', + 'reservations.span.start': 'Начало', + 'reservations.span.end': 'Конец', + 'reservations.span.ongoing': 'Продолжается', + 'reservations.validation.endBeforeStart': + 'Дата/время окончания должны быть позже даты/времени начала', + 'reservations.addBooking': 'Добавить бронирование', +}; +export default reservations; diff --git a/shared/src/i18n/ru/settings.ts b/shared/src/i18n/ru/settings.ts new file mode 100644 index 00000000..fb25b3e4 --- /dev/null +++ b/shared/src/i18n/ru/settings.ts @@ -0,0 +1,299 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Настройки', + 'settings.subtitle': 'Настройте свои персональные параметры', + 'settings.tabs.display': 'Дисплей', + 'settings.tabs.map': 'Карта', + 'settings.tabs.notifications': 'Уведомления', + 'settings.tabs.integrations': 'Интеграции', + 'settings.tabs.account': 'Аккаунт', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': 'О приложении', + 'settings.map': 'Карта', + 'settings.mapTemplate': 'Шаблон карты', + 'settings.mapTemplatePlaceholder.select': 'Выберите шаблон...', + 'settings.mapDefaultHint': 'Оставьте пустым для OpenStreetMap (по умолчанию)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL-шаблон для тайлов карты', + 'settings.mapProvider': 'Провайдер карты', + 'settings.mapProviderHint': + 'Применяется к Trip Planner и Journey. Atlas всегда использует Leaflet.', + 'settings.mapLeafletSubtitle': 'Классические 2D, любые растровые тайлы', + 'settings.mapMapboxSubtitle': 'Векторные тайлы, 3D-здания и рельеф', + 'settings.mapExperimental': 'Экспериментально', + 'settings.mapMapboxToken': 'Токен доступа Mapbox', + 'settings.mapMapboxTokenHint': 'Публичный токен (pk.*) с', + 'settings.mapMapboxTokenLink': 'mapbox.com → Токены доступа', + 'settings.mapStyle': 'Стиль карты', + 'settings.mapStylePlaceholder': 'Выберите стиль Mapbox', + 'settings.mapStyleHint': 'Preset или собственный URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': '3D-здания и рельеф', + 'settings.map3dHint': + 'Наклон + настоящие 3D-здания — работает со всеми стилями, включая спутник.', + 'settings.mapHighQuality': 'Режим высокого качества', + 'settings.mapHighQualityHint': + 'Сглаживание + проекция глобуса для более чётких краёв и реалистичного вида мира.', + 'settings.mapHighQualityWarning': + 'Может повлиять на производительность на слабых устройствах.', + 'settings.mapTipLabel': 'Совет:', + 'settings.mapTip': + 'Зажмите правую кнопку мыши и перетащите, чтобы повернуть/наклонить карту. Клик средней кнопкой — добавить место (правая кнопка зарезервирована для вращения).', + 'settings.latitude': 'Широта', + 'settings.longitude': 'Долгота', + 'settings.saveMap': 'Сохранить карту', + 'settings.apiKeys': 'API-ключи', + 'settings.mapsKey': 'API-ключ Google Maps', + 'settings.mapsKeyHint': + 'Для поиска мест. Требуется Places API (New). Получите на console.cloud.google.com', + 'settings.weatherKey': 'API-ключ OpenWeatherMap', + 'settings.weatherKeyHint': + 'Для данных о погоде. Бесплатно на openweathermap.org/api', + 'settings.keyPlaceholder': 'Введите ключ...', + 'settings.configured': 'Настроено', + 'settings.saveKeys': 'Сохранить ключи', + 'settings.display': 'Отображение', + 'settings.colorMode': 'Цветовая схема', + 'settings.light': 'Светлая', + 'settings.dark': 'Тёмная', + 'settings.auto': 'Авто', + 'settings.language': 'Язык', + 'settings.temperature': 'Единица температуры', + 'settings.timeFormat': 'Формат времени', + 'settings.blurBookingCodes': 'Скрыть коды бронирования', + 'settings.notifications': 'Уведомления', + 'settings.notifyTripInvite': 'Приглашения в поездку', + 'settings.notifyBookingChange': 'Изменения бронирований', + 'settings.notifyTripReminder': 'Напоминания о поездке', + 'settings.notifyTodoDue': 'Задача к сроку', + 'settings.notifyVacayInvite': 'Приглашения слияния Vacay', + 'settings.notifyPhotosShared': 'Общие фото (Immich)', + 'settings.notifyCollabMessage': 'Сообщения чата (Collab)', + 'settings.notifyPackingTagged': 'Список вещей: назначения', + 'settings.notifyWebhook': 'Webhook-уведомления', + 'settings.notificationsDisabled': + 'Уведомления не настроены. Попросите администратора включить уведомления по электронной почте или webhook.', + 'settings.notificationsActive': 'Активный канал', + 'settings.notificationsManagedByAdmin': + 'События уведомлений настраиваются администратором.', + 'settings.on': 'Вкл.', + 'settings.off': 'Выкл.', + 'settings.mcp.title': 'Настройка MCP', + 'settings.mcp.endpoint': 'MCP-эндпоинт', + 'settings.mcp.clientConfig': 'Конфигурация клиента', + 'settings.mcp.clientConfigHint': + 'Замените на API-токен из списка ниже. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Замените и на учётные данные из созданного выше клиента OAuth 2.1. При первом подключении mcp-remote откроет браузер для завершения авторизации. Путь к npx может потребовать настройки для вашей системы (например, C:\\PROGRA~1\\nodejs\\npx.cmd в Windows).', + 'settings.mcp.copy': 'Копировать', + 'settings.mcp.copied': 'Скопировано!', + 'settings.mcp.apiTokens': 'API-токены', + 'settings.mcp.createToken': 'Создать токен', + 'settings.mcp.noTokens': + 'Токенов пока нет. Создайте один для подключения MCP-клиентов.', + 'settings.mcp.tokenCreatedAt': 'Создан', + 'settings.mcp.tokenUsedAt': 'Использован', + 'settings.mcp.deleteTokenTitle': 'Удалить токен', + 'settings.mcp.deleteTokenMessage': + 'Этот токен перестанет работать немедленно. Любой MCP-клиент, использующий его, потеряет доступ.', + 'settings.mcp.modal.createTitle': 'Создать API-токен', + 'settings.mcp.modal.tokenName': 'Название токена', + 'settings.mcp.modal.tokenNamePlaceholder': + 'напр. Claude Desktop, Рабочий ноутбук', + 'settings.mcp.modal.creating': 'Создание…', + 'settings.mcp.modal.create': 'Создать токен', + 'settings.mcp.modal.createdTitle': 'Токен создан', + 'settings.mcp.modal.createdWarning': + 'Этот токен будет показан только один раз. Скопируйте и сохраните его сейчас — восстановить его будет невозможно.', + 'settings.mcp.modal.done': 'Готово', + 'settings.mcp.toast.created': 'Токен создан', + 'settings.mcp.toast.createError': 'Не удалось создать токен', + 'settings.mcp.toast.deleted': 'Токен удалён', + 'settings.mcp.toast.deleteError': 'Не удалось удалить токен', + 'settings.mcp.apiTokensDeprecated': + 'API-токены устарели и будут удалены в будущей версии. Пожалуйста, используйте клиенты OAuth 2.1.', + 'settings.oauth.clients': 'Клиенты OAuth 2.1', + 'settings.oauth.clientsHint': + 'Зарегистрируйте клиенты OAuth 2.1, чтобы сторонние MCP-приложения (Claude Web, Cursor и др.) могли подключаться без статических токенов.', + 'settings.oauth.createClient': 'Новый клиент', + 'settings.oauth.noClients': 'Нет зарегистрированных клиентов OAuth.', + 'settings.oauth.clientId': 'ID клиента', + 'settings.oauth.clientSecret': 'Секрет клиента', + 'settings.oauth.deleteClient': 'Удалить клиента', + 'settings.oauth.deleteClientMessage': + 'Этот клиент и все активные сессии будут удалены навсегда. Любое приложение, использующее его, немедленно потеряет доступ.', + 'settings.oauth.rotateSecret': 'Обновить секрет', + 'settings.oauth.rotateSecretMessage': + 'Будет сгенерирован новый секрет клиента, а все существующие сессии будут немедленно аннулированы. Обновите приложение перед закрытием этого диалога.', + 'settings.oauth.rotateSecretConfirm': 'Обновить', + 'settings.oauth.rotateSecretConfirming': 'Обновление…', + 'settings.oauth.rotateSecretDoneTitle': 'Новый секрет сгенерирован', + 'settings.oauth.rotateSecretDoneWarning': + 'Этот секрет отображается только один раз. Скопируйте его сейчас и обновите приложение — все предыдущие сессии были аннулированы.', + 'settings.oauth.activeSessions': 'Активные сессии OAuth', + 'settings.oauth.sessionScopes': 'Области доступа', + 'settings.oauth.sessionExpires': 'Истекает', + 'settings.oauth.revoke': 'Отозвать', + 'settings.oauth.revokeSession': 'Отозвать сессию', + 'settings.oauth.revokeSessionMessage': + 'Это немедленно отзовёт доступ для данной сессии OAuth.', + 'settings.oauth.modal.createTitle': 'Зарегистрировать клиент OAuth', + 'settings.oauth.modal.presets': 'Быстрые настройки', + 'settings.oauth.modal.clientName': 'Название приложения', + 'settings.oauth.modal.clientNamePlaceholder': + 'напр. Claude Web, Моё MCP-приложение', + 'settings.oauth.modal.redirectUris': 'URI перенаправления', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Один URI на строку. Требуется HTTPS (localhost исключён). Требуется точное совпадение.', + 'settings.oauth.modal.scopes': 'Разрешённые области доступа', + 'settings.oauth.modal.scopesHint': + 'list_trips и get_trip_summary всегда доступны — область не требуется. Они помогают ИИ находить нужные ID поездок.', + 'settings.oauth.modal.selectAll': 'Выбрать все', + 'settings.oauth.modal.deselectAll': 'Снять выбор', + 'settings.oauth.modal.creating': 'Регистрация…', + 'settings.oauth.modal.create': 'Зарегистрировать клиента', + 'settings.oauth.modal.createdTitle': 'Клиент зарегистрирован', + 'settings.oauth.modal.createdWarning': + 'Секрет клиента отображается только один раз. Скопируйте его сейчас — его нельзя будет восстановить.', + 'settings.oauth.toast.createError': + 'Не удалось зарегистрировать клиент OAuth', + 'settings.oauth.toast.deleted': 'Клиент OAuth удалён', + 'settings.oauth.toast.deleteError': 'Не удалось удалить клиент OAuth', + 'settings.oauth.toast.revoked': 'Сессия отозвана', + 'settings.oauth.toast.revokeError': 'Не удалось отозвать сессию', + 'settings.oauth.toast.rotateError': 'Не удалось обновить секрет клиента', + 'settings.oauth.modal.machineClient': + 'Машинный клиент (без входа через браузер)', + 'settings.oauth.modal.machineClientHint': + 'Использует грант client_credentials — URI перенаправления не требуются. Токен выдаётся напрямую через client_id + client_secret и действует от вашего имени в пределах выбранных областей.', + 'settings.oauth.modal.machineClientUsage': + 'Получить токен: POST /oauth/token с grant_type=client_credentials, client_id и client_secret. Без браузера, без токена обновления.', + 'settings.oauth.badge.machine': 'машинный', + 'settings.account': 'Аккаунт', + 'settings.about': 'О приложении', + 'settings.about.reportBug': 'Сообщить об ошибке', + 'settings.about.reportBugHint': 'Нашли проблему? Сообщите нам', + 'settings.about.featureRequest': 'Предложить функцию', + 'settings.about.featureRequestHint': 'Предложите новую функцию', + 'settings.about.wikiHint': 'Документация и руководства', + 'settings.about.supporters.badge': 'Ежемесячные спонсоры', + 'settings.about.supporters.title': 'Спутники TREK', + 'settings.about.supporters.subtitle': + 'Пока ты планируешь следующий маршрут, эти люди планируют вместе со мной будущее TREK. Их ежемесячный взнос идёт напрямую в разработку и реально потраченные часы — чтобы TREK оставался Open Source.', + 'settings.about.supporters.since': 'спонсор с {date}', + 'settings.about.supporters.tierEmpty': 'Стань первым', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK — это самостоятельно размещаемый планировщик путешествий, который помогает организовать поездки от первой идеи до последнего воспоминания. Планирование по дням, бюджет, списки вещей, фото и многое другое — всё в одном месте, на вашем собственном сервере.', + 'settings.about.madeWith': 'Сделано с', + 'settings.about.madeBy': 'Морисом и растущим open-source сообществом.', + 'settings.username': 'Имя пользователя', + 'settings.email': 'Эл. почта', + 'settings.role': 'Роль', + 'settings.roleAdmin': 'Администратор', + 'settings.oidcLinked': 'Связан с', + 'settings.changePassword': 'Изменить пароль', + 'settings.mustChangePassword': + 'Вы должны сменить пароль перед продолжением. Пожалуйста, установите новый пароль ниже.', + 'settings.currentPassword': 'Текущий пароль', + 'settings.currentPasswordRequired': 'Текущий пароль обязателен', + 'settings.newPassword': 'Новый пароль', + 'settings.confirmPassword': 'Подтвердите новый пароль', + 'settings.updatePassword': 'Обновить пароль', + 'settings.passwordRequired': 'Введите текущий и новый пароль', + 'settings.passwordTooShort': 'Пароль должен содержать не менее 8 символов', + 'settings.passwordMismatch': 'Пароли не совпадают', + 'settings.passwordWeak': + 'Пароль должен содержать заглавные, строчные буквы, цифру и специальный символ', + 'settings.passwordChanged': 'Пароль успешно изменён', + 'settings.deleteAccount': 'Удалить аккаунт', + 'settings.deleteAccountTitle': 'Удалить ваш аккаунт?', + 'settings.deleteAccountWarning': + 'Ваш аккаунт и все поездки, места и файлы будут безвозвратно удалены. Это действие нельзя отменить.', + 'settings.deleteAccountConfirm': 'Удалить безвозвратно', + 'settings.deleteBlockedTitle': 'Удаление невозможно', + 'settings.deleteBlockedMessage': + 'Вы единственный администратор. Назначьте другого пользователя администратором перед удалением своего аккаунта.', + 'settings.roleUser': 'Пользователь', + 'settings.saveProfile': 'Сохранить профиль', + 'settings.mfa.title': 'Двухфакторная аутентификация (2FA)', + 'settings.mfa.description': + 'Добавляет второй шаг при входе. Используйте приложение-аутентификатор (Google Authenticator, Authy и др.).', + 'settings.mfa.requiredByPolicy': + 'Администратор требует двухфакторную аутентификацию. Настройте приложение-аутентификатор ниже, прежде чем продолжить.', + 'settings.mfa.backupTitle': 'Резервные коды', + 'settings.mfa.backupDescription': + 'Используйте эти одноразовые коды, если потеряете доступ к приложению-аутентификатору.', + 'settings.mfa.backupWarning': + 'Сохраните их сейчас. Каждый код можно использовать только один раз.', + 'settings.mfa.backupCopy': 'Скопировать коды', + 'settings.mfa.backupDownload': 'Скачать TXT', + 'settings.mfa.backupPrint': 'Печать / PDF', + 'settings.mfa.backupCopied': 'Резервные коды скопированы', + 'settings.mfa.enabled': '2FA включена для вашего аккаунта.', + 'settings.mfa.disabled': '2FA не включена.', + 'settings.mfa.setup': 'Настроить аутентификатор', + 'settings.mfa.scanQr': + 'Отсканируйте QR-код приложением или введите ключ вручную.', + 'settings.mfa.secretLabel': 'Секретный ключ (ручной ввод)', + 'settings.mfa.codePlaceholder': '6-значный код', + 'settings.mfa.enable': 'Включить 2FA', + 'settings.mfa.cancelSetup': 'Отмена', + 'settings.mfa.disableTitle': 'Отключить 2FA', + 'settings.mfa.disableHint': + 'Введите пароль аккаунта и текущий код из аутентификатора.', + 'settings.mfa.disable': 'Отключить 2FA', + 'settings.mfa.toastEnabled': 'Двухфакторная аутентификация включена', + 'settings.mfa.toastDisabled': 'Двухфакторная аутентификация отключена', + 'settings.mfa.demoBlocked': 'Недоступно в демо-режиме', + 'settings.toast.mapSaved': 'Настройки карты сохранены', + 'settings.toast.keysSaved': 'API-ключи сохранены', + 'settings.toast.displaySaved': 'Настройки отображения сохранены', + 'settings.toast.profileSaved': 'Профиль сохранён', + 'settings.uploadAvatar': 'Загрузить фото профиля', + 'settings.removeAvatar': 'Удалить фото профиля', + 'settings.avatarUploaded': 'Фото профиля обновлено', + 'settings.avatarRemoved': 'Фото профиля удалено', + 'settings.avatarError': 'Ошибка загрузки', + 'settings.bookingLabels': 'Подписи маршрутов бронирований', + 'settings.bookingLabelsHint': + 'Отображает названия станций / аэропортов на карте. Если выключено, показывается только значок.', + 'settings.notifyVersionAvailable': 'Доступна новая версия', + 'settings.notificationPreferences.noChannels': + 'Каналы уведомлений не настроены. Попросите администратора настроить уведомления по электронной почте или через webhook.', + 'settings.webhookUrl.label': 'URL вебхука', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Введите URL вашего вебхука Discord, Slack или пользовательского для получения уведомлений.', + 'settings.webhookUrl.saved': 'URL вебхука сохранён', + 'settings.webhookUrl.test': 'Тест', + 'settings.webhookUrl.testSuccess': 'Тестовый вебхук успешно отправлен', + 'settings.webhookUrl.testFailed': 'Ошибка тестового вебхука', + 'settings.ntfyUrl.topicLabel': 'Тема Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL сервера Ntfy (необязательно)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Введите тему Ntfy для получения push-уведомлений. Оставьте поле сервера пустым, чтобы использовать настройку по умолчанию, заданную администратором.', + 'settings.ntfyUrl.tokenLabel': 'Токен доступа (необязательно)', + 'settings.ntfyUrl.tokenHint': 'Требуется для тем, защищённых паролем.', + 'settings.ntfyUrl.saved': 'Настройки Ntfy сохранены', + 'settings.ntfyUrl.test': 'Тест', + 'settings.ntfyUrl.testSuccess': + 'Тестовое уведомление Ntfy успешно отправлено', + 'settings.ntfyUrl.testFailed': 'Ошибка отправки тестового уведомления Ntfy', + 'settings.ntfyUrl.tokenCleared': 'Токен доступа очищен', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/ru/share.ts b/shared/src/i18n/ru/share.ts new file mode 100644 index 00000000..c1f10a58 --- /dev/null +++ b/shared/src/i18n/ru/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Публичная ссылка', + 'share.linkHint': + 'Создайте ссылку, по которой любой сможет просмотреть эту поездку без входа в систему. Только чтение — редактирование невозможно.', + 'share.createLink': 'Создать ссылку', + 'share.deleteLink': 'Удалить ссылку', + 'share.createError': 'Не удалось создать ссылку', + 'share.permMap': 'Карта и план', + 'share.permBookings': 'Бронирования', + 'share.permPacking': 'Вещи', + 'share.permBudget': 'Бюджет', + 'share.permCollab': 'Чат', +}; +export default share; diff --git a/shared/src/i18n/ru/shared.ts b/shared/src/i18n/ru/shared.ts new file mode 100644 index 00000000..a792116d --- /dev/null +++ b/shared/src/i18n/ru/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Ссылка устарела или недействительна', + 'shared.expiredHint': 'Эта ссылка на поездку больше не активна.', + 'shared.readOnly': 'Режим только для чтения', + 'shared.tabPlan': 'План', + 'shared.tabBookings': 'Бронирования', + 'shared.tabPacking': 'Багаж', + 'shared.tabBudget': 'Бюджет', + 'shared.tabChat': 'Чат', + 'shared.days': 'дней', + 'shared.places': 'мест', + 'shared.other': 'Прочее', + 'shared.totalBudget': 'Общий бюджет', + 'shared.messages': 'сообщений', + 'shared.sharedVia': 'Поделено через', + 'shared.confirmed': 'Подтверждено', + 'shared.pending': 'Ожидает', +}; +export default shared; diff --git a/shared/src/i18n/ru/stats.ts b/shared/src/i18n/ru/stats.ts new file mode 100644 index 00000000..405a51f1 --- /dev/null +++ b/shared/src/i18n/ru/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Страны', + 'stats.cities': 'Города', + 'stats.trips': 'Поездки', + 'stats.places': 'Места', + 'stats.worldProgress': 'Прогресс по миру', + 'stats.visited': 'посещено', + 'stats.remaining': 'осталось', + 'stats.visitedCountries': 'Посещённые страны', +}; +export default stats; diff --git a/shared/src/i18n/ru/system_notice.ts b/shared/src/i18n/ru/system_notice.ts new file mode 100644 index 00000000..30d29b55 --- /dev/null +++ b/shared/src/i18n/ru/system_notice.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Добро пожаловать в TREK', + 'system_notice.welcome_v1.body': + 'Ваш универсальный планировщик путешествий. Создавайте маршруты, делитесь поездками с друзьями и оставайтесь организованными — онлайн и офлайн.', + 'system_notice.welcome_v1.cta_label': 'Спланировать поездку', + 'system_notice.welcome_v1.hero_alt': + 'Живописное место назначения с интерфейсом TREK', + 'system_notice.welcome_v1.highlight_plan': 'Маршруты по дням', + 'system_notice.welcome_v1.highlight_share': + 'Совместное планирование с партнёрами', + 'system_notice.welcome_v1.highlight_offline': 'Работает офлайн на мобильном', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Предыдущее уведомление', + 'system_notice.pager.next': 'Следующее уведомление', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Перейти к уведомлению {n}', + 'system_notice.pager.position': 'Уведомление {current} из {total}', + 'system_notice.v3_photos.title': 'Фото перемещены в версии 3.0', + 'system_notice.v3_photos.body': + 'Вкладка **Фото** в Планировщике путешествий удалена. Ваши фото в безопасности — TREK никогда не изменял вашу библиотеку Immich или Synology.\n\nФото теперь доступны в дополнении **Journey**. Journey необязателен — если он ещё недоступен, попросите администратора включить его в разделе Admin → Дополнения.', + 'system_notice.v3_journey.title': 'Знакомьтесь с Journey', + 'system_notice.v3_journey.body': + 'Документируйте путешествия в виде рассказов с хронологиями, фотогалереями и интерактивными картами.', + 'system_notice.v3_journey.cta_label': 'Открыть Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Ежедневная хронология и галерея', + 'system_notice.v3_journey.highlight_photos': 'Импорт из Immich или Synology', + 'system_notice.v3_journey.highlight_share': 'Общий доступ — без входа', + 'system_notice.v3_journey.highlight_export': 'Экспорт в PDF-фотокнигу', + 'system_notice.v3_features.title': 'Ещё нового в версии 3.0', + 'system_notice.v3_features.body': + 'Несколько других важных новшеств в этом релизе.', + 'system_notice.v3_features.highlight_dashboard': + 'Переработанная панель в mobile-first стиле', + 'system_notice.v3_features.highlight_offline': 'Полный офлайн-режим как PWA', + 'system_notice.v3_features.highlight_search': + 'Автодополнение поиска мест в реальном времени', + 'system_notice.v3_features.highlight_import': 'Импорт мест из KMZ/KML-файлов', + 'system_notice.v3_mcp.title': 'MCP: обновление OAuth 2.1', + 'system_notice.v3_mcp.body': + 'Интеграция MCP была полностью переработана. OAuth 2.1 теперь является рекомендуемым методом аутентификации. Статические токены (trek_…) устарели и будут удалены в будущей версии.', + 'system_notice.v3_mcp.highlight_oauth': + 'OAuth 2.1 рекомендуется (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 детальных области разрешений', + 'system_notice.v3_mcp.highlight_deprecated': + 'Статические токены trek_ устарели', + 'system_notice.v3_mcp.highlight_tools': 'Расширенный набор инструментов', + 'system_notice.v3_thankyou.title': 'Личное слово от меня', + 'system_notice.v3_thankyou.body': + 'Прежде чем продолжить — хочу остановиться на мгновение.\n\nTREK начинался как сторонний проект, который я создал для собственных поездок. Я никогда не думал, что он вырастет во что-то, чему 4 000 из вас доверяют планирование своих приключений. Каждая звёздочка, каждый issue, каждый запрос на фичу — я читаю их все, и именно они поддерживают меня в поздние ночи между основной работой и университетом.\n\nХочу, чтобы вы знали: TREK всегда будет open source, всегда self-hosted, всегда вашим. Никакого отслеживания, никаких подписок, никаких подвохов. Просто инструмент, созданный человеком, который любит путешествовать так же, как и вы.\n\nОсобая благодарность [jubnl](https://github.com/jubnl) — ты стал невероятным соратником. Многое из того, что делает версию 3.0 великолепной, несёт твой отпечаток. Спасибо, что поверил в этот проект, когда он был ещё сырым.\n\nИ каждому из вас, кто сообщил об ошибке, перевёл строку, поделился TREK с другом или просто использовал его для планирования поездки — **спасибо**. Вы — причина, по которой всё это существует.\n\nЗа множество новых приключений вместе.\n\n— Maurice\n\n---\n\n[Присоединяйся к сообществу в Discord](https://discord.gg/7Q6M6jDwzf)\n\nЕсли TREK делает твои путешествия лучше, [маленький кофе](https://ko-fi.com/mauriceboe) всегда помогает держать свет включённым.', + 'system_notice.v3014_whitespace_collision.title': + 'Требуется действие: конфликт учётных записей', + 'system_notice.v3014_whitespace_collision.body': + 'Обновление 3.0.14 обнаружило один или несколько конфликтов имён пользователей или адресов электронной почты, вызванных ведущими или завершающими пробелами в сохранённых значениях. Затронутые учётные записи были автоматически переименованы. Проверьте логи сервера на строки, начинающиеся с **[migration] WHITESPACE COLLISION**, чтобы определить учётные записи, требующие проверки.', +}; +export default system_notice; diff --git a/shared/src/i18n/ru/todo.ts b/shared/src/i18n/ru/todo.ts new file mode 100644 index 00000000..036631e3 --- /dev/null +++ b/shared/src/i18n/ru/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Список вещей', + 'todo.subtab.todo': 'Задачи', + 'todo.completed': 'выполнено', + 'todo.filter.all': 'Все', + 'todo.filter.open': 'Открытые', + 'todo.filter.done': 'Выполненные', + 'todo.uncategorized': 'Без категории', + 'todo.namePlaceholder': 'Название задачи', + 'todo.descriptionPlaceholder': 'Описание (необязательно)', + 'todo.unassigned': 'Не назначено', + 'todo.noCategory': 'Без категории', + 'todo.hasDescription': 'Есть описание', + 'todo.addItem': 'Новая задача', + 'todo.sidebar.sortBy': 'Сортировать по', + 'todo.priority': 'Приоритет', + 'todo.newCategoryLabel': 'новая', + 'todo.newCategory': 'Название категории', + 'todo.addCategory': 'Добавить категорию', + 'todo.newItem': 'Новая задача', + 'todo.empty': 'Задач пока нет. Добавьте задачу, чтобы начать!', + 'todo.filter.my': 'Мои задачи', + 'todo.filter.overdue': 'Просроченные', + 'todo.sidebar.tasks': 'Задачи', + 'todo.sidebar.categories': 'Категории', + 'todo.detail.title': 'Задача', + 'todo.detail.description': 'Описание', + 'todo.detail.category': 'Категория', + 'todo.detail.dueDate': 'Срок выполнения', + 'todo.detail.assignedTo': 'Назначено', + 'todo.detail.delete': 'Удалить', + 'todo.detail.save': 'Сохранить изменения', + 'todo.detail.create': 'Создать задачу', + 'todo.detail.priority': 'Приоритет', + 'todo.detail.noPriority': 'Нет', + 'todo.sortByPrio': 'Приоритет', +}; +export default todo; diff --git a/shared/src/i18n/ru/transport.ts b/shared/src/i18n/ru/transport.ts new file mode 100644 index 00000000..3986a437 --- /dev/null +++ b/shared/src/i18n/ru/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Добавить транспорт', + 'transport.modalTitle.create': 'Добавить транспорт', + 'transport.modalTitle.edit': 'Изменить транспорт', + 'transport.title': 'Транспорт', + 'transport.addManual': 'Ручной транспорт', +}; +export default transport; diff --git a/shared/src/i18n/ru/trip.ts b/shared/src/i18n/ru/trip.ts new file mode 100644 index 00000000..335af736 --- /dev/null +++ b/shared/src/i18n/ru/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'План', + 'trip.tabs.transports': 'Транспорт', + 'trip.tabs.reservations': 'Бронирования', + 'trip.tabs.reservationsShort': 'Брони', + 'trip.tabs.packing': 'Список вещей', + 'trip.tabs.packingShort': 'Вещи', + 'trip.tabs.lists': 'Списки', + 'trip.tabs.listsShort': 'Списки', + 'trip.tabs.budget': 'Бюджет', + 'trip.tabs.files': 'Файлы', + 'trip.loading': 'Загрузка поездки...', + 'trip.loadingPhotos': 'Загрузка фото мест...', + 'trip.mobilePlan': 'План', + 'trip.mobilePlaces': 'Места', + 'trip.toast.placeUpdated': 'Место обновлено', + 'trip.toast.placeAdded': 'Место добавлено', + 'trip.toast.placeDeleted': 'Место удалено', + 'trip.toast.selectDay': 'Сначала выберите день', + 'trip.toast.assignedToDay': 'Место назначено на день', + 'trip.toast.reorderError': 'Ошибка изменения порядка', + 'trip.toast.reservationUpdated': 'Бронирование обновлено', + 'trip.toast.reservationAdded': 'Бронирование добавлено', + 'trip.toast.deleted': 'Удалено', + 'trip.confirm.deletePlace': 'Вы уверены, что хотите удалить это место?', + 'trip.confirm.deletePlaces': 'Удалить {count} мест?', + 'trip.toast.placesDeleted': '{count} мест удалено', +}; +export default trip; diff --git a/shared/src/i18n/ru/trips.ts b/shared/src/i18n/ru/trips.ts new file mode 100644 index 00000000..e129f5f9 --- /dev/null +++ b/shared/src/i18n/ru/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} удалён', + 'trips.memberRemoveError': 'Не удалось удалить', + 'trips.memberAdded': '{username} добавлен', + 'trips.memberAddError': 'Не удалось добавить', + 'trips.reminder': 'Напоминание', + 'trips.reminderNone': 'Нет', + 'trips.reminderDay': 'день', + 'trips.reminderDays': 'дней', + 'trips.reminderCustom': 'Другое', + 'trips.reminderDaysBefore': 'дней до отъезда', + 'trips.reminderDisabledHint': + 'Напоминания о поездках отключены. Включите их в Админ > Настройки > Уведомления.', +}; +export default trips; diff --git a/shared/src/i18n/ru/undo.ts b/shared/src/i18n/ru/undo.ts new file mode 100644 index 00000000..48afd25b --- /dev/null +++ b/shared/src/i18n/ru/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Отменить', + 'undo.tooltip': 'Отменить: {action}', + 'undo.assignPlace': 'Место добавлено в день', + 'undo.removeAssignment': 'Место удалено из дня', + 'undo.reorder': 'Места переупорядочены', + 'undo.optimize': 'Маршрут оптимизирован', + 'undo.deletePlace': 'Место удалено', + 'undo.deletePlaces': 'Места удалены', + 'undo.moveDay': 'Место перемещено в другой день', + 'undo.lock': 'Блокировка места изменена', + 'undo.importGpx': 'Импорт GPX', + 'undo.importKeyholeMarkup': 'Импорт KMZ/KML', + 'undo.importGoogleList': 'Импорт из Google Maps', + 'undo.importNaverList': 'Импорт из Naver Maps', + 'undo.addPlace': 'Место добавлено', + 'undo.done': 'Отменено: {action}', +}; +export default undo; diff --git a/shared/src/i18n/ru/vacay.ts b/shared/src/i18n/ru/vacay.ts new file mode 100644 index 00000000..f9a1edcc --- /dev/null +++ b/shared/src/i18n/ru/vacay.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Планируйте и управляйте днями отпуска', + 'vacay.settings': 'Настройки', + 'vacay.year': 'Год', + 'vacay.addYear': 'Добавить следующий год', + 'vacay.addPrevYear': 'Добавить предыдущий год', + 'vacay.removeYear': 'Удалить год', + 'vacay.removeYearConfirm': 'Удалить {year}?', + 'vacay.removeYearHint': + 'Все записи об отпуске и корпоративные выходные за этот год будут безвозвратно удалены.', + 'vacay.remove': 'Удалить', + 'vacay.persons': 'Люди', + 'vacay.noPersons': 'Никто не добавлен', + 'vacay.addPerson': 'Добавить человека', + 'vacay.editPerson': 'Редактировать', + 'vacay.removePerson': 'Удалить человека', + 'vacay.removePersonConfirm': 'Удалить {name}?', + 'vacay.removePersonHint': + 'Все записи об отпуске этого человека будут безвозвратно удалены.', + 'vacay.personName': 'Имя', + 'vacay.personNamePlaceholder': 'Введите имя', + 'vacay.color': 'Цвет', + 'vacay.add': 'Добавить', + 'vacay.legend': 'Легенда', + 'vacay.publicHoliday': 'Государственный праздник', + 'vacay.companyHoliday': 'Корпоративный выходной', + 'vacay.weekend': 'Выходные', + 'vacay.modeVacation': 'Отпуск', + 'vacay.modeCompany': 'Корпоративный выходной', + 'vacay.entitlement': 'Право на отпуск', + 'vacay.entitlementDays': 'Дни', + 'vacay.used': 'Использовано', + 'vacay.remaining': 'Осталось', + 'vacay.carriedOver': 'из {year}', + 'vacay.blockWeekends': 'Блокировать выходные', + 'vacay.blockWeekendsHint': + 'Запретить записи об отпуске в субботу и воскресенье', + 'vacay.weekendDays': 'Выходные дни', + 'vacay.mon': 'Пн', + 'vacay.tue': 'Вт', + 'vacay.wed': 'Ср', + 'vacay.thu': 'Чт', + 'vacay.fri': 'Пт', + 'vacay.sat': 'Сб', + 'vacay.sun': 'Вс', + 'vacay.publicHolidays': 'Государственные праздники', + 'vacay.publicHolidaysHint': 'Отмечать государственные праздники в календаре', + 'vacay.selectCountry': 'Выберите страну', + 'vacay.selectRegion': 'Выберите регион (необязательно)', + 'vacay.companyHolidays': 'Корпоративные выходные', + 'vacay.companyHolidaysHint': 'Разрешить отмечать корпоративные выходные дни', + 'vacay.companyHolidaysNoDeduct': + 'Корпоративные выходные не вычитаются из дней отпуска.', + 'vacay.weekStart': 'Неделя начинается с', + 'vacay.weekStartHint': + 'Выберите, начинается ли неделя с понедельника или воскресенья', + 'vacay.carryOver': 'Перенос', + 'vacay.carryOverHint': + 'Автоматически переносить оставшиеся дни отпуска на следующий год', + 'vacay.sharing': 'Общий доступ', + 'vacay.sharingHint': + 'Поделитесь планом отпуска с другими пользователями TREK', + 'vacay.owner': 'Владелец', + 'vacay.shareEmailPlaceholder': 'Эл. почта пользователя TREK', + 'vacay.shareSuccess': 'План успешно предоставлен', + 'vacay.shareError': 'Не удалось поделиться планом', + 'vacay.dissolve': 'Разделить объединение', + 'vacay.dissolveHint': + 'Снова разделить календари. Ваши записи будут сохранены.', + 'vacay.dissolveAction': 'Разделить', + 'vacay.dissolved': 'Календарь разделён', + 'vacay.fusedWith': 'Объединён с', + 'vacay.you': 'вы', + 'vacay.noData': 'Нет данных', + 'vacay.changeColor': 'Изменить цвет', + 'vacay.inviteUser': 'Пригласить пользователя', + 'vacay.inviteHint': + 'Пригласите другого пользователя TREK для совместного календаря отпусков.', + 'vacay.selectUser': 'Выберите пользователя', + 'vacay.sendInvite': 'Отправить приглашение', + 'vacay.inviteSent': 'Приглашение отправлено', + 'vacay.inviteError': 'Не удалось отправить приглашение', + 'vacay.pending': 'ожидание', + 'vacay.noUsersAvailable': 'Нет доступных пользователей', + 'vacay.accept': 'Принять', + 'vacay.decline': 'Отклонить', + 'vacay.acceptFusion': 'Принять и объединить', + 'vacay.inviteTitle': 'Запрос на объединение', + 'vacay.inviteWantsToFuse': 'хочет объединить календарь отпусков с вами.', + 'vacay.fuseInfo1': + 'Вы оба будете видеть все записи об отпуске в одном общем календаре.', + 'vacay.fuseInfo2': + 'Обе стороны могут создавать и редактировать записи друг для друга.', + 'vacay.fuseInfo3': + 'Обе стороны могут удалять записи и изменять право на отпуск.', + 'vacay.fuseInfo4': + 'Настройки, такие как праздники и корпоративные выходные, становятся общими.', + 'vacay.fuseInfo5': + 'Объединение можно отменить в любое время любой из сторон. Ваши записи будут сохранены.', + 'vacay.addCalendar': 'Добавить календарь', + 'vacay.calendarColor': 'Цвет', + 'vacay.calendarLabel': 'Название', + 'vacay.noCalendars': 'Нет календарей', +}; +export default vacay; diff --git a/shared/src/i18n/tr/admin.ts b/shared/src/i18n/tr/admin.ts new file mode 100644 index 00000000..1e77378d --- /dev/null +++ b/shared/src/i18n/tr/admin.ts @@ -0,0 +1,358 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'Notifications', + 'admin.notifications.hint': + 'Choose one notification channel. Only one can be active at a time.', + 'admin.notifications.none': 'Disabled', + 'admin.notifications.email': 'Email (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Allow users to configure their own ntfy topics for push notifications. Set the default server below to pre-fill user settings.', + 'admin.notifications.save': 'Save notification settings', + 'admin.notifications.saved': 'Notification settings saved', + 'admin.notifications.testWebhook': 'Send test webhook', + 'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully', + 'admin.notifications.testWebhookFailed': 'Test webhook failed', + 'admin.notifications.testNtfy': 'Send test ntfy', + 'admin.notifications.testNtfySuccess': 'Test ntfy sent successfully', + 'admin.notifications.testNtfyFailed': 'Test ntfy failed', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'In-app notifications are always active and cannot be disabled globally.', + 'admin.notifications.adminWebhookPanel.title': 'Admin Webhook', + 'admin.notifications.adminWebhookPanel.hint': + 'This webhook is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user webhooks and always fires when set.', + 'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL saved', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Test webhook sent successfully', + 'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Admin webhook always fires when a URL is configured', + 'admin.notifications.adminNtfyPanel.title': 'Admin Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + 'This ntfy topic is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user topics and always fires when configured.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy Server URL', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Also used as the default server for user ntfy notifications. Leave blank to default to ntfy.sh. Users can override this in their own settings.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Admin Topic', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': 'Access Token (optional)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Admin access token cleared', + 'admin.notifications.adminNtfyPanel.saved': 'Admin ntfy settings saved', + 'admin.notifications.adminNtfyPanel.test': 'Send test ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Test ntfy sent successfully', + 'admin.notifications.adminNtfyPanel.testFailed': 'Test ntfy failed', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Admin ntfy always fires when a topic is configured', + 'admin.notifications.adminNotificationsHint': + 'Configure which channels deliver admin-only notifications (e.g. version alerts).', + 'admin.notifications.tripReminders.title': 'Trip Reminders', + 'admin.notifications.tripReminders.hint': + 'Send a reminder notification before a trip starts (requires reminder days to be set on the trip).', + 'admin.notifications.tripReminders.enabled': 'Trip reminders enabled', + 'admin.notifications.tripReminders.disabled': 'Trip reminders disabled', + 'admin.smtp.title': 'Email & Notifications', + 'admin.smtp.hint': 'SMTP configuration for sending email notifications.', + 'admin.smtp.testButton': 'Send test email', + 'admin.webhook.hint': + 'Allow users to configure their own webhook URLs for notifications (Discord, Slack, etc.).', + 'admin.smtp.testSuccess': 'Test email sent successfully', + 'admin.smtp.testFailed': 'Test email failed', + 'admin.title': 'Yönetim', + 'admin.subtitle': 'Kullanıcı yönetimi ve sistem ayarları', + 'admin.tabs.users': 'Kullanıcılar', + 'admin.tabs.categories': 'Kategoriler', + 'admin.tabs.backup': 'Yedekleme', + 'admin.tabs.notifications': 'Bildirimler', + 'admin.tabs.audit': 'Denetim', + 'admin.stats.users': 'Kullanıcı', + 'admin.stats.trips': 'Gezi', + 'admin.stats.places': 'Yer', + 'admin.stats.photos': 'Fotoğraf', + 'admin.stats.files': 'Dosya', + 'admin.table.user': 'User', + 'admin.table.email': 'Email', + 'admin.table.role': 'Role', + 'admin.table.created': 'Created', + 'admin.table.lastLogin': 'Last Login', + 'admin.table.actions': 'Actions', + 'admin.you': '(You)', + 'admin.editUser': 'Edit User', + 'admin.newPassword': 'New Password', + 'admin.newPasswordHint': 'Leave empty to keep current password', + 'admin.deleteUser': + 'Delete user "{name}"? All trips will be permanently deleted.', + 'admin.deleteUserTitle': 'Delete user', + 'admin.newPasswordPlaceholder': 'Enter new password…', + 'admin.toast.loadError': 'Failed to load admin data', + 'admin.toast.userUpdated': 'User updated', + 'admin.toast.updateError': 'Failed to update', + 'admin.toast.userDeleted': 'User deleted', + 'admin.toast.deleteError': 'Failed to delete', + 'admin.toast.cannotDeleteSelf': 'Cannot delete your own account', + 'admin.toast.userCreated': 'User created', + 'admin.toast.createError': 'Failed to create user', + 'admin.toast.fieldsRequired': 'Username, email and password are required', + 'admin.createUser': 'Create User', + 'admin.invite.title': 'Invite Links', + 'admin.invite.subtitle': 'Create one-time registration links', + 'admin.invite.create': 'Create Link', + 'admin.invite.createAndCopy': 'Create & Copy', + 'admin.invite.empty': 'No invite links created yet', + 'admin.invite.maxUses': 'Max. Uses', + 'admin.invite.expiry': 'Expires after', + 'admin.invite.uses': 'used', + 'admin.invite.expiresAt': 'expires', + 'admin.invite.createdBy': 'by', + 'admin.invite.active': 'Active', + 'admin.invite.expired': 'Expired', + 'admin.invite.usedUp': 'Used up', + 'admin.invite.copied': 'Invite link copied to clipboard', + 'admin.invite.copyLink': 'Copy link', + 'admin.invite.deleted': 'Invite link deleted', + 'admin.invite.createError': 'Failed to create invite link', + 'admin.invite.deleteError': 'Failed to delete invite link', + 'admin.tabs.settings': 'Ayarlar', + 'admin.allowRegistration': 'Allow Registration', + 'admin.allowRegistrationHint': 'New users can register themselves', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': 'Require two-factor authentication (2FA)', + 'admin.requireMfaHint': + 'Users without 2FA must complete setup in Settings before using the app.', + 'admin.apiKeys': 'API Keys', + 'admin.apiKeysHint': + 'Optional. Enables extended place data like photos and weather.', + 'admin.mapsKey': 'Google Maps API Key', + 'admin.mapsKeyHint': + 'Required for place search. Get at console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Without an API key, OpenStreetMap is used for place search. With a Google API key, photos, ratings, and opening hours can be loaded as well. Get one at console.cloud.google.com.', + 'admin.recommended': 'Recommended', + 'admin.weatherKey': 'OpenWeatherMap API Key', + 'admin.weatherKeyHint': 'For weather data. Free at openweathermap.org', + 'admin.validateKey': 'Test', + 'admin.keyValid': 'Connected', + 'admin.keyInvalid': 'Invalid', + 'admin.keySaved': 'API keys saved', + 'admin.oidcTitle': 'Single Sign-On (OIDC)', + 'admin.oidcSubtitle': + 'Allow login via external providers like Google, Apple, Authentik or Keycloak.', + 'admin.oidcDisplayName': 'Display Name', + 'admin.oidcIssuer': 'Issuer URL', + 'admin.oidcIssuerHint': + 'The OpenID Connect Issuer URL of the provider. e.g. https://accounts.google.com', + 'admin.oidcSaved': 'OIDC configuration saved', + 'admin.oidcOnlyMode': 'Disable password authentication', + 'admin.oidcOnlyModeHint': + 'When enabled, only SSO login is permitted. Password-based login and registration are blocked.', + 'admin.fileTypes': 'Allowed File Types', + 'admin.fileTypesHint': 'Configure which file types users can upload.', + 'admin.fileTypesFormat': + 'Comma-separated extensions (e.g. jpg,png,pdf,doc). Use * to allow all types.', + 'admin.fileTypesSaved': 'File type settings saved', + 'admin.placesPhotos.title': 'Place Photos', + 'admin.placesPhotos.subtitle': + 'Fetch photos from the Google Places API. Disable to save API quota. Wikimedia photos are unaffected.', + 'admin.placesAutocomplete.title': 'Place Autocomplete', + 'admin.placesAutocomplete.subtitle': + 'Use the Google Places API for search suggestions. Disable to save API quota.', + 'admin.placesDetails.title': 'Place Details', + 'admin.placesDetails.subtitle': + 'Fetch detailed place information (hours, rating, website) from the Google Places API. Disable to save API quota.', + 'admin.bagTracking.title': 'Bag Tracking', + 'admin.bagTracking.subtitle': + 'Enable weight and bag assignment for packing items', + 'admin.collab.chat.title': 'Chat', + 'admin.collab.chat.subtitle': 'Real-time messaging for trip collaboration', + 'admin.collab.notes.title': 'Notes', + 'admin.collab.notes.subtitle': 'Shared notes and documents', + 'admin.collab.polls.title': 'Polls', + 'admin.collab.polls.subtitle': 'Group polls and voting', + 'admin.collab.whatsnext.title': "What's Next", + 'admin.collab.whatsnext.subtitle': 'Activity suggestions and next steps', + 'admin.tabs.config': 'Personalization', + 'admin.tabs.defaults': 'User Defaults', + 'admin.defaultSettings.title': 'Default User Settings', + 'admin.defaultSettings.description': + 'Set instance-wide defaults. Users who have not changed a setting will see these values. Their own changes always take priority.', + 'admin.defaultSettings.saved': 'Default saved', + 'admin.defaultSettings.reset': 'Reset to built-in default', + 'admin.defaultSettings.resetToBuiltIn': 'reset', + 'admin.tabs.templates': 'Packing Templates', + 'admin.packingTemplates.title': 'Packing Templates', + 'admin.packingTemplates.subtitle': + 'Create reusable packing lists for your trips', + 'admin.packingTemplates.create': 'New Template', + 'admin.packingTemplates.namePlaceholder': + 'Template name (e.g. Beach Holiday)', + 'admin.packingTemplates.empty': 'No templates created yet', + 'admin.packingTemplates.items': 'items', + 'admin.packingTemplates.categories': 'categories', + 'admin.packingTemplates.itemName': 'Item name', + 'admin.packingTemplates.itemCategory': 'Category', + 'admin.packingTemplates.categoryName': 'Category name (e.g. Clothing)', + 'admin.packingTemplates.addCategory': 'Add category', + 'admin.packingTemplates.created': 'Template created', + 'admin.packingTemplates.deleted': 'Template deleted', + 'admin.packingTemplates.loadError': 'Failed to load templates', + 'admin.packingTemplates.createError': 'Failed to create template', + 'admin.packingTemplates.deleteError': 'Failed to delete template', + 'admin.packingTemplates.saveError': 'Failed to save', + 'admin.tabs.addons': 'Addons', + 'admin.addons.title': 'Addons', + 'admin.addons.subtitle': + 'Enable or disable features to customize your TREK experience.', + 'admin.addons.catalog.packing.name': 'Lists', + 'admin.addons.catalog.packing.description': + 'Packing lists and to-do tasks for your trips', + 'admin.addons.catalog.budget.name': 'Budget', + 'admin.addons.catalog.budget.description': + 'Track expenses and plan your trip budget', + 'admin.addons.catalog.documents.name': 'Documents', + 'admin.addons.catalog.documents.description': + 'Store and manage travel documents', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Personal vacation planner with calendar view', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'World map with visited countries and travel stats', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Real-time notes, polls, and chat for trip planning', + 'admin.addons.catalog.memories.name': 'Photos (Immich)', + 'admin.addons.catalog.memories.description': + 'Share trip photos via your Immich instance', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Model Context Protocol for AI assistant integration', + 'admin.addons.subtitleBefore': + 'Enable or disable features to customize your ', + 'admin.addons.subtitleAfter': ' experience.', + 'admin.addons.enabled': 'Enabled', + 'admin.addons.disabled': 'Disabled', + 'admin.addons.type.trip': 'Trip', + 'admin.addons.type.global': 'Global', + 'admin.addons.type.integration': 'Integration', + 'admin.addons.tripHint': 'Available as a tab within each trip', + 'admin.addons.globalHint': + 'Available as a standalone section in the main navigation', + 'admin.addons.integrationHint': + 'Backend services and API integrations with no dedicated page', + 'admin.addons.toast.updated': 'Addon updated', + 'admin.addons.toast.error': 'Failed to update addon', + 'admin.addons.noAddons': 'No addons available', + 'admin.weather.title': 'Weather Data', + 'admin.weather.badge': 'Since March 24, 2026', + 'admin.weather.description': + 'TREK uses Open-Meteo as its weather data source. Open-Meteo is a free, open-source weather service — no API key required.', + 'admin.weather.forecast': '16-day forecast', + 'admin.weather.forecastDesc': 'Previously 5 days (OpenWeatherMap)', + 'admin.weather.climate': 'Historical climate data', + 'admin.weather.climateDesc': + 'Averages from the last 85 years for days beyond the 16-day forecast', + 'admin.weather.requests': '10,000 requests / day', + 'admin.weather.requestsDesc': 'Free, no API key required', + 'admin.weather.locationHint': + 'Weather is based on the first place with coordinates in each day. If no place is assigned to a day, any place from the place list is used as a reference.', + 'admin.tabs.mcpTokens': 'MCP Access', + 'admin.mcpTokens.title': 'MCP Access', + 'admin.mcpTokens.subtitle': + 'Manage OAuth sessions and API tokens across all users', + 'admin.mcpTokens.sectionTitle': 'API Tokens', + 'admin.mcpTokens.owner': 'Owner', + 'admin.mcpTokens.tokenName': 'Token Name', + 'admin.mcpTokens.created': 'Created', + 'admin.mcpTokens.lastUsed': 'Last Used', + 'admin.mcpTokens.never': 'Never', + 'admin.mcpTokens.empty': 'No MCP tokens have been created yet', + 'admin.mcpTokens.deleteTitle': 'Delete Token', + 'admin.mcpTokens.deleteMessage': + 'This will revoke the token immediately. The user will lose MCP access through this token.', + 'admin.mcpTokens.deleteSuccess': 'Token deleted', + 'admin.mcpTokens.deleteError': 'Failed to delete token', + 'admin.mcpTokens.loadError': 'Failed to load tokens', + 'admin.oauthSessions.sectionTitle': 'OAuth Sessions', + 'admin.oauthSessions.clientName': 'Client', + 'admin.oauthSessions.owner': 'Owner', + 'admin.oauthSessions.scopes': 'Scopes', + 'admin.oauthSessions.created': 'Created', + 'admin.oauthSessions.empty': 'No active OAuth sessions', + 'admin.oauthSessions.revokeTitle': 'Revoke Session', + 'admin.oauthSessions.revokeMessage': + 'This will revoke the OAuth session immediately. The client will lose MCP access.', + 'admin.oauthSessions.revokeSuccess': 'Session revoked', + 'admin.oauthSessions.revokeError': 'Failed to revoke session', + 'admin.oauthSessions.loadError': 'Failed to load OAuth sessions', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Security-sensitive and administration events (backups, users, MFA, settings).', + 'admin.audit.empty': 'No audit entries yet.', + 'admin.audit.refresh': 'Refresh', + 'admin.audit.loadMore': 'Load more', + 'admin.audit.showing': '{count} loaded · {total} total', + 'admin.audit.col.time': 'Time', + 'admin.audit.col.user': 'User', + 'admin.audit.col.action': 'Action', + 'admin.audit.col.resource': 'Resource', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Details', + 'admin.github.title': 'Release History', + 'admin.github.subtitle': 'Latest updates from {repo}', + 'admin.github.latest': 'Latest', + 'admin.github.prerelease': 'Pre-release', + 'admin.github.showDetails': 'Show details', + 'admin.github.hideDetails': 'Hide details', + 'admin.github.loadMore': 'Load more', + 'admin.github.loading': 'Loading...', + 'admin.github.error': 'Failed to load releases', + 'admin.github.by': 'by', + 'admin.github.support': 'Helps me keep building TREK', + 'admin.update.available': 'Update available', + 'admin.update.text': + 'TREK {version} is available. You are running {current}.', + 'admin.update.button': 'View on GitHub', + 'admin.update.install': 'Install Update', + 'admin.update.confirmTitle': 'Install Update?', + 'admin.update.confirmText': + 'TREK will be updated from {current} to {version}. The server will restart automatically afterwards.', + 'admin.update.dataInfo': + 'All your data (trips, users, API keys, uploads, Vacay, Atlas, budgets) will be preserved.', + 'admin.update.warning': + 'The app will be briefly unavailable during the restart.', + 'admin.update.confirm': 'Update Now', + 'admin.update.installing': 'Updating…', + 'admin.update.success': 'Update installed! Server is restarting…', + 'admin.update.failed': 'Update failed', + 'admin.update.backupHint': 'We recommend creating a backup before updating.', + 'admin.update.backupLink': 'Go to Backup', + 'admin.update.howTo': 'How to Update', + 'admin.update.dockerText': + 'Your TREK instance runs in Docker. To update to {version}, run the following commands on your server:', + 'admin.update.reloadHint': 'Please reload the page in a few seconds.', + 'admin.tabs.permissions': 'Permissions', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + 'Trip tracking & travel journal with check-ins, photos, and daily stories', +}; +export default admin; diff --git a/shared/src/i18n/tr/airport.ts b/shared/src/i18n/tr/airport.ts new file mode 100644 index 00000000..8be3c19d --- /dev/null +++ b/shared/src/i18n/tr/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Airport code or city (e.g. FRA)', +}; +export default airport; diff --git a/shared/src/i18n/tr/atlas.ts b/shared/src/i18n/tr/atlas.ts new file mode 100644 index 00000000..f960c86b --- /dev/null +++ b/shared/src/i18n/tr/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Your travel footprint around the world', + 'atlas.countries': 'Countries', + 'atlas.trips': 'Trips', + 'atlas.places': 'Places', + 'atlas.unmark': 'Remove', + 'atlas.confirmMark': 'Mark this country as visited?', + 'atlas.confirmUnmark': 'Remove this country from your visited list?', + 'atlas.confirmUnmarkRegion': 'Remove this region from your visited list?', + 'atlas.markVisited': 'Mark as visited', + 'atlas.markVisitedHint': 'Add this country to your visited list', + 'atlas.markRegionVisitedHint': 'Add this region to your visited list', + 'atlas.addToBucket': 'Add to bucket list', + 'atlas.addPoi': 'Add place', + 'atlas.searchCountry': 'Search a country...', + 'atlas.bucketNamePlaceholder': 'Name (country, city, place...)', + 'atlas.month': 'Month', + 'atlas.year': 'Year', + 'atlas.addToBucketHint': 'Save as a place you want to visit', + 'atlas.bucketWhen': 'When do you plan to visit?', + 'atlas.statsTab': 'Stats', + 'atlas.bucketTab': 'Bucket List', + 'atlas.addBucket': 'Add to bucket list', + 'atlas.bucketNotesPlaceholder': 'Notes (optional)', + 'atlas.bucketEmpty': 'Your bucket list is empty', + 'atlas.bucketEmptyHint': 'Add places you dream of visiting', + 'atlas.days': 'Days', + 'atlas.visitedCountries': 'Visited Countries', + 'atlas.cities': 'Cities', + 'atlas.noData': 'No travel data yet', + 'atlas.noDataHint': 'Create a trip and add places to see your world map', + 'atlas.lastTrip': 'Last trip', + 'atlas.nextTrip': 'Next trip', + 'atlas.daysLeft': 'days left', + 'atlas.streak': 'Streak', + 'atlas.years': 'years', + 'atlas.yearInRow': 'year in a row', + 'atlas.yearsInRow': 'years in a row', + 'atlas.tripIn': 'trip in', + 'atlas.tripsIn': 'trips in', + 'atlas.since': 'since', + 'atlas.europe': 'Europe', + 'atlas.asia': 'Asia', + 'atlas.northAmerica': 'N. America', + 'atlas.southAmerica': 'S. America', + 'atlas.africa': 'Africa', + 'atlas.oceania': 'Oceania', + 'atlas.other': 'Other', + 'atlas.firstVisit': 'First trip', + 'atlas.lastVisitLabel': 'Last trip', + 'atlas.tripSingular': 'Trip', + 'atlas.tripPlural': 'Trips', + 'atlas.placeVisited': 'Place visited', + 'atlas.placesVisited': 'Places visited', +}; +export default atlas; diff --git a/shared/src/i18n/tr/backup.ts b/shared/src/i18n/tr/backup.ts new file mode 100644 index 00000000..e1bc00ff --- /dev/null +++ b/shared/src/i18n/tr/backup.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Data Backup', + 'backup.subtitle': 'Database and all uploaded files', + 'backup.refresh': 'Refresh', + 'backup.upload': 'Upload Backup', + 'backup.uploading': 'Uploading…', + 'backup.create': 'Create Backup', + 'backup.creating': 'Creating…', + 'backup.empty': 'No backups yet', + 'backup.createFirst': 'Create first backup', + 'backup.download': 'Download', + 'backup.restore': 'Restore', + 'backup.confirm.restore': + 'Restore backup "{name}"?\n\nAll current data will be replaced with the backup.', + 'backup.confirm.uploadRestore': + 'Upload and restore backup file "{name}"?\n\nAll current data will be overwritten.', + 'backup.confirm.delete': 'Delete backup "{name}"?', + 'backup.toast.loadError': 'Failed to load backups', + 'backup.toast.created': 'Backup created successfully', + 'backup.toast.createError': 'Failed to create backup', + 'backup.toast.restored': 'Backup restored. Page will reload…', + 'backup.toast.restoreError': 'Failed to restore', + 'backup.toast.uploadError': 'Failed to upload', + 'backup.toast.deleted': 'Backup deleted', + 'backup.toast.deleteError': 'Failed to delete', + 'backup.toast.downloadError': 'Download failed', + 'backup.toast.settingsSaved': 'Auto-backup settings saved', + 'backup.toast.settingsError': 'Failed to save settings', + 'backup.auto.title': 'Auto-Backup', + 'backup.auto.subtitle': 'Automatic backup on a schedule', + 'backup.auto.enable': 'Enable auto-backup', + 'backup.auto.enableHint': + 'Backups will be created automatically on the chosen schedule', + 'backup.auto.interval': 'Interval', + 'backup.auto.hour': 'Run at hour', + 'backup.auto.hourHint': 'Server local time ({format} format)', + 'backup.auto.dayOfWeek': 'Day of week', + 'backup.auto.dayOfMonth': 'Day of month', + 'backup.auto.dayOfMonthHint': + 'Limited to 1–28 for compatibility with all months', + 'backup.auto.scheduleSummary': 'Schedule', + 'backup.auto.summaryDaily': 'Every day at {hour}:00', + 'backup.auto.summaryWeekly': 'Every {day} at {hour}:00', + 'backup.auto.summaryMonthly': 'Day {day} of every month at {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Auto-backup is configured via Docker environment variables. To change these settings, update your docker-compose.yml and restart the container.', + 'backup.auto.copyEnv': 'Copy Docker env vars', + 'backup.auto.envCopied': 'Docker env vars copied to clipboard', + 'backup.auto.keepLabel': 'Delete old backups after', + 'backup.dow.sunday': 'Sun', + 'backup.dow.monday': 'Mon', + 'backup.dow.tuesday': 'Tue', + 'backup.dow.wednesday': 'Wed', + 'backup.dow.thursday': 'Thu', + 'backup.dow.friday': 'Fri', + 'backup.dow.saturday': 'Sat', + 'backup.interval.hourly': 'Hourly', + 'backup.interval.daily': 'Daily', + 'backup.interval.weekly': 'Weekly', + 'backup.interval.monthly': 'Monthly', + 'backup.keep.1day': '1 day', + 'backup.keep.3days': '3 days', + 'backup.keep.7days': '7 days', + 'backup.keep.14days': '14 days', + 'backup.keep.30days': '30 days', + 'backup.keep.forever': 'Keep forever', + 'backup.restoreConfirmTitle': 'Restore Backup?', + 'backup.restoreWarning': + 'All current data (trips, places, users, uploads) will be permanently replaced by the backup. This action cannot be undone.', + 'backup.restoreTip': + 'Tip: Create a backup of the current state before restoring.', + 'backup.restoreConfirm': 'Yes, restore', +}; +export default backup; diff --git a/shared/src/i18n/tr/budget.ts b/shared/src/i18n/tr/budget.ts new file mode 100644 index 00000000..733abca5 --- /dev/null +++ b/shared/src/i18n/tr/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Budget', + 'budget.exportCsv': 'Export CSV', + 'budget.emptyTitle': 'No budget created yet', + 'budget.emptyText': + 'Create categories and entries to plan your travel budget', + 'budget.emptyPlaceholder': 'Enter category name...', + 'budget.createCategory': 'Create Category', + 'budget.category': 'Category', + 'budget.categoryName': 'Category Name', + 'budget.table.name': 'Name', + 'budget.table.total': 'Total', + 'budget.table.persons': 'Persons', + 'budget.table.days': 'Days', + 'budget.table.perPerson': 'Per Person', + 'budget.table.perDay': 'Per Day', + 'budget.table.perPersonDay': 'P. p / Day', + 'budget.table.note': 'Note', + 'budget.table.date': 'Date', + 'budget.newEntry': 'New Entry', + 'budget.defaultEntry': 'New Entry', + 'budget.defaultCategory': 'New Category', + 'budget.total': 'Total', + 'budget.totalBudget': 'Total Budget', + 'budget.byCategory': 'By Category', + 'budget.editTooltip': 'Click to edit', + 'budget.linkedToReservation': 'Linked to a reservation — edit the name there', + 'budget.confirm.deleteCategory': + 'Are you sure you want to delete the category "{name}" with {count} entries?', + 'budget.deleteCategory': 'Delete Category', + 'budget.perPerson': 'Per Person', + 'budget.paid': 'Paid', + 'budget.open': 'Open', + 'budget.noMembers': 'No members assigned', + 'budget.settlement': 'Settlement', + 'budget.settlementInfo': + 'Click a member avatar on a budget item to mark them green — this means they paid. The settlement then shows who owes whom and how much.', + 'budget.netBalances': 'Net Balances', + 'budget.categoriesLabel': 'categories', +}; +export default budget; diff --git a/shared/src/i18n/tr/categories.ts b/shared/src/i18n/tr/categories.ts new file mode 100644 index 00000000..adff391f --- /dev/null +++ b/shared/src/i18n/tr/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Categories', + 'categories.subtitle': 'Manage categories for places', + 'categories.new': 'New Category', + 'categories.empty': 'No categories yet', + 'categories.namePlaceholder': 'Category name', + 'categories.icon': 'Icon', + 'categories.color': 'Color', + 'categories.customColor': 'Choose custom color', + 'categories.preview': 'Preview', + 'categories.defaultName': 'Category', + 'categories.update': 'Update', + 'categories.create': 'Create', + 'categories.confirm.delete': + 'Delete category? Places in this category will not be deleted.', + 'categories.toast.loadError': 'Failed to load categories', + 'categories.toast.nameRequired': 'Please enter a name', + 'categories.toast.updated': 'Category updated', + 'categories.toast.created': 'Category created', + 'categories.toast.saveError': 'Failed to save', + 'categories.toast.deleted': 'Category deleted', + 'categories.toast.deleteError': 'Failed to delete', +}; +export default categories; diff --git a/shared/src/i18n/tr/collab.ts b/shared/src/i18n/tr/collab.ts new file mode 100644 index 00000000..aee6b9a5 --- /dev/null +++ b/shared/src/i18n/tr/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Chat', + 'collab.tabs.notes': 'Notes', + 'collab.tabs.polls': 'Polls', + 'collab.whatsNext.title': "What's Next", + 'collab.whatsNext.today': 'Today', + 'collab.whatsNext.tomorrow': 'Tomorrow', + 'collab.whatsNext.empty': 'No upcoming activities', + 'collab.whatsNext.until': 'to', + 'collab.whatsNext.emptyHint': 'Activities with times will appear here', + 'collab.chat.send': 'Send', + 'collab.chat.placeholder': 'Type a message...', + 'collab.chat.empty': 'Start the conversation', + 'collab.chat.emptyHint': 'Messages are shared with all trip members', + 'collab.chat.emptyDesc': + 'Share ideas, plans, and updates with your travel group', + 'collab.chat.today': 'Today', + 'collab.chat.yesterday': 'Yesterday', + 'collab.chat.deletedMessage': 'deleted a message', + 'collab.chat.reply': 'Reply', + 'collab.chat.loadMore': 'Load older messages', + 'collab.chat.justNow': 'just now', + 'collab.chat.minutesAgo': '{n}m ago', + 'collab.chat.hoursAgo': '{n}h ago', + 'collab.notes.title': 'Notes', + 'collab.notes.new': 'New Note', + 'collab.notes.empty': 'No notes yet', + 'collab.notes.emptyHint': 'Start capturing ideas and plans', + 'collab.notes.all': 'All', + 'collab.notes.titlePlaceholder': 'Note title', + 'collab.notes.contentPlaceholder': 'Write something...', + 'collab.notes.categoryPlaceholder': 'Category', + 'collab.notes.newCategory': 'New category...', + 'collab.notes.category': 'Category', + 'collab.notes.noCategory': 'No category', + 'collab.notes.color': 'Color', + 'collab.notes.save': 'Save', + 'collab.notes.cancel': 'Cancel', + 'collab.notes.edit': 'Edit', + 'collab.notes.delete': 'Delete', + 'collab.notes.pin': 'Pin', + 'collab.notes.unpin': 'Unpin', + 'collab.notes.daysAgo': '{n}d ago', + 'collab.notes.categorySettings': 'Manage Categories', + 'collab.notes.create': 'Create', + 'collab.notes.website': 'Website', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Attach files', + 'collab.notes.noCategoriesYet': 'No categories yet', + 'collab.notes.emptyDesc': 'Create a note to get started', + 'collab.polls.title': 'Polls', + 'collab.polls.new': 'New Poll', + 'collab.polls.empty': 'No polls yet', + 'collab.polls.emptyHint': 'Ask the group and vote together', + 'collab.polls.question': 'Question', + 'collab.polls.questionPlaceholder': 'What should we do?', + 'collab.polls.addOption': '+ Add option', + 'collab.polls.optionPlaceholder': 'Option {n}', + 'collab.polls.create': 'Create Poll', + 'collab.polls.close': 'Close', + 'collab.polls.closed': 'Closed', + 'collab.polls.votes': '{n} votes', + 'collab.polls.vote': '{n} vote', + 'collab.polls.multipleChoice': 'Multiple choice', + 'collab.polls.multiChoice': 'Multiple choice', + 'collab.polls.deadline': 'Deadline', + 'collab.polls.option': 'Option', + 'collab.polls.options': 'Options', + 'collab.polls.delete': 'Delete', + 'collab.polls.closedSection': 'Closed', +}; +export default collab; diff --git a/shared/src/i18n/tr/common.ts b/shared/src/i18n/tr/common.ts new file mode 100644 index 00000000..dbd5e826 --- /dev/null +++ b/shared/src/i18n/tr/common.ts @@ -0,0 +1,55 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Kaydet', + 'common.showMore': 'Daha fazla göster', + 'common.showLess': 'Daha az göster', + 'common.cancel': 'İptal', + 'common.clear': 'Temizle', + 'common.delete': 'Sil', + 'common.edit': 'Düzenle', + 'common.add': 'Ekle', + 'common.loading': 'Yükleniyor...', + 'common.import': 'İçe aktar', + 'common.select': 'Seç', + 'common.selectAll': 'Tümünü seç', + 'common.deselectAll': 'Seçimi kaldır', + 'common.error': 'Hata', + 'common.unknownError': 'Bilinmeyen hata', + 'common.tooManyAttempts': + 'Çok fazla deneme. Lütfen daha sonra tekrar deneyin.', + 'common.back': 'Geri', + 'common.all': 'Tümü', + 'common.close': 'Kapat', + 'common.open': 'Aç', + 'common.upload': 'Yükle', + 'common.search': 'Ara', + 'common.confirm': 'Onayla', + 'common.ok': 'OK', + 'common.yes': 'Evet', + 'common.no': 'Hayır', + 'common.or': 'veya', + 'common.none': 'Yok', + 'common.date': 'Tarih', + 'common.rename': 'Yeniden adlandır', + 'common.discardChanges': 'Değişiklikleri iptal et', + 'common.discard': 'Vazgeç', + 'common.name': 'Ad', + 'common.email': 'E-posta', + 'common.password': 'Parola', + 'common.saving': 'Kaydediliyor...', + 'common.justNow': 'az önce', + 'common.hoursAgo': '{count}s önce', + 'common.daysAgo': '{count}g önce', + 'common.saved': 'Kaydedildi', + 'common.update': 'Güncelle', + 'common.change': 'Değiştir', + 'common.uploading': 'Yükleniyor…', + 'common.backToPlanning': 'Planlamaya dön', + 'common.reset': 'Sıfırla', + 'common.expand': 'Genişlet', + 'common.collapse': 'Daralt', + 'common.copy': 'Copy', + 'common.copied': 'Copied', +}; +export default common; diff --git a/shared/src/i18n/tr/dashboard.ts b/shared/src/i18n/tr/dashboard.ts new file mode 100644 index 00000000..721b6885 --- /dev/null +++ b/shared/src/i18n/tr/dashboard.ts @@ -0,0 +1,121 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Gezilerim', + 'dashboard.subtitle.loading': 'Geziler yükleniyor...', + 'dashboard.subtitle.trips': '{count} gezi ({archived} arşivlenmiş)', + 'dashboard.subtitle.empty': 'İlk gezinizi başlatın', + 'dashboard.subtitle.activeOne': '{count} aktif gezi', + 'dashboard.subtitle.activeMany': '{count} aktif gezi', + 'dashboard.subtitle.archivedSuffix': ' · {count} arşivlendi', + 'dashboard.newTrip': 'Yeni gezi', + 'dashboard.gridView': 'Izgara görünümü', + 'dashboard.listView': 'Liste görünümü', + 'dashboard.currency': 'Para birimi', + 'dashboard.timezone': 'Saat dilimleri', + 'dashboard.localTime': 'Yerel', + 'dashboard.timezoneCustomTitle': 'Özel saat dilimi', + 'dashboard.timezoneCustomLabelPlaceholder': 'Etiket (opsiyonel)', + 'dashboard.timezoneCustomTzPlaceholder': 'örn. Europe/Istanbul', + 'dashboard.timezoneCustomAdd': 'Ekle', + 'dashboard.timezoneCustomErrorEmpty': 'Bir saat dilimi kimliği girin', + 'dashboard.timezoneCustomErrorInvalid': + 'Geçersiz saat dilimi. Europe/Istanbul gibi bir format kullanın', + 'dashboard.timezoneCustomErrorDuplicate': 'Zaten eklendi', + 'dashboard.emptyTitle': 'Henüz gezi yok', + 'dashboard.emptyText': 'İlk gezinizi oluşturun ve planlamaya başlayın!', + 'dashboard.emptyButton': 'İlk geziyi oluştur', + 'dashboard.nextTrip': 'Sıradaki gezi', + 'dashboard.shared': 'Paylaşılan', + 'dashboard.sharedBy': '{name} tarafından paylaşıldı', + 'dashboard.days': 'Gün', + 'dashboard.places': 'Yer', + 'dashboard.members': 'Arkadaş', + 'dashboard.archive': 'Arşivle', + 'dashboard.copyTrip': 'Kopyala', + 'dashboard.copySuffix': 'kopya', + 'dashboard.restore': 'Geri yükle', + 'dashboard.archived': 'Arşivlendi', + 'dashboard.status.ongoing': 'Devam ediyor', + 'dashboard.status.today': 'Bugün', + 'dashboard.status.tomorrow': 'Yarın', + 'dashboard.status.past': 'Geçmiş', + 'dashboard.status.daysLeft': '{count} gün kaldı', + 'dashboard.toast.loadError': 'Failed to load trips', + 'dashboard.toast.created': 'Trip created successfully!', + 'dashboard.toast.createError': 'Failed to create trip', + 'dashboard.toast.updated': 'Trip updated!', + 'dashboard.toast.updateError': 'Failed to update trip', + 'dashboard.toast.deleted': 'Trip deleted', + 'dashboard.toast.deleteError': 'Failed to delete trip', + 'dashboard.toast.archived': 'Trip archived', + 'dashboard.toast.archiveError': 'Failed to archive trip', + 'dashboard.toast.restored': 'Trip restored', + 'dashboard.toast.restoreError': 'Failed to restore trip', + 'dashboard.toast.copied': 'Trip copied!', + 'dashboard.toast.copyError': 'Failed to copy trip', + 'dashboard.confirm.delete': + 'Delete trip "{title}"? All places and plans will be permanently deleted.', + 'dashboard.confirm.copy.title': 'Copy this trip?', + 'dashboard.confirm.copy.willCopy': 'Will be copied', + 'dashboard.confirm.copy.will1': 'Days, places & day assignments', + 'dashboard.confirm.copy.will2': 'Accommodations & reservations', + 'dashboard.confirm.copy.will3': 'Budget items & category order', + 'dashboard.confirm.copy.will4': 'Packing lists (unchecked)', + 'dashboard.confirm.copy.will5': 'TODOs (unassigned & unchecked)', + 'dashboard.confirm.copy.will6': 'Day notes', + 'dashboard.confirm.copy.wontCopy': "Won't be copied", + 'dashboard.confirm.copy.wont1': 'Collaborators & member assignments', + 'dashboard.confirm.copy.wont2': 'Collab notes, polls & messages', + 'dashboard.confirm.copy.wont3': 'Files & photos', + 'dashboard.confirm.copy.wont4': 'Share tokens', + 'dashboard.confirm.copy.confirm': 'Copy trip', + 'dashboard.editTrip': 'Geziyi düzenle', + 'dashboard.createTrip': 'Yeni gezi oluştur', + 'dashboard.tripTitle': 'Başlık', + 'dashboard.tripTitlePlaceholder': 'e.g. Summer in Japan', + 'dashboard.tripDescription': 'Açıklama', + 'dashboard.tripDescriptionPlaceholder': 'What is this trip about?', + 'dashboard.startDate': 'Başlangıç tarihi', + 'dashboard.endDate': 'Bitiş tarihi', + 'dashboard.dayCount': 'Gün sayısı', + 'dashboard.dayCountHint': + 'How many days to plan for when no travel dates are set.', + 'dashboard.noDateHint': + 'No date set — 7 default days will be created. You can change this anytime.', + 'dashboard.coverImage': 'Kapak resmi', + 'dashboard.addCoverImage': 'Add cover image (or drag & drop)', + 'dashboard.addMembers': 'Seyahat arkadaşları', + 'dashboard.addMember': 'Üye ekle', + 'dashboard.coverSaved': 'Cover image saved', + 'dashboard.coverUploadError': 'Failed to upload', + 'dashboard.coverRemoveError': 'Failed to remove', + 'dashboard.titleRequired': 'Başlık zorunludur', + 'dashboard.endDateError': 'Bitiş tarihi başlangıç tarihinden sonra olmalıdır', + 'dashboard.greeting.morning': 'Good morning,', + 'dashboard.greeting.afternoon': 'Good afternoon,', + 'dashboard.greeting.evening': 'Good evening,', + 'dashboard.mobile.liveNow': 'Live Now', + 'dashboard.mobile.tripProgress': 'Trip progress', + 'dashboard.mobile.daysLeft': '{count} days left', + 'dashboard.mobile.places': 'Places', + 'dashboard.mobile.buddies': 'Buddies', + 'dashboard.mobile.newTrip': 'New Trip', + 'dashboard.mobile.currency': 'Currency', + 'dashboard.mobile.timezone': 'Timezone', + 'dashboard.mobile.upcomingTrips': 'Upcoming Trips', + 'dashboard.mobile.yourTrips': 'Your Trips', + 'dashboard.mobile.trips': 'trips', + 'dashboard.mobile.starts': 'Starts', + 'dashboard.mobile.duration': 'Duration', + 'dashboard.mobile.day': 'day', + 'dashboard.mobile.days': 'days', + 'dashboard.mobile.ongoing': 'Ongoing', + 'dashboard.mobile.startsToday': 'Starts today', + 'dashboard.mobile.tomorrow': 'Tomorrow', + 'dashboard.mobile.inDays': 'In {count} days', + 'dashboard.mobile.inMonths': 'In {count} months', + 'dashboard.mobile.completed': 'Completed', + 'dashboard.mobile.currencyConverter': 'Currency Converter', +}; +export default dashboard; diff --git a/shared/src/i18n/tr/day.ts b/shared/src/i18n/tr/day.ts new file mode 100644 index 00000000..96f47973 --- /dev/null +++ b/shared/src/i18n/tr/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Rain probability', + 'day.precipitation': 'Precipitation', + 'day.wind': 'Wind', + 'day.sunrise': 'Sunrise', + 'day.sunset': 'Sunset', + 'day.hourlyForecast': 'Hourly Forecast', + 'day.climateHint': + 'Historical averages — real forecast available within 16 days of this date.', + 'day.noWeather': 'No weather data available. Add a place with coordinates.', + 'day.overview': 'Daily Overview', + 'day.accommodation': 'Accommodation', + 'day.addAccommodation': 'Add accommodation', + 'day.hotelDayRange': 'Apply to days', + 'day.noPlacesForHotel': 'Add places to your trip first', + 'day.allDays': 'All', + 'day.checkIn': 'Check-in', + 'day.checkInUntil': 'Until', + 'day.checkOut': 'Check-out', + 'day.confirmation': 'Confirmation', + 'day.editAccommodation': 'Edit accommodation', + 'day.reservations': 'Reservations', +}; +export default day; diff --git a/shared/src/i18n/tr/dayplan.ts b/shared/src/i18n/tr/dayplan.ts new file mode 100644 index 00000000..8f1825f6 --- /dev/null +++ b/shared/src/i18n/tr/dayplan.ts @@ -0,0 +1,48 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Export calendar (ICS)', + 'dayplan.emptyDay': 'No places planned for this day', + 'dayplan.cannotReorderTransport': + 'Bookings with a fixed time cannot be reordered', + 'dayplan.confirmRemoveTimeTitle': 'Remove time?', + 'dayplan.confirmRemoveTimeBody': + 'This place has a fixed time ({time}). Moving it will remove the time and allow free sorting.', + 'dayplan.confirmRemoveTimeAction': 'Remove time & move', + 'dayplan.cannotDropOnTimed': + 'Items cannot be placed between time-bound entries', + 'dayplan.cannotBreakChronology': + 'This would break the chronological order of timed items and bookings', + 'dayplan.addNote': 'Add Note', + 'dayplan.expandAll': 'Expand all days', + 'dayplan.collapseAll': 'Collapse all days', + 'dayplan.editNote': 'Edit Note', + 'dayplan.noteAdd': 'Add Note', + 'dayplan.noteEdit': 'Edit Note', + 'dayplan.noteTitle': 'Note', + 'dayplan.noteSubtitle': 'Daily Note', + 'dayplan.totalCost': 'Total Cost', + 'dayplan.days': 'Days', + 'dayplan.dayN': 'Day {n}', + 'dayplan.calculating': 'Calculating...', + 'dayplan.route': 'Route', + 'dayplan.optimize': 'Optimize', + 'dayplan.optimized': 'Route optimized', + 'dayplan.routeError': 'Failed to calculate route', + 'dayplan.toast.needTwoPlaces': + 'At least two places needed for route optimization', + 'dayplan.toast.routeOptimized': 'Route optimized', + 'dayplan.toast.noGeoPlaces': + 'No places with coordinates found for route calculation', + 'dayplan.confirmed': 'Confirmed', + 'dayplan.pendingRes': 'Pending', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Export day plan as PDF', + 'dayplan.pdfError': 'Failed to export PDF', + 'dayplan.mobile.addPlace': 'Add Place', + 'dayplan.mobile.searchPlaces': 'Search places...', + 'dayplan.mobile.allAssigned': 'All places assigned', + 'dayplan.mobile.noMatch': 'No match', + 'dayplan.mobile.createNew': 'Create new place', +}; +export default dayplan; diff --git a/shared/src/i18n/tr/externalNotifications.ts b/shared/src/i18n/tr/externalNotifications.ts new file mode 100644 index 00000000..218d860a --- /dev/null +++ b/shared/src/i18n/tr/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const tr: NotificationLocale = { + email: { + footer: "TREK'te bildirimleri etkinleştirdiğiniz için bunu aldınız.", + manage: 'Ayarlarda tercihleri yönetin', + madeWith: 'Made with', + openTrek: "TREK'i aç", + }, + events: { + trip_invite: (p) => ({ + title: `"${p.trip}" seyahatine davet`, + body: `${p.actor}, ${p.invitee || 'bir üyeyi'} "${p.trip}" seyahatine davet etti.`, + }), + booking_change: (p) => ({ + title: `Yeni rezervasyon: ${p.booking}`, + body: `${p.actor}, "${p.trip}" seyahatine "${p.booking}" (${p.type}) rezervasyonu ekledi.`, + }), + trip_reminder: (p) => ({ + title: `Seyahat hatırlatıcısı: ${p.trip}`, + body: `"${p.trip}" seyahatiniz yaklaşıyor!`, + }), + todo_due: (p) => ({ + title: `Görev süresi dolmak üzere: ${p.todo}`, + body: `"${p.trip}" içindeki "${p.todo}" görevi ${p.due} tarihinde bitiyor.`, + }), + vacay_invite: (p) => ({ + title: 'Vacay Fusion Daveti', + body: `${p.actor} sizi tatil planlarını birleştirmeye davet etti. Kabul etmek veya reddetmek için TREK'i açın.`, + }), + photos_shared: (p) => ({ + title: `${p.count} fotoğraf paylaşıldı`, + body: `${p.actor}, "${p.trip}" içinde ${p.count} fotoğraf paylaştı.`, + }), + collab_message: (p) => ({ + title: `"${p.trip}" içinde yeni mesaj`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Bagaj: ${p.category}`, + body: `${p.actor}, sizi "${p.trip}" içindeki "${p.category}" bagaj kategorisine atadı.`, + }), + version_available: (p) => ({ + title: 'Yeni TREK sürümü mevcut', + body: `TREK ${p.version} artık mevcut. Güncellemek için yönetici panelini ziyaret edin.`, + }), + synology_session_cleared: () => ({ + title: 'Synology oturumu temizlendi', + body: 'Synology hesabınız veya URL değişti. Synology Photos oturumunuz kapatıldı.', + }), + }, + passwordReset: { + subject: 'Şifrenizi sıfırlayın', + greeting: 'Merhaba', + body: 'TREK hesabınızın şifresini sıfırlamak için bir istek aldık. Yeni bir şifre belirlemek için aşağıdaki butona tıklayın.', + ctaIntro: 'Şifreyi sıfırla', + expiry: 'Bu bağlantı 60 dakika içinde sona erer.', + ignore: + 'Bu isteği siz yapmadıysanız, bu e-postayı güvenle yok sayabilirsiniz — şifreniz değişmeyecektir.', + }, +}; + +export default tr; diff --git a/shared/src/i18n/tr/files.ts b/shared/src/i18n/tr/files.ts new file mode 100644 index 00000000..779ad58b --- /dev/null +++ b/shared/src/i18n/tr/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Files', + 'files.pageTitle': 'Files & Documents', + 'files.subtitle': '{count} files for {trip}', + 'files.download': 'Download', + 'files.openError': 'Could not open file', + 'files.downloadPdf': 'Download PDF', + 'files.count': '{count} files', + 'files.countSingular': '1 file', + 'files.uploaded': '{count} uploaded', + 'files.uploadError': 'Upload failed', + 'files.dropzone': 'Drop files here', + 'files.dropzoneHint': 'or click to browse', + 'files.allowedTypes': + 'Images, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Max 50 MB', + 'files.uploading': 'Uploading...', + 'files.filterAll': 'All', + 'files.filterPdf': 'PDFs', + 'files.filterImages': 'Images', + 'files.filterDocs': 'Documents', + 'files.filterCollab': 'Collab Notes', + 'files.sourceCollab': 'From Collab Notes', + 'files.empty': 'No files yet', + 'files.emptyHint': 'Upload files to attach them to your trip', + 'files.openTab': 'Open in new tab', + 'files.confirm.delete': 'Are you sure you want to delete this file?', + 'files.toast.deleted': 'File deleted', + 'files.toast.deleteError': 'Failed to delete file', + 'files.sourcePlan': 'Day Plan', + 'files.sourceBooking': 'Booking', + 'files.sourceTransport': 'Transport', + 'files.attach': 'Attach', + 'files.pasteHint': 'You can also paste images from clipboard (Ctrl+V)', + 'files.trash': 'Trash', + 'files.trashEmpty': 'Trash is empty', + 'files.emptyTrash': 'Empty Trash', + 'files.restore': 'Restore', + 'files.star': 'Star', + 'files.unstar': 'Unstar', + 'files.assign': 'Assign', + 'files.assignTitle': 'Assign File', + 'files.assignPlace': 'Place', + 'files.assignBooking': 'Booking', + 'files.assignTransport': 'Transport', + 'files.unassigned': 'Unassigned', + 'files.unlink': 'Remove link', + 'files.toast.trashed': 'Moved to trash', + 'files.toast.restored': 'File restored', + 'files.toast.trashEmptied': 'Trash emptied', + 'files.toast.assigned': 'File assigned', + 'files.toast.assignError': 'Assignment failed', + 'files.toast.restoreError': 'Restore failed', + 'files.confirm.permanentDelete': + 'Permanently delete this file? This cannot be undone.', + 'files.confirm.emptyTrash': + 'Permanently delete all trashed files? This cannot be undone.', + 'files.noteLabel': 'Note', + 'files.notePlaceholder': 'Add a note...', +}; +export default files; diff --git a/shared/src/i18n/tr/index.ts b/shared/src/i18n/tr/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/tr/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/tr/inspector.ts b/shared/src/i18n/tr/inspector.ts new file mode 100644 index 00000000..d42c8804 --- /dev/null +++ b/shared/src/i18n/tr/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Open', + 'inspector.closed': 'Closed', + 'inspector.openingHours': 'Opening Hours', + 'inspector.showHours': 'Show opening hours', + 'inspector.files': 'Files', + 'inspector.filesCount': '{count} files', + 'inspector.remove': 'Remove', + 'inspector.removeFromDay': 'Remove from Day', + 'inspector.addToDay': 'Add to Day', + 'inspector.confirmedRes': 'Confirmed Reservation', + 'inspector.pendingRes': 'Pending Reservation', + 'inspector.google': 'Open in Google Maps', + 'inspector.website': 'Open Website', + 'inspector.addRes': 'Reservation', + 'inspector.editRes': 'Edit Reservation', + 'inspector.participants': 'Participants', + 'inspector.trackStats': 'Track Stats', +}; +export default inspector; diff --git a/shared/src/i18n/tr/journey.ts b/shared/src/i18n/tr/journey.ts new file mode 100644 index 00000000..9da83379 --- /dev/null +++ b/shared/src/i18n/tr/journey.ts @@ -0,0 +1,244 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Search journeys…', + 'journey.search.noResults': 'No journeys match "{query}"', + 'journey.title': 'Journey', + 'journey.subtitle': 'Track your travels as they happen', + 'journey.new': 'New Journey', + 'journey.create': 'Create', + 'journey.titlePlaceholder': 'Where are you going?', + 'journey.empty': 'No journeys yet', + 'journey.emptyHint': 'Start documenting your next trip', + 'journey.deleted': 'Journey deleted', + 'journey.createError': 'Could not create journey', + 'journey.deleteError': 'Could not delete journey', + 'journey.deleteConfirmTitle': 'Delete', + 'journey.deleteConfirmMessage': 'Delete "{title}"? This cannot be undone.', + 'journey.deleteConfirmGeneric': 'Are you sure you want to delete this?', + 'journey.notFound': 'Journey not found', + 'journey.photos': 'Photos', + 'journey.timelineEmpty': 'No stops yet', + 'journey.timelineEmptyHint': + 'Add a check-in or write a journal entry to get started', + 'journey.status.draft': 'Draft', + 'journey.status.active': 'Active', + 'journey.status.completed': 'Completed', + 'journey.status.upcoming': 'Upcoming', + 'journey.status.archived': 'Archived', + 'journey.checkin.add': 'Check in', + 'journey.checkin.namePlaceholder': 'Location name', + 'journey.checkin.notesPlaceholder': 'Notes (optional)', + 'journey.checkin.save': 'Save', + 'journey.checkin.error': 'Could not save check-in', + 'journey.entry.add': 'Journal', + 'journey.entry.edit': 'Edit entry', + 'journey.entry.titlePlaceholder': 'Title (optional)', + 'journey.entry.bodyPlaceholder': 'What happened today?', + 'journey.entry.save': 'Save', + 'journey.entry.error': 'Could not save entry', + 'journey.photo.add': 'Photo', + 'journey.photo.uploadError': 'Upload failed', + 'journey.share.share': 'Share', + 'journey.share.public': 'Public', + 'journey.share.linkCopied': 'Public link copied', + 'journey.share.disabled': 'Public sharing disabled', + 'journey.editor.titlePlaceholder': 'Give this moment a name...', + 'journey.editor.bodyPlaceholder': 'Tell the story of this day...', + 'journey.editor.placePlaceholder': 'Location (optional)', + 'journey.editor.tagsPlaceholder': + 'Tags: hidden gem, best meal, must revisit...', + 'journey.visibility.private': 'Private', + 'journey.visibility.shared': 'Shared', + 'journey.visibility.public': 'Public', + 'journey.emptyState.title': 'Your story starts here', + 'journey.emptyState.subtitle': + 'Check in at a place or write your first journal entry', + 'journey.frontpage.subtitle': + "Turn your trips into stories you'll never forget", + 'journey.frontpage.createJourney': 'Create Journey', + 'journey.frontpage.activeJourney': 'Active Journey', + 'journey.frontpage.allJourneys': 'All Journeys', + 'journey.frontpage.journeys': 'journeys', + 'journey.frontpage.createNew': 'Create a new Journey', + 'journey.frontpage.createNewSub': + 'Pick trips, write stories, share your adventures', + 'journey.frontpage.live': 'Live', + 'journey.frontpage.synced': 'Synced', + 'journey.frontpage.continueWriting': 'Continue writing', + 'journey.frontpage.updated': 'Updated {time}', + 'journey.frontpage.suggestionLabel': 'Trip just ended', + 'journey.frontpage.suggestionText': + 'Turn {title} into a Journey', + 'journey.frontpage.dismiss': 'Dismiss', + 'journey.frontpage.journeyName': 'Journey Name', + 'journey.frontpage.namePlaceholder': 'e.g. Southeast Asia 2026', + 'journey.frontpage.selectTrips': 'Select Trips', + 'journey.frontpage.tripsSelected': 'trips selected', + 'journey.frontpage.trips': 'trips', + 'journey.frontpage.placesImported': 'places will be imported', + 'journey.frontpage.places': 'places', + 'journey.detail.backToJourney': 'Back to Journey', + 'journey.detail.syncedWithTrips': 'Synced with Trips', + 'journey.detail.addEntry': 'Add Entry', + 'journey.detail.newEntry': 'New Entry', + 'journey.detail.editEntry': 'Edit Entry', + 'journey.detail.noEntries': 'No entries yet', + 'journey.detail.noEntriesHint': + 'Add a trip to get started with skeleton entries', + 'journey.detail.noPhotos': 'No photos yet', + 'journey.detail.noPhotosHint': + 'Upload photos to entries or browse your Immich/Synology library', + 'journey.detail.journeyTab': 'Journey', + 'journey.detail.journeyStats': 'Journey Stats', + 'journey.detail.syncedTrips': 'Synced Trips', + 'journey.detail.noTripsLinked': 'No trips linked yet', + 'journey.detail.contributors': 'Contributors', + 'journey.detail.readMore': 'Read more', + 'journey.detail.prosCons': 'Pros & Cons', + 'journey.detail.photos': 'photos', + 'journey.detail.day': 'Day {number}', + 'journey.detail.places': 'places', + 'journey.stats.days': 'Days', + 'journey.stats.cities': 'Cities', + 'journey.stats.entries': 'Entries', + 'journey.stats.photos': 'Photos', + 'journey.stats.places': 'Places', + 'journey.skeletons.show': 'Show suggestions', + 'journey.skeletons.hide': 'Hide suggestions', + 'journey.verdict.lovedIt': 'Loved it', + 'journey.verdict.couldBeBetter': 'Could be better', + 'journey.synced.places': 'places', + 'journey.synced.synced': 'synced', + 'journey.editor.discardChangesConfirm': + 'You have unsaved changes. Discard them?', + 'journey.editor.uploadPhotos': 'Upload photos', + 'journey.editor.uploading': 'Uploading...', + 'journey.editor.fromGallery': 'From Gallery', + 'journey.editor.allPhotosAdded': 'All photos already added', + 'journey.editor.writeStory': 'Write your story...', + 'journey.editor.prosCons': 'Pros & Cons', + 'journey.editor.pros': 'Pros', + 'journey.editor.cons': 'Cons', + 'journey.editor.proPlaceholder': 'Something great...', + 'journey.editor.conPlaceholder': 'Not so great...', + 'journey.editor.addAnother': 'Add another', + 'journey.editor.date': 'Date', + 'journey.editor.location': 'Location', + 'journey.editor.searchLocation': 'Search location...', + 'journey.editor.mood': 'Mood', + 'journey.editor.weather': 'Weather', + 'journey.editor.photoFirst': '1st', + 'journey.editor.makeFirst': 'Make 1st', + 'journey.editor.searching': 'Searching...', + 'journey.mood.amazing': 'Amazing', + 'journey.mood.good': 'Good', + 'journey.mood.neutral': 'Neutral', + 'journey.mood.rough': 'Rough', + 'journey.weather.sunny': 'Sunny', + 'journey.weather.partly': 'Partly cloudy', + 'journey.weather.cloudy': 'Cloudy', + 'journey.weather.rainy': 'Rainy', + 'journey.weather.stormy': 'Stormy', + 'journey.weather.cold': 'Snowy', + 'journey.trips.linkTrip': 'Link Trip', + 'journey.trips.searchTrip': 'Search Trip', + 'journey.trips.searchPlaceholder': 'Trip name or destination...', + 'journey.trips.noTripsAvailable': 'No trips available', + 'journey.trips.link': 'Link', + 'journey.trips.tripLinked': 'Trip linked', + 'journey.trips.linkFailed': 'Failed to link trip', + 'journey.trips.addTrip': 'Add Trip', + 'journey.trips.unlinkTrip': 'Unlink Trip', + 'journey.trips.unlinkMessage': + 'Unlink "{title}"? All synced entries and photos from this trip will be permanently deleted. This cannot be undone.', + 'journey.trips.unlink': 'Unlink', + 'journey.trips.tripUnlinked': 'Trip unlinked', + 'journey.trips.unlinkFailed': 'Failed to unlink trip', + 'journey.trips.noTripsLinkedSettings': 'No trips linked', + 'journey.contributors.invite': 'Invite Contributor', + 'journey.contributors.searchUser': 'Search User', + 'journey.contributors.searchPlaceholder': 'Username or email...', + 'journey.contributors.noUsers': 'No users found', + 'journey.contributors.role': 'Role', + 'journey.contributors.added': 'Contributor added', + 'journey.contributors.addFailed': 'Failed to add contributor', + 'journey.contributors.remove': 'Remove contributor', + 'journey.contributors.removeConfirm': 'Remove {username} from this journey?', + 'journey.contributors.removed': 'Contributor removed', + 'journey.contributors.removeFailed': 'Failed to remove contributor', + 'journey.share.publicShare': 'Public Share', + 'journey.share.createLink': 'Create share link', + 'journey.share.linkCreated': 'Share link created', + 'journey.share.createFailed': 'Failed to create link', + 'journey.share.copy': 'Copy', + 'journey.share.copied': 'Copied!', + 'journey.share.timeline': 'Timeline', + 'journey.share.gallery': 'Gallery', + 'journey.share.map': 'Map', + 'journey.share.removeLink': 'Remove share link', + 'journey.share.linkDeleted': 'Share link deleted', + 'journey.share.deleteFailed': 'Failed to delete', + 'journey.share.updateFailed': 'Failed to update', + 'journey.invite.role': 'Role', + 'journey.invite.viewer': 'Viewer', + 'journey.invite.editor': 'Editor', + 'journey.invite.invite': 'Invite', + 'journey.invite.inviting': 'Inviting...', + 'journey.settings.title': 'Journey Settings', + 'journey.settings.coverImage': 'Cover Image', + 'journey.settings.changeCover': 'Change cover', + 'journey.settings.addCover': 'Add cover image', + 'journey.settings.name': 'Name', + 'journey.settings.subtitle': 'Subtitle', + 'journey.settings.subtitlePlaceholder': 'e.g. Thailand, Vietnam & Cambodia', + 'journey.settings.endJourney': 'Archive Journey', + 'journey.settings.reopenJourney': 'Restore Journey', + 'journey.settings.archived': 'Journey archived', + 'journey.settings.reopened': 'Journey reopened', + 'journey.settings.endDescription': + 'Hides the Live badge. You can reopen anytime.', + 'journey.settings.delete': 'Delete', + 'journey.settings.deleteJourney': 'Delete Journey', + 'journey.settings.deleteMessage': + 'Delete "{title}"? All entries and photos will be lost.', + 'journey.settings.saved': 'Settings saved', + 'journey.settings.saveFailed': 'Failed to save', + 'journey.settings.coverUpdated': 'Cover updated', + 'journey.settings.coverFailed': 'Upload failed', + 'journey.settings.failedToDelete': 'Failed to delete', + 'journey.entries.deleteTitle': 'Delete Entry', + 'journey.photosUploaded': '{count} photos uploaded', + 'journey.photosAdded': '{count} photos added', + 'journey.public.notFound': 'Not Found', + 'journey.public.notFoundMessage': + "This journey doesn't exist or the link has expired.", + 'journey.public.readOnly': 'Read-only · Public Journey', + 'journey.public.tagline': 'Travel Resource & Exploration Kit', + 'journey.public.sharedVia': 'Shared via', + 'journey.public.madeWith': 'Made with', + 'journey.pdf.journeyBook': 'Journey Book', + 'journey.pdf.madeWith': 'Made with TREK', + 'journey.pdf.day': 'Day', + 'journey.pdf.theEnd': 'The End', + 'journey.pdf.saveAsPdf': 'Save as PDF', + 'journey.pdf.pages': 'pages', + 'journey.picker.tripPeriod': 'Trip Period', + 'journey.picker.dateRange': 'Date Range', + 'journey.picker.allPhotos': 'All Photos', + 'journey.picker.albums': 'Albums', + 'journey.picker.selected': 'selected', + 'journey.picker.addTo': 'Add to', + 'journey.picker.newGallery': 'New Gallery', + 'journey.picker.selectAll': 'Select all', + 'journey.picker.deselectAll': 'Deselect all', + 'journey.picker.noAlbums': 'No albums found', + 'journey.picker.selectDate': 'Select date', + 'journey.picker.search': 'Search', + 'journey.editor.uploadingProgress': 'Yükleniyor {done}/{total}…', + 'journey.editor.uploadFailed': 'Fotoğraf yüklenemedi', + 'journey.editor.uploadPartialFailed': + '{total} fotoğraftan {failed} tanesi yüklenemedi — yeniden denemek için tekrar kaydedin', + 'journey.photosUploadFailed': 'Bazı fotoğraflar yüklenemedi', +}; +export default journey; diff --git a/shared/src/i18n/tr/login.ts b/shared/src/i18n/tr/login.ts new file mode 100644 index 00000000..34e19b3c --- /dev/null +++ b/shared/src/i18n/tr/login.ts @@ -0,0 +1,95 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Login failed. Please check your credentials.', + 'login.tagline': 'Your Trips.\nYour Plan.', + 'login.description': + 'Plan trips collaboratively with interactive maps, budgets, and real-time sync.', + 'login.features.maps': 'Interactive Maps', + 'login.features.mapsDesc': 'Google Places, routes & clustering', + 'login.features.realtime': 'Real-Time Sync', + 'login.features.realtimeDesc': 'Plan together via WebSocket', + 'login.features.budget': 'Budget Tracking', + 'login.features.budgetDesc': 'Categories, charts & per-person costs', + 'login.features.collab': 'Collaboration', + 'login.features.collabDesc': 'Multi-user with shared trips', + 'login.features.packing': 'Packing Lists', + 'login.features.packingDesc': 'Categories, progress & suggestions', + 'login.features.bookings': 'Reservations', + 'login.features.bookingsDesc': 'Flights, hotels, restaurants & more', + 'login.features.files': 'Documents', + 'login.features.filesDesc': 'Upload & manage documents', + 'login.features.routes': 'Smart Routes', + 'login.features.routesDesc': 'Auto-optimize & Google Maps export', + 'login.selfHosted': 'Self-hosted · Open Source · Your data stays yours', + 'login.title': 'Giriş yap', + 'login.subtitle': 'Tekrar hoş geldiniz', + 'login.signingIn': 'Giriş yapılıyor…', + 'login.signIn': 'Giriş yap', + 'login.createAdmin': 'Create Admin Account', + 'login.createAdminHint': 'Set up the first admin account for TREK.', + 'login.setNewPassword': 'Set New Password', + 'login.setNewPasswordHint': + 'You must change your password before continuing.', + 'login.createAccount': 'Hesap oluştur', + 'login.createAccountHint': 'Register a new account.', + 'login.creating': 'Creating…', + 'login.noAccount': "Don't have an account?", + 'login.hasAccount': 'Zaten hesabınız var mı?', + 'login.register': 'Kayıt ol', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': 'Kullanıcı adı', + 'login.oidc.registrationDisabled': + 'Registration is disabled. Contact your administrator.', + 'login.oidc.noEmail': 'No email received from provider.', + 'login.oidc.tokenFailed': 'Authentication failed.', + 'login.oidc.invalidState': 'Invalid session. Please try again.', + 'login.demoFailed': 'Demo login failed', + 'login.oidcSignIn': 'Sign in with {name}', + 'login.oidcOnly': + 'Password authentication is disabled. Please sign in using your SSO provider.', + 'login.oidcLoggedOut': + 'You have been logged out. Sign in again using your SSO provider.', + 'login.demoHint': 'Try the demo — no registration needed', + 'login.mfaTitle': 'Two-factor authentication', + 'login.mfaSubtitle': 'Enter the 6-digit code from your authenticator app.', + 'login.mfaCodeLabel': 'Verification code', + 'login.mfaCodeRequired': 'Enter the code from your authenticator app.', + 'login.mfaHint': 'Open Google Authenticator, Authy, or another TOTP app.', + 'login.mfaBack': '← Back to sign in', + 'login.mfaVerify': 'Verify', + 'login.invalidInviteLink': 'Invalid or expired invite link', + 'login.oidcFailed': 'OIDC login failed', + 'login.usernameRequired': 'Username is required', + 'login.passwordMinLength': 'Password must be at least 8 characters', + 'login.forgotPassword': 'Parolamı unuttum?', + 'login.forgotPasswordTitle': 'Reset your password', + 'login.forgotPasswordBody': + "Enter the email address you signed up with. If an account exists, we'll send a reset link.", + 'login.forgotPasswordSubmit': 'Send reset link', + 'login.forgotPasswordSentTitle': 'Check your email', + 'login.forgotPasswordSentBody': + 'If an account exists for that email, a reset link is on its way. It expires in 60 minutes.', + 'login.forgotPasswordSmtpHintOff': + "Heads up: your administrator hasn't configured SMTP, so the reset link will be written to the server console instead of being emailed.", + 'login.backToLogin': 'Girişe dön', + 'login.newPassword': 'New password', + 'login.confirmPassword': 'Confirm new password', + 'login.passwordsDontMatch': 'Parolalar eşleşmiyor', + 'login.mfaCode': '2FA code', + 'login.resetPasswordTitle': 'Set a new password', + 'login.resetPasswordBody': + 'Pick a strong password you haven’t used here before. Minimum 8 characters.', + 'login.resetPasswordMfaBody': + 'Enter your 2FA code or a backup code to complete the reset.', + 'login.resetPasswordSubmit': 'Reset password', + 'login.resetPasswordVerify': 'Verify & reset', + 'login.resetPasswordSuccessTitle': 'Password updated', + 'login.resetPasswordSuccessBody': + 'You can now sign in with your new password.', + 'login.resetPasswordInvalidLink': 'Invalid reset link', + 'login.resetPasswordInvalidLinkBody': + 'This link is missing or broken. Request a new one to continue.', + 'login.resetPasswordFailed': 'Reset failed. The link may have expired.', +}; +export default login; diff --git a/shared/src/i18n/tr/map.ts b/shared/src/i18n/tr/map.ts new file mode 100644 index 00000000..836e5a4a --- /dev/null +++ b/shared/src/i18n/tr/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Connections', + 'map.showConnections': 'Show booking routes', + 'map.hideConnections': 'Hide booking routes', +}; +export default map; diff --git a/shared/src/i18n/tr/members.ts b/shared/src/i18n/tr/members.ts new file mode 100644 index 00000000..dc2319b2 --- /dev/null +++ b/shared/src/i18n/tr/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Share Trip', + 'members.inviteUser': 'Invite User', + 'members.selectUser': 'Select user…', + 'members.invite': 'Invite', + 'members.allHaveAccess': 'All users already have access.', + 'members.access': 'Access', + 'members.person': 'person', + 'members.persons': 'persons', + 'members.you': 'you', + 'members.owner': 'Owner', + 'members.leaveTrip': 'Leave trip', + 'members.removeAccess': 'Remove access', + 'members.confirmLeave': 'Leave trip? You will lose access.', + 'members.confirmRemove': 'Remove access for this user?', + 'members.loadError': 'Failed to load members', + 'members.added': 'added', + 'members.addError': 'Failed to add', + 'members.removed': 'Member removed', + 'members.removeError': 'Failed to remove', +}; +export default members; diff --git a/shared/src/i18n/tr/memories.ts b/shared/src/i18n/tr/memories.ts new file mode 100644 index 00000000..b0a349cc --- /dev/null +++ b/shared/src/i18n/tr/memories.ts @@ -0,0 +1,80 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Photos', + 'memories.notConnected': '{provider_name} not connected', + 'memories.notConnectedHint': + 'Connect your {provider_name} instance in Settings to be able add photos to this trip.', + 'memories.notConnectedMultipleHint': + 'Connect any of these photo providers: {provider_names} in Settings to be able add photos to this trip.', + 'memories.noDates': 'Add dates to your trip to load photos.', + 'memories.noPhotos': 'No photos found', + 'memories.noPhotosHint': + "No photos found in {provider_name} for this trip's date range.", + 'memories.photosFound': 'photos', + 'memories.fromOthers': 'from others', + 'memories.sharePhotos': 'Share photos', + 'memories.sharing': 'Sharing', + 'memories.reviewTitle': 'Review your photos', + 'memories.reviewHint': 'Click photos to exclude them from sharing.', + 'memories.shareCount': 'Share {count} photos', + 'memories.providerUrl': 'Server URL', + 'memories.providerApiKey': 'API Key', + 'memories.providerUsername': 'Username', + 'memories.providerPassword': 'Password', + 'memories.providerOTP': 'MFA code (if enabled)', + 'memories.skipSSLVerification': 'Skip SSL certificate verification', + 'memories.immichAutoUpload': 'Mirror journey photos to Immich on upload', + 'memories.providerUrlHintSynology': + 'Include the Photos app path in the URL, e.g. https://nas:5001/photo', + 'memories.testConnection': 'Test connection', + 'memories.testShort': 'Test', + 'memories.testFirst': 'Test connection first', + 'memories.connected': 'Connected', + 'memories.disconnected': 'Not connected', + 'memories.connectionSuccess': 'Connected to {provider_name}', + 'memories.connectionError': 'Could not connect to {provider_name}', + 'memories.saved': '{provider_name} settings saved', + 'memories.providerDisconnectedBanner': + 'Your {provider_name} connection is lost. Reconnect in Settings to view photos.', + 'memories.saveError': 'Could not save {provider_name} settings', + 'memories.addPhotos': 'Add photos', + 'memories.linkAlbum': 'Link Album', + 'memories.selectAlbum': 'Select {provider_name} Album', + 'memories.selectAlbumMultiple': 'Select Album', + 'memories.noAlbums': 'No albums found', + 'memories.syncAlbum': 'Sync album', + 'memories.unlinkAlbum': 'Unlink album', + 'memories.photos': 'photos', + 'memories.selectPhotos': 'Select photos from {provider_name}', + 'memories.selectPhotosMultiple': 'Select Photos', + 'memories.selectHint': 'Tap photos to select them.', + 'memories.selected': 'selected', + 'memories.addSelected': 'Add {count} photos', + 'memories.alreadyAdded': 'Added', + 'memories.private': 'Private', + 'memories.stopSharing': 'Stop sharing', + 'memories.oldest': 'Oldest first', + 'memories.newest': 'Newest first', + 'memories.allLocations': 'All locations', + 'memories.tripDates': 'Trip dates', + 'memories.allPhotos': 'All photos', + 'memories.confirmShareTitle': 'Share with trip members?', + 'memories.confirmShareHint': + '{count} photos will be visible to all members of this trip. You can make individual photos private later.', + 'memories.confirmShareButton': 'Share photos', + 'memories.error.loadAlbums': 'Failed to load albums', + 'memories.error.linkAlbum': 'Failed to link album', + 'memories.error.unlinkAlbum': 'Failed to unlink album', + 'memories.error.syncAlbum': 'Failed to sync album', + 'memories.error.loadPhotos': 'Failed to load photos', + 'memories.error.addPhotos': 'Failed to add photos', + 'memories.error.removePhoto': 'Failed to remove photo', + 'memories.error.toggleSharing': 'Failed to update sharing', + 'memories.saveRouteNotConfigured': + 'Save route is not configured for this provider', + 'memories.testRouteNotConfigured': + 'Test route is not configured for this provider', + 'memories.fillRequiredFields': 'Please fill all required fields', +}; +export default memories; diff --git a/shared/src/i18n/tr/nav.ts b/shared/src/i18n/tr/nav.ts new file mode 100644 index 00000000..cb0432bf --- /dev/null +++ b/shared/src/i18n/tr/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Gezi', + 'nav.share': 'Paylaş', + 'nav.settings': 'Ayarlar', + 'nav.admin': 'Yönetim', + 'nav.logout': 'Çıkış yap', + 'nav.lightMode': 'Açık tema', + 'nav.darkMode': 'Koyu tema', + 'nav.autoMode': 'Otomatik tema', + 'nav.administrator': 'Yönetici', + 'nav.myTrips': 'Gezilerim', + 'nav.profile': 'Profil', + 'nav.bottomSettings': 'Ayarlar', + 'nav.bottomAdmin': 'Yönetim ayarları', + 'nav.bottomLogout': 'Çıkış', + 'nav.bottomAdminBadge': 'Yönetici', +}; +export default nav; diff --git a/shared/src/i18n/tr/notif.ts b/shared/src/i18n/tr/notif.ts new file mode 100644 index 00000000..43e55a4c --- /dev/null +++ b/shared/src/i18n/tr/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Test] Notification', + 'notif.test.simple.text': 'This is a simple test notification.', + 'notif.test.boolean.text': 'Do you accept this test notification?', + 'notif.test.navigate.text': 'Click below to navigate to the dashboard.', + 'notif.trip_invite.title': 'Trip Invitation', + 'notif.trip_invite.text': '{actor} invited you to {trip}', + 'notif.booking_change.title': 'Booking Updated', + 'notif.booking_change.text': '{actor} updated a booking in {trip}', + 'notif.trip_reminder.title': 'Trip Reminder', + 'notif.trip_reminder.text': 'Your trip {trip} is coming up soon!', + 'notif.todo_due.title': 'To-do due', + 'notif.todo_due.text': '{todo} in {trip} is due on {due}', + 'notif.vacay_invite.title': 'Vacay Fusion Invite', + 'notif.vacay_invite.text': '{actor} invited you to fuse vacation plans', + 'notif.photos_shared.title': 'Photos Shared', + 'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}', + 'notif.collab_message.title': 'New Message', + 'notif.collab_message.text': '{actor} sent a message in {trip}', + 'notif.packing_tagged.title': 'Packing Assignment', + 'notif.packing_tagged.text': '{actor} assigned you to {category} in {trip}', + 'notif.version_available.title': 'New Version Available', + 'notif.version_available.text': 'TREK {version} is now available', + 'notif.action.view_trip': 'View Trip', + 'notif.action.view_collab': 'View Messages', + 'notif.action.view_packing': 'View Packing', + 'notif.action.view_photos': 'View Photos', + 'notif.action.view_vacay': 'View Vacay', + 'notif.action.view_admin': 'Go to Admin', + 'notif.action.view': 'View', + 'notif.action.accept': 'Accept', + 'notif.action.decline': 'Decline', + 'notif.generic.title': 'Notification', + 'notif.generic.text': 'You have a new notification', + 'notif.dev.unknown_event.title': '[DEV] Unknown Event', + 'notif.dev.unknown_event.text': + 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/tr/notifications.ts b/shared/src/i18n/tr/notifications.ts new file mode 100644 index 00000000..1b5df5ae --- /dev/null +++ b/shared/src/i18n/tr/notifications.ts @@ -0,0 +1,38 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Notifications', + 'notifications.markAllRead': 'Mark all read', + 'notifications.deleteAll': 'Delete all', + 'notifications.showAll': 'Show all notifications', + 'notifications.empty': 'No notifications', + 'notifications.emptyDescription': "You're all caught up!", + 'notifications.all': 'All', + 'notifications.unreadOnly': 'Unread', + 'notifications.markRead': 'Mark as read', + 'notifications.markUnread': 'Mark as unread', + 'notifications.delete': 'Delete', + 'notifications.system': 'System', + 'notifications.synologySessionCleared.title': 'Synology Photos disconnected', + 'notifications.synologySessionCleared.text': + 'Your server or account changed — go to Settings to test your connection again.', + 'notifications.versionAvailable.title': 'Update Available', + 'notifications.versionAvailable.text': 'TREK {version} is now available.', + 'notifications.versionAvailable.button': 'View Details', + 'notifications.test.title': 'Test notification from {actor}', + 'notifications.test.text': 'This is a simple test notification.', + 'notifications.test.booleanTitle': '{actor} asks for your approval', + 'notifications.test.booleanText': + 'This is a test boolean notification. Choose an action below.', + 'notifications.test.accept': 'Approve', + 'notifications.test.decline': 'Decline', + 'notifications.test.navigateTitle': 'Check something out', + 'notifications.test.navigateText': 'This is a test navigate notification.', + 'notifications.test.goThere': 'Go there', + 'notifications.test.adminTitle': 'Admin broadcast', + 'notifications.test.adminText': + '{actor} sent a test notification to all admins.', + 'notifications.test.tripTitle': '{actor} posted in your trip', + 'notifications.test.tripText': 'Test notification for trip "{trip}".', +}; +export default notifications; diff --git a/shared/src/i18n/tr/oauth.ts b/shared/src/i18n/tr/oauth.ts new file mode 100644 index 00000000..ef4f8f5c --- /dev/null +++ b/shared/src/i18n/tr/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Trips', + 'oauth.scope.group.places': 'Places', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Packing', + 'oauth.scope.group.todos': 'To-dos', + 'oauth.scope.group.budget': 'Budget', + 'oauth.scope.group.reservations': 'Reservations', + 'oauth.scope.group.collab': 'Collaboration', + 'oauth.scope.group.notifications': 'Notifications', + 'oauth.scope.group.vacay': 'Vacation', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Weather', + 'oauth.scope.group.journey': 'Journey', + 'oauth.scope.trips:read.label': 'View trips & itineraries', + 'oauth.scope.trips:read.description': + 'Read trips, days, day notes, and members', + 'oauth.scope.trips:write.label': 'Edit trips & itineraries', + 'oauth.scope.trips:write.description': + 'Create and update trips, days, notes, and manage members', + 'oauth.scope.trips:delete.label': 'Delete trips', + 'oauth.scope.trips:delete.description': + 'Permanently delete entire trips — this action is irreversible', + 'oauth.scope.trips:share.label': 'Manage share links', + 'oauth.scope.trips:share.description': + 'Create, update, and revoke public share links for trips', + 'oauth.scope.places:read.label': 'View places & map data', + 'oauth.scope.places:read.description': + 'Read places, day assignments, tags, and categories', + 'oauth.scope.places:write.label': 'Manage places', + 'oauth.scope.places:write.description': + 'Create, update, and delete places, assignments, and tags', + 'oauth.scope.atlas:read.label': 'View Atlas', + 'oauth.scope.atlas:read.description': + 'Read visited countries, regions, and bucket list', + 'oauth.scope.atlas:write.label': 'Manage Atlas', + 'oauth.scope.atlas:write.description': + 'Mark countries and regions visited, manage bucket list', + 'oauth.scope.packing:read.label': 'View packing lists', + 'oauth.scope.packing:read.description': + 'Read packing items, bags, and category assignees', + 'oauth.scope.packing:write.label': 'Manage packing lists', + 'oauth.scope.packing:write.description': + 'Add, update, delete, toggle, and reorder packing items and bags', + 'oauth.scope.todos:read.label': 'View to-do lists', + 'oauth.scope.todos:read.description': + 'Read trip to-do items and category assignees', + 'oauth.scope.todos:write.label': 'Manage to-do lists', + 'oauth.scope.todos:write.description': + 'Create, update, toggle, delete, and reorder to-do items', + 'oauth.scope.budget:read.label': 'View budget', + 'oauth.scope.budget:read.description': + 'Read budget items and expense breakdown', + 'oauth.scope.budget:write.label': 'Manage budget', + 'oauth.scope.budget:write.description': + 'Create, update, and delete budget items', + 'oauth.scope.reservations:read.label': 'View reservations', + 'oauth.scope.reservations:read.description': + 'Read reservations and accommodation details', + 'oauth.scope.reservations:write.label': 'Manage reservations', + 'oauth.scope.reservations:write.description': + 'Create, update, delete, and reorder reservations', + 'oauth.scope.collab:read.label': 'View collaboration', + 'oauth.scope.collab:read.description': + 'Read collab notes, polls, and messages', + 'oauth.scope.collab:write.label': 'Manage collaboration', + 'oauth.scope.collab:write.description': + 'Create, update, and delete collab notes, polls, and messages', + 'oauth.scope.notifications:read.label': 'View notifications', + 'oauth.scope.notifications:read.description': + 'Read in-app notifications and unread counts', + 'oauth.scope.notifications:write.label': 'Manage notifications', + 'oauth.scope.notifications:write.description': + 'Mark notifications as read and respond to them', + 'oauth.scope.vacay:read.label': 'View vacation plans', + 'oauth.scope.vacay:read.description': + 'Read vacation planning data, entries, and stats', + 'oauth.scope.vacay:write.label': 'Manage vacation plans', + 'oauth.scope.vacay:write.description': + 'Create and manage vacation entries, holidays, and team plans', + 'oauth.scope.geo:read.label': 'Maps & geocoding', + 'oauth.scope.geo:read.description': + 'Search locations, resolve map URLs, and reverse geocode coordinates', + 'oauth.scope.weather:read.label': 'Weather forecasts', + 'oauth.scope.weather:read.description': + 'Fetch weather forecasts for trip locations and dates', + 'oauth.scope.journey:read.label': 'View journeys', + 'oauth.scope.journey:read.description': + 'Read journeys, entries, and contributor list', + 'oauth.scope.journey:write.label': 'Manage journeys', + 'oauth.scope.journey:write.description': + 'Create, update, and delete journeys and their entries', + 'oauth.scope.journey:share.label': 'Manage journey links', + 'oauth.scope.journey:share.description': + 'Create, update, and revoke public share links for journeys', +}; +export default oauth; diff --git a/shared/src/i18n/tr/packing.ts b/shared/src/i18n/tr/packing.ts new file mode 100644 index 00000000..b57c355d --- /dev/null +++ b/shared/src/i18n/tr/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Packing List', + 'packing.empty': 'Packing list is empty', + 'packing.import': 'Import', + 'packing.importTitle': 'Import Packing List', + 'packing.importHint': + 'One item per line. Format: Category, Name, Weight in g (optional), Bag (optional), checked/unchecked (optional)', + 'packing.importPlaceholder': + 'Hygiene, Toothbrush\nClothing, T-Shirts, 200\nDocuments, Passport, , Carry-on\nElectronics, Charger, 50, Suitcase, checked', + 'packing.importCsv': 'Load CSV/TXT', + 'packing.importAction': 'Import {count}', + 'packing.importSuccess': '{count} items imported', + 'packing.importError': 'Import failed', + 'packing.importEmpty': 'No items to import', + 'packing.progress': '{packed} of {total} packed ({percent}%)', + 'packing.clearChecked': 'Remove {count} checked', + 'packing.clearCheckedShort': 'Remove {count}', + 'packing.suggestions': 'Suggestions', + 'packing.suggestionsTitle': 'Add Suggestions', + 'packing.allSuggested': 'All suggestions added', + 'packing.allPacked': 'All packed!', + 'packing.addPlaceholder': 'Add new item...', + 'packing.categoryPlaceholder': 'Category...', + 'packing.filterAll': 'All', + 'packing.filterOpen': 'Open', + 'packing.filterDone': 'Done', + 'packing.emptyTitle': 'Packing list is empty', + 'packing.emptyHint': 'Add items or use the suggestions', + 'packing.emptyFiltered': 'No items match this filter', + 'packing.menuRename': 'Rename', + 'packing.menuCheckAll': 'Check All', + 'packing.menuUncheckAll': 'Uncheck All', + 'packing.menuDeleteCat': 'Delete Category', + 'packing.noMembers': 'No trip members', + 'packing.addItem': 'Add item', + 'packing.addItemPlaceholder': 'Item name...', + 'packing.addCategory': 'Add category', + 'packing.newCategoryPlaceholder': 'Category name (e.g. Clothing)', + 'packing.applyTemplate': 'Apply template', + 'packing.template': 'Template', + 'packing.templateApplied': '{count} items added from template', + 'packing.templateError': 'Failed to apply template', + 'packing.saveAsTemplate': 'Save as template', + 'packing.templateName': 'Template name', + 'packing.templateSaved': 'Packing list saved as template', + 'packing.bags': 'Bags', + 'packing.noBag': 'Unassigned', + 'packing.totalWeight': 'Total weight', + 'packing.bagName': 'Bag name...', + 'packing.addBag': 'Add bag', + 'packing.changeCategory': 'Change Category', + 'packing.confirm.clearChecked': + 'Are you sure you want to remove {count} checked items?', + 'packing.confirm.deleteCat': + 'Are you sure you want to delete the category "{name}" with {count} items?', + 'packing.defaultCategory': 'Other', + 'packing.toast.saveError': 'Failed to save', + 'packing.toast.deleteError': 'Failed to delete', + 'packing.toast.renameError': 'Failed to rename', + 'packing.toast.addError': 'Failed to add', + 'packing.suggestions.items': [ + { + name: 'Passport', + category: 'Documents', + }, + { + name: 'ID Card', + category: 'Documents', + }, + { + name: 'Travel Insurance', + category: 'Documents', + }, + { + name: 'Flight Tickets', + category: 'Documents', + }, + { + name: 'Credit Card', + category: 'Finances', + }, + { + name: 'Cash', + category: 'Finances', + }, + { + name: 'Visa', + category: 'Documents', + }, + { + name: 'T-Shirts', + category: 'Clothing', + }, + { + name: 'Pants', + category: 'Clothing', + }, + { + name: 'Underwear', + category: 'Clothing', + }, + { + name: 'Socks', + category: 'Clothing', + }, + { + name: 'Jacket', + category: 'Clothing', + }, + { + name: 'Sleepwear', + category: 'Clothing', + }, + { + name: 'Swimwear', + category: 'Clothing', + }, + { + name: 'Rain Jacket', + category: 'Clothing', + }, + { + name: 'Comfortable Shoes', + category: 'Clothing', + }, + { + name: 'Toothbrush', + category: 'Toiletries', + }, + { + name: 'Toothpaste', + category: 'Toiletries', + }, + { + name: 'Shampoo', + category: 'Toiletries', + }, + { + name: 'Deodorant', + category: 'Toiletries', + }, + { + name: 'Sunscreen', + category: 'Toiletries', + }, + { + name: 'Razor', + category: 'Toiletries', + }, + { + name: 'Charger', + category: 'Electronics', + }, + { + name: 'Power Bank', + category: 'Electronics', + }, + { + name: 'Headphones', + category: 'Electronics', + }, + { + name: 'Travel Adapter', + category: 'Electronics', + }, + { + name: 'Camera', + category: 'Electronics', + }, + { + name: 'Pain Medication', + category: 'Health', + }, + { + name: 'Band-Aids', + category: 'Health', + }, + { + name: 'Disinfectant', + category: 'Health', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/tr/pdf.ts b/shared/src/i18n/tr/pdf.ts new file mode 100644 index 00000000..3ee899f6 --- /dev/null +++ b/shared/src/i18n/tr/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'Travel Plan', + 'pdf.planned': 'Planned', + 'pdf.costLabel': 'Cost EUR', + 'pdf.preview': 'PDF Preview', + 'pdf.saveAsPdf': 'Save as PDF', +}; +export default pdf; diff --git a/shared/src/i18n/tr/perm.ts b/shared/src/i18n/tr/perm.ts new file mode 100644 index 00000000..a33dbd16 --- /dev/null +++ b/shared/src/i18n/tr/perm.ts @@ -0,0 +1,57 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Permission Settings', + 'perm.subtitle': 'Control who can perform actions across the application', + 'perm.saved': 'Permission settings saved', + 'perm.resetDefaults': 'Reset to defaults', + 'perm.customized': 'customized', + 'perm.level.admin': 'Admin only', + 'perm.level.tripOwner': 'Trip owner', + 'perm.level.tripMember': 'Trip members', + 'perm.level.everybody': 'Everyone', + 'perm.cat.trip': 'Trip Management', + 'perm.cat.members': 'Member Management', + 'perm.cat.files': 'Files', + 'perm.cat.content': 'Content & Schedule', + 'perm.cat.extras': 'Budget, Packing & Collaboration', + 'perm.action.trip_create': 'Create trips', + 'perm.action.trip_edit': 'Edit trip details', + 'perm.action.trip_delete': 'Delete trips', + 'perm.action.trip_archive': 'Archive / unarchive trips', + 'perm.action.trip_cover_upload': 'Upload cover image', + 'perm.action.member_manage': 'Add / remove members', + 'perm.action.file_upload': 'Upload files', + 'perm.action.file_edit': 'Edit file metadata', + 'perm.action.file_delete': 'Delete files', + 'perm.action.place_edit': 'Add / edit / delete places', + 'perm.action.day_edit': 'Edit days, notes & assignments', + 'perm.action.reservation_edit': 'Manage reservations', + 'perm.action.budget_edit': 'Manage budget', + 'perm.action.packing_edit': 'Manage packing lists', + 'perm.action.collab_edit': 'Collaboration (notes, polls, chat)', + 'perm.action.share_manage': 'Manage share links', + 'perm.actionHint.trip_create': 'Who can create new trips', + 'perm.actionHint.trip_edit': + 'Who can change trip name, dates, description and currency', + 'perm.actionHint.trip_delete': 'Who can permanently delete a trip', + 'perm.actionHint.trip_archive': 'Who can archive or unarchive a trip', + 'perm.actionHint.trip_cover_upload': + 'Who can upload or change the cover image', + 'perm.actionHint.member_manage': 'Who can invite or remove trip members', + 'perm.actionHint.file_upload': 'Who can upload files to a trip', + 'perm.actionHint.file_edit': 'Who can edit file descriptions and links', + 'perm.actionHint.file_delete': + 'Who can move files to trash or permanently delete them', + 'perm.actionHint.place_edit': 'Who can add, edit or delete places', + 'perm.actionHint.day_edit': + 'Who can edit days, day notes and place assignments', + 'perm.actionHint.reservation_edit': + 'Who can create, edit or delete reservations', + 'perm.actionHint.budget_edit': 'Who can create, edit or delete budget items', + 'perm.actionHint.packing_edit': 'Who can manage packing items and bags', + 'perm.actionHint.collab_edit': + 'Who can create notes, polls and send messages', + 'perm.actionHint.share_manage': 'Who can create or delete public share links', +}; +export default perm; diff --git a/shared/src/i18n/tr/photos.ts b/shared/src/i18n/tr/photos.ts new file mode 100644 index 00000000..622b6c71 --- /dev/null +++ b/shared/src/i18n/tr/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Photos', + 'photos.subtitle': '{count} photos for {trip}', + 'photos.dropHere': 'Drop photos here...', + 'photos.dropHereActive': 'Drop photos here', + 'photos.captionForAll': 'Caption (for all)', + 'photos.captionPlaceholder': 'Optional caption...', + 'photos.addCaption': 'Add caption...', + 'photos.allDays': 'All Days', + 'photos.noPhotos': 'No photos yet', + 'photos.uploadHint': 'Upload your travel photos', + 'photos.clickToSelect': 'or click to select', + 'photos.linkPlace': 'Link Place', + 'photos.noPlace': 'No Place', + 'photos.uploadN': '{n} photo(s) upload', + 'photos.linkDay': 'Link Day', + 'photos.noDay': 'No Day', + 'photos.dayLabel': 'Day {number}', + 'photos.photoSelected': 'Photo selected', + 'photos.photosSelected': 'Photos selected', + 'photos.fileTypeHint': 'JPG, PNG, WebP · max. 10 MB · up to 30 photos', +}; +export default photos; diff --git a/shared/src/i18n/tr/places.ts b/shared/src/i18n/tr/places.ts new file mode 100644 index 00000000..9454fdb1 --- /dev/null +++ b/shared/src/i18n/tr/places.ts @@ -0,0 +1,91 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Add Place/Activity', + 'places.importFile': 'Import file', + 'places.sidebarDrop': 'Drop to import', + 'places.importFileHint': + 'Import .gpx, .kml or .kmz files from tools like Google My Maps, Google Earth, or a GPS tracker.', + 'places.importFileDropHere': 'Click to select a file or drag and drop here', + 'places.importFileDropActive': 'Drop file to select', + 'places.importFileUnsupported': + 'Unsupported file type. Use .gpx, .kml or .kmz.', + 'places.importFileTooLarge': + 'File is too large. Maximum upload size is {maxMb} MB.', + 'places.importFileError': 'Import failed', + 'places.importAllSkipped': 'All places were already in the trip.', + 'places.gpxImported': '{count} places imported from GPX', + 'places.gpxImportTypes': 'What do you want to import?', + 'places.gpxImportWaypoints': 'Waypoints', + 'places.gpxImportRoutes': 'Routes', + 'places.gpxImportTracks': 'Tracks (with path geometry)', + 'places.gpxImportNoneSelected': 'Select at least one type to import.', + 'places.kmlImportTypes': 'What do you want to import?', + 'places.kmlImportPoints': 'Points (Placemarks)', + 'places.kmlImportPaths': 'Paths (LineStrings)', + 'places.kmlImportNoneSelected': 'Select at least one type to import.', + 'places.selectionCount': '{count} selected', + 'places.deleteSelected': 'Delete selected', + 'places.kmlKmzImported': '{count} places imported from KMZ/KML', + 'places.urlResolved': 'Place imported from URL', + 'places.importList': 'List Import', + 'places.kmlKmzSummaryValues': + 'Placemarks: {total} • Imported: {created} • Skipped: {skipped}', + 'places.importGoogleList': 'Google List', + 'places.importNaverList': 'Naver List', + 'places.googleListHint': + 'Paste a shared Google Maps list link to import all places.', + 'places.googleListImported': '{count} places imported from "{list}"', + 'places.googleListError': 'Failed to import Google Maps list', + 'places.naverListHint': + 'Paste a shared Naver Maps list link to import all places.', + 'places.naverListImported': '{count} places imported from "{list}"', + 'places.naverListError': 'Failed to import Naver Maps list', + 'places.viewDetails': 'View Details', + 'places.assignToDay': 'Add to which day?', + 'places.all': 'All', + 'places.unplanned': 'Unplanned', + 'places.filterTracks': 'Tracks', + 'places.search': 'Search places...', + 'places.allCategories': 'All Categories', + 'places.categoriesSelected': 'categories', + 'places.clearFilter': 'Clear filter', + 'places.count': '{count} places', + 'places.countSingular': '1 place', + 'places.allPlanned': 'All places are planned', + 'places.noneFound': 'No places found', + 'places.editPlace': 'Edit Place', + 'places.formName': 'Name', + 'places.formNamePlaceholder': 'e.g. Eiffel Tower', + 'places.formDescription': 'Description', + 'places.formDescriptionPlaceholder': 'Short description...', + 'places.formAddress': 'Address', + 'places.formAddressPlaceholder': 'Street, City, Country', + 'places.formLat': 'Latitude (e.g. 48.8566)', + 'places.formLng': 'Longitude (e.g. 2.3522)', + 'places.formCategory': 'Category', + 'places.noCategory': 'No Category', + 'places.categoryNamePlaceholder': 'Category name', + 'places.formTime': 'Time', + 'places.startTime': 'Start', + 'places.endTime': 'End', + 'places.endTimeBeforeStart': 'End time is before start time', + 'places.timeCollision': 'Time overlap with:', + 'places.formWebsite': 'Website', + 'places.formNotes': 'Notes', + 'places.formNotesPlaceholder': 'Personal notes...', + 'places.formReservation': 'Reservation', + 'places.reservationNotesPlaceholder': + 'Reservation notes, confirmation number...', + 'places.mapsSearchPlaceholder': 'Search places...', + 'places.mapsSearchError': 'Place search failed.', + 'places.loadingDetails': 'Loading place details…', + 'places.osmHint': + 'Using OpenStreetMap search (no photos, opening hours, or ratings). Add a Google API key in settings for full details.', + 'places.osmActive': + 'Search via OpenStreetMap (no photos, ratings or opening hours). Add a Google API key in Settings for enhanced data.', + 'places.categoryCreateError': 'Failed to create category', + 'places.nameRequired': 'Please enter a name', + 'places.saveError': 'Failed to save', +}; +export default places; diff --git a/shared/src/i18n/tr/planner.ts b/shared/src/i18n/tr/planner.ts new file mode 100644 index 00000000..259ff606 --- /dev/null +++ b/shared/src/i18n/tr/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Places', + 'planner.bookings': 'Bookings', + 'planner.packingList': 'Packing List', + 'planner.documents': 'Documents', + 'planner.dayPlan': 'Day Plan', + 'planner.reservations': 'Reservations', + 'planner.minTwoPlaces': 'At least 2 places with coordinates needed', + 'planner.noGeoPlaces': 'No places with coordinates available', + 'planner.routeCalculated': 'Route calculated', + 'planner.routeCalcFailed': 'Route could not be calculated', + 'planner.routeError': 'Error calculating route', + 'planner.icsExportFailed': 'ICS export failed', + 'planner.routeOptimized': 'Route optimized', + 'planner.reservationUpdated': 'Reservation updated', + 'planner.reservationAdded': 'Reservation added', + 'planner.confirmDeleteReservation': 'Delete reservation?', + 'planner.reservationDeleted': 'Reservation deleted', + 'planner.days': 'Days', + 'planner.allPlaces': 'All Places', + 'planner.totalPlaces': '{n} places total', + 'planner.noDaysPlanned': 'No days planned yet', + 'planner.editTrip': 'Edit trip →', + 'planner.placeOne': '1 place', + 'planner.placeN': '{n} places', + 'planner.addNote': 'Add note', + 'planner.noEntries': 'No entries for this day', + 'planner.addPlace': 'Add place/activity', + 'planner.addPlaceShort': '+ Add place/activity', + 'planner.resPending': 'Reservation pending · ', + 'planner.resConfirmed': 'Reservation confirmed · ', + 'planner.notePlaceholder': 'Note…', + 'planner.noteTimePlaceholder': 'Time (optional)', + 'planner.noteExamplePlaceholder': + 'e.g. S3 at 14:30 from central station, ferry from pier 7, lunch break…', + 'planner.totalCost': 'Total cost', + 'planner.searchPlaces': 'Search places…', + 'planner.allCategories': 'All Categories', + 'planner.noPlacesFound': 'No places found', + 'planner.addFirstPlace': 'Add first place', + 'planner.noReservations': 'No reservations', + 'planner.addFirstReservation': 'Add first reservation', + 'planner.new': 'New', + 'planner.addToDay': '+ Day', + 'planner.calculating': 'Calculating…', + 'planner.route': 'Route', + 'planner.optimize': 'Optimize', + 'planner.openGoogleMaps': 'Open in Google Maps', + 'planner.selectDayHint': + 'Select a day from the left list to see the day plan', + 'planner.noPlacesForDay': 'No places for this day yet', + 'planner.addPlacesLink': 'Add places →', + 'planner.minTotal': 'min. total', + 'planner.noReservation': 'No reservation', + 'planner.removeFromDay': 'Remove from day', + 'planner.addToThisDay': 'Add to day', + 'planner.overview': 'Overview', + 'planner.noDays': 'No days yet', + 'planner.editTripToAddDays': 'Edit trip to add days', + 'planner.dayCount': '{n} Days', + 'planner.clickToUnlock': 'Click to unlock', + 'planner.keepPosition': 'Keep position during route optimization', + 'planner.dayDetails': 'Day details', + 'planner.dayN': 'Day {n}', +}; +export default planner; diff --git a/shared/src/i18n/tr/register.ts b/shared/src/i18n/tr/register.ts new file mode 100644 index 00000000..e660875b --- /dev/null +++ b/shared/src/i18n/tr/register.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Passwords do not match', + 'register.passwordTooShort': 'Password must be at least 8 characters', + 'register.failed': 'Registration failed', + 'register.getStarted': 'Başlayın', + 'register.subtitle': + 'Hesap oluşturun ve hayalinizdeki gezileri planlamaya başlayın.', + 'register.feature1': 'Unlimited trip plans', + 'register.feature2': 'Interactive map view', + 'register.feature3': 'Manage places and categories', + 'register.feature4': 'Track reservations', + 'register.feature5': 'Create packing lists', + 'register.feature6': 'Store photos and files', + 'register.createAccount': 'Hesap oluştur', + 'register.startPlanning': 'Start your trip planning', + 'register.minChars': 'Min. 6 characters', + 'register.confirmPassword': 'Confirm Password', + 'register.repeatPassword': 'Repeat password', + 'register.registering': 'Registering...', + 'register.register': 'Kayıt ol', + 'register.hasAccount': 'Zaten hesabınız var mı?', + 'register.signIn': 'Giriş yap', +}; +export default register; diff --git a/shared/src/i18n/tr/reservations.ts b/shared/src/i18n/tr/reservations.ts new file mode 100644 index 00000000..be51e44e --- /dev/null +++ b/shared/src/i18n/tr/reservations.ts @@ -0,0 +1,118 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Bookings', + 'reservations.empty': 'No reservations yet', + 'reservations.emptyHint': 'Add reservations for flights, hotels and more', + 'reservations.add': 'Add Reservation', + 'reservations.addManual': 'Manual Booking', + 'reservations.placeHint': + 'Tip: Reservations are best created directly from a place to link them with your day plan.', + 'reservations.confirmed': 'Confirmed', + 'reservations.pending': 'Pending', + 'reservations.summary': '{confirmed} confirmed, {pending} pending', + 'reservations.fromPlan': 'From Plan', + 'reservations.showFiles': 'Show Files', + 'reservations.editTitle': 'Edit Reservation', + 'reservations.status': 'Status', + 'reservations.datetime': 'Date & Time', + 'reservations.startTime': 'Start time', + 'reservations.endTime': 'End time', + 'reservations.date': 'Date', + 'reservations.time': 'Time', + 'reservations.timeAlt': 'Time (alternative, e.g. 19:30)', + 'reservations.notes': 'Notes', + 'reservations.notesPlaceholder': 'Additional notes...', + 'reservations.meta.airline': 'Airline', + 'reservations.meta.flightNumber': 'Flight No.', + 'reservations.meta.from': 'From', + 'reservations.meta.to': 'To', + 'reservations.needsReview': 'Review', + 'reservations.needsReviewHint': + 'Airport could not be matched automatically — please confirm the location.', + 'reservations.searchLocation': 'Search station, port, address…', + 'reservations.meta.trainNumber': 'Train No.', + 'reservations.meta.platform': 'Platform', + 'reservations.meta.seat': 'Seat', + 'reservations.meta.checkIn': 'Check-in', + 'reservations.meta.checkInUntil': 'Check-in until', + 'reservations.meta.checkOut': 'Check-out', + 'reservations.meta.linkAccommodation': 'Accommodation', + 'reservations.meta.pickAccommodation': 'Link to accommodation', + 'reservations.meta.noAccommodation': 'None', + 'reservations.meta.hotelPlace': 'Accommodation', + 'reservations.meta.pickHotel': 'Select accommodation', + 'reservations.meta.fromDay': 'From', + 'reservations.meta.toDay': 'To', + 'reservations.meta.selectDay': 'Select day', + 'reservations.type.flight': 'Flight', + 'reservations.type.hotel': 'Accommodation', + 'reservations.type.restaurant': 'Restaurant', + 'reservations.type.train': 'Train', + 'reservations.type.car': 'Car', + 'reservations.type.cruise': 'Cruise', + 'reservations.type.event': 'Event', + 'reservations.type.tour': 'Tour', + 'reservations.type.other': 'Other', + 'reservations.confirm.delete': + 'Are you sure you want to delete the reservation "{name}"?', + 'reservations.confirm.deleteTitle': 'Delete booking?', + 'reservations.confirm.deleteBody': '"{name}" will be permanently deleted.', + 'reservations.toast.updated': 'Reservation updated', + 'reservations.toast.removed': 'Reservation deleted', + 'reservations.toast.fileUploaded': 'File uploaded', + 'reservations.toast.uploadError': 'Failed to upload', + 'reservations.newTitle': 'New Reservation', + 'reservations.bookingType': 'Booking Type', + 'reservations.titleLabel': 'Title', + 'reservations.titlePlaceholder': 'e.g. Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Location / Address', + 'reservations.locationPlaceholder': 'Address, Airport, Hotel...', + 'reservations.confirmationCode': 'Booking Code', + 'reservations.confirmationPlaceholder': 'e.g. ABC12345', + 'reservations.day': 'Day', + 'reservations.noDay': 'No Day', + 'reservations.place': 'Place', + 'reservations.noPlace': 'No Place', + 'reservations.pendingSave': 'will be saved…', + 'reservations.uploading': 'Uploading...', + 'reservations.attachFile': 'Attach file', + 'reservations.linkExisting': 'Link existing file', + 'reservations.toast.saveError': 'Failed to save', + 'reservations.toast.updateError': 'Failed to update', + 'reservations.toast.deleteError': 'Failed to delete', + 'reservations.confirm.remove': 'Remove reservation for "{name}"?', + 'reservations.linkAssignment': 'Link to day assignment', + 'reservations.pickAssignment': 'Select an assignment from your plan...', + 'reservations.noAssignment': 'No link (standalone)', + 'reservations.price': 'Price', + 'reservations.budgetCategory': 'Budget category', + 'reservations.budgetCategoryPlaceholder': 'e.g. Transport, Accommodation', + 'reservations.budgetCategoryAuto': 'Auto (from booking type)', + 'reservations.budgetHint': + 'A budget entry will be created automatically when saving.', + 'reservations.departureDate': 'Departure', + 'reservations.arrivalDate': 'Arrival', + 'reservations.departureTime': 'Dep. time', + 'reservations.arrivalTime': 'Arr. time', + 'reservations.pickupDate': 'Pickup', + 'reservations.returnDate': 'Return', + 'reservations.pickupTime': 'Pickup time', + 'reservations.returnTime': 'Return time', + 'reservations.endDate': 'End date', + 'reservations.meta.departureTimezone': 'Dep. TZ', + 'reservations.meta.arrivalTimezone': 'Arr. TZ', + 'reservations.span.departure': 'Departure', + 'reservations.span.arrival': 'Arrival', + 'reservations.span.inTransit': 'In transit', + 'reservations.span.pickup': 'Pickup', + 'reservations.span.return': 'Return', + 'reservations.span.active': 'Active', + 'reservations.span.start': 'Start', + 'reservations.span.end': 'End', + 'reservations.span.ongoing': 'Ongoing', + 'reservations.validation.endBeforeStart': + 'End date/time must be after start date/time', + 'reservations.addBooking': 'Add booking', +}; +export default reservations; diff --git a/shared/src/i18n/tr/settings.ts b/shared/src/i18n/tr/settings.ts new file mode 100644 index 00000000..5519b388 --- /dev/null +++ b/shared/src/i18n/tr/settings.ts @@ -0,0 +1,293 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Ayarlar', + 'settings.subtitle': 'Kişisel ayarlarınızı yapılandırın', + 'settings.tabs.display': 'Görünüm', + 'settings.tabs.map': 'Harita', + 'settings.tabs.notifications': 'Bildirimler', + 'settings.tabs.integrations': 'Entegrasyonlar', + 'settings.tabs.account': 'Hesap', + 'settings.tabs.offline': 'Çevrimdışı', + 'settings.tabs.about': 'Hakkında', + 'settings.map': 'Map', + 'settings.mapTemplate': 'Map Template', + 'settings.mapTemplatePlaceholder.select': 'Select template...', + 'settings.mapDefaultHint': 'Leave empty for OpenStreetMap (default)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL template for map tiles', + 'settings.mapProvider': 'Map Provider', + 'settings.mapProviderHint': + 'Affects Trip Planner and Journey maps. Atlas always uses Leaflet.', + 'settings.mapLeafletSubtitle': 'Classic 2D, any raster tiles', + 'settings.mapMapboxSubtitle': 'Vector tiles, 3D buildings & terrain', + 'settings.mapExperimental': 'Experimental', + 'settings.mapMapboxToken': 'Mapbox Access Token', + 'settings.mapMapboxTokenHint': 'Public token (pk.*) from', + 'settings.mapMapboxTokenLink': 'mapbox.com → Access tokens', + 'settings.mapStyle': 'Map Style', + 'settings.mapStylePlaceholder': 'Select a Mapbox style', + 'settings.mapStyleHint': 'Preset or your own mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D Buildings & Terrain', + 'settings.map3dHint': + 'Pitch + real 3D building extrusions — works on every style, including satellite.', + 'settings.mapHighQuality': 'High Quality Mode', + 'settings.mapHighQualityHint': + 'Antialiasing + globe projection for sharper edges and a realistic world view.', + 'settings.mapHighQualityWarning': + 'May impact performance on lower-end devices.', + 'settings.mapTipLabel': 'Tip:', + 'settings.mapTip': + 'right-click and drag to rotate/pitch the map. Middle-click to add a place (right-click is reserved for rotation).', + 'settings.latitude': 'Latitude', + 'settings.longitude': 'Longitude', + 'settings.saveMap': 'Save Map', + 'settings.apiKeys': 'API Keys', + 'settings.mapsKey': 'Google Maps API Key', + 'settings.mapsKeyHint': + 'For place search. Requires Places API (New). Get at console.cloud.google.com', + 'settings.weatherKey': 'OpenWeatherMap API Key', + 'settings.weatherKeyHint': 'For weather data. Free at openweathermap.org/api', + 'settings.keyPlaceholder': 'Enter key...', + 'settings.configured': 'Configured', + 'settings.saveKeys': 'Save Keys', + 'settings.display': 'Görünüm', + 'settings.colorMode': 'Tema modu', + 'settings.light': 'Açık', + 'settings.dark': 'Koyu', + 'settings.auto': 'Otomatik', + 'settings.language': 'Dil', + 'settings.temperature': 'Sıcaklık birimi', + 'settings.timeFormat': 'Saat biçimi', + 'settings.bookingLabels': 'Booking route labels', + 'settings.bookingLabelsHint': + 'Show station / airport names on the map. When off, only the icon is shown.', + 'settings.blurBookingCodes': 'Blur Booking Codes', + 'settings.notifications': 'Bildirimler', + 'settings.notifyTripInvite': 'Trip invitations', + 'settings.notifyBookingChange': 'Booking changes', + 'settings.notifyTripReminder': 'Trip reminders', + 'settings.notifyTodoDue': 'Todo due soon', + 'settings.notifyVacayInvite': 'Vacay fusion invitations', + 'settings.notifyPhotosShared': 'Shared photos (Immich)', + 'settings.notifyCollabMessage': 'Chat messages (Collab)', + 'settings.notifyPackingTagged': 'Packing list: assignments', + 'settings.notifyWebhook': 'Webhook notifications', + 'settings.notifyVersionAvailable': 'New version available', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + 'No notification channels are configured. Ask an admin to set up email or webhook notifications.', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Enter your Discord, Slack, or custom webhook URL to receive notifications.', + 'settings.webhookUrl.saved': 'Webhook URL saved', + 'settings.webhookUrl.test': 'Test', + 'settings.webhookUrl.testSuccess': 'Test webhook sent successfully', + 'settings.webhookUrl.testFailed': 'Test webhook failed', + 'settings.ntfyUrl.topicLabel': 'Ntfy Topic', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy Server URL (optional)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Enter your ntfy topic to receive push notifications. Leave server blank to use the default configured by your admin.', + 'settings.ntfyUrl.tokenLabel': 'Access Token (optional)', + 'settings.ntfyUrl.tokenHint': 'Required for password-protected topics.', + 'settings.ntfyUrl.saved': 'Ntfy settings saved', + 'settings.ntfyUrl.test': 'Test', + 'settings.ntfyUrl.testSuccess': 'Test ntfy notification sent successfully', + 'settings.ntfyUrl.testFailed': 'Test ntfy notification failed', + 'settings.ntfyUrl.tokenCleared': 'Access token cleared', + 'settings.notificationsDisabled': + 'Notifications are not configured. Ask an admin to enable email or webhook notifications.', + 'settings.notificationsActive': 'Active channel', + 'settings.notificationsManagedByAdmin': + 'Notification events are configured by your administrator.', + 'settings.on': 'On', + 'settings.off': 'Off', + 'settings.mcp.title': 'MCP Configuration', + 'settings.mcp.endpoint': 'MCP Endpoint', + 'settings.mcp.clientConfig': 'Client Configuration', + 'settings.mcp.clientConfigHint': + 'Replace with an API token from the list below. The path to npx may need to be adjusted for your system (e.g. C:\\PROGRA~1\\nodejs\\npx.cmd on Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Replace and with the credentials shown in the OAuth 2.1 client you created above. mcp-remote will open your browser to complete the authorization the first time you connect. The path to npx may need to be adjusted for your system (e.g. C:PROGRA~1\nodejs\npx.cmd on Windows).', + 'settings.mcp.copy': 'Copy', + 'settings.mcp.copied': 'Copied!', + 'settings.mcp.apiTokens': 'API Tokens', + 'settings.mcp.createToken': 'Create New Token', + 'settings.mcp.noTokens': 'No tokens yet. Create one to connect MCP clients.', + 'settings.mcp.tokenCreatedAt': 'Created', + 'settings.mcp.tokenUsedAt': 'Used', + 'settings.mcp.deleteTokenTitle': 'Delete Token', + 'settings.mcp.deleteTokenMessage': + 'This token will stop working immediately. Any MCP client using it will lose access.', + 'settings.mcp.modal.createTitle': 'Create API Token', + 'settings.mcp.modal.tokenName': 'Token Name', + 'settings.mcp.modal.tokenNamePlaceholder': 'e.g. Claude Desktop, Work laptop', + 'settings.mcp.modal.creating': 'Creating…', + 'settings.mcp.modal.create': 'Create Token', + 'settings.mcp.modal.createdTitle': 'Token Created', + 'settings.mcp.modal.createdWarning': + 'This token will only be shown once. Copy and store it now — it cannot be recovered.', + 'settings.mcp.modal.done': 'Done', + 'settings.mcp.toast.created': 'Token created', + 'settings.mcp.toast.createError': 'Failed to create token', + 'settings.mcp.toast.deleted': 'Token deleted', + 'settings.mcp.toast.deleteError': 'Failed to delete token', + 'settings.mcp.apiTokensDeprecated': + 'API Tokens are deprecated and will be removed in a future release. Please use OAuth 2.1 Clients instead.', + 'settings.oauth.clients': 'OAuth 2.1 Clients', + 'settings.oauth.clientsHint': + 'Register OAuth 2.1 clients to let third-party MCP applications (Claude Web, Cursor, etc.) connect without static tokens.', + 'settings.oauth.createClient': 'New Client', + 'settings.oauth.noClients': 'No OAuth clients registered.', + 'settings.oauth.clientId': 'Client ID', + 'settings.oauth.clientSecret': 'Client Secret', + 'settings.oauth.deleteClient': 'Delete Client', + 'settings.oauth.deleteClientMessage': + 'This client and all active sessions will be permanently removed. Any application using it will lose access immediately.', + 'settings.oauth.rotateSecret': 'Rotate Secret', + 'settings.oauth.rotateSecretMessage': + 'A new client secret will be generated and all existing sessions will be invalidated immediately. Update your application before closing this dialog.', + 'settings.oauth.rotateSecretConfirm': 'Rotate', + 'settings.oauth.rotateSecretConfirming': 'Rotating…', + 'settings.oauth.rotateSecretDoneTitle': 'New Secret Generated', + 'settings.oauth.rotateSecretDoneWarning': + 'This secret is shown only once. Copy it now and update your application — all previous sessions have been invalidated.', + 'settings.oauth.activeSessions': 'Active OAuth Sessions', + 'settings.oauth.sessionScopes': 'Scopes', + 'settings.oauth.sessionExpires': 'Expires', + 'settings.oauth.revoke': 'Revoke', + 'settings.oauth.revokeSession': 'Revoke Session', + 'settings.oauth.revokeSessionMessage': + 'This will immediately revoke access for this OAuth session.', + 'settings.oauth.modal.createTitle': 'Register OAuth Client', + 'settings.oauth.modal.presets': 'Quick presets', + 'settings.oauth.modal.clientName': 'Application Name', + 'settings.oauth.modal.clientNamePlaceholder': 'e.g. Claude Web, My MCP App', + 'settings.oauth.modal.redirectUris': 'Redirect URIs', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'One URI per line. HTTPS required (localhost exempt). Exact match enforced.', + 'settings.oauth.modal.scopes': 'Allowed Scopes', + 'settings.oauth.modal.scopesHint': + 'list_trips and get_trip_summary are always available — no scope required. They let the AI discover trip IDs needed to use any other tool.', + 'settings.oauth.modal.selectAll': 'Select all', + 'settings.oauth.modal.deselectAll': 'Deselect all', + 'settings.oauth.modal.creating': 'Registering…', + 'settings.oauth.modal.create': 'Register Client', + 'settings.oauth.modal.createdTitle': 'Client Registered', + 'settings.oauth.modal.createdWarning': + 'The client secret is shown only once. Copy it now — it cannot be recovered.', + 'settings.oauth.toast.createError': 'Failed to register OAuth client', + 'settings.oauth.toast.deleted': 'OAuth client deleted', + 'settings.oauth.toast.deleteError': 'Failed to delete OAuth client', + 'settings.oauth.toast.revoked': 'Session revoked', + 'settings.oauth.toast.revokeError': 'Failed to revoke session', + 'settings.oauth.toast.rotateError': 'Failed to rotate client secret', + 'settings.account': 'Hesap', + 'settings.about': 'Hakkında', + 'settings.about.reportBug': 'Report a Bug', + 'settings.about.reportBugHint': 'Found an issue? Let us know', + 'settings.about.featureRequest': 'Feature Request', + 'settings.about.featureRequestHint': 'Suggest a new feature', + 'settings.about.wikiHint': 'Documentation & guides', + 'settings.about.supporters.badge': 'Monthly Supporters', + 'settings.about.supporters.title': 'Travel companions for TREK', + 'settings.about.supporters.subtitle': + "While you're planning your next route, these folks are helping plan TREK's future. Their monthly contribution goes straight into development and real hours spent — so TREK stays Open Source.", + 'settings.about.supporters.since': 'supporter since {date}', + 'settings.about.supporters.tierEmpty': 'Be the first', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK is a self-hosted travel planner that helps you organize your trips from the first idea to the last memory. Day planning, budget, packing lists, photos and much more — all in one place, on your own server.', + 'settings.about.madeWith': 'Made with', + 'settings.about.madeBy': 'by Maurice and a growing open-source community.', + 'settings.username': 'Kullanıcı adı', + 'settings.email': 'Email', + 'settings.role': 'Rol', + 'settings.roleAdmin': 'Yönetici', + 'settings.oidcLinked': 'Linked with', + 'settings.changePassword': 'Parolayı değiştir', + 'settings.currentPassword': 'Mevcut parola', + 'settings.currentPasswordRequired': 'Current password is required', + 'settings.newPassword': 'Yeni parola', + 'settings.confirmPassword': 'Yeni parolayı doğrula', + 'settings.updatePassword': 'Parolayı güncelle', + 'settings.passwordRequired': 'Please enter current and new password', + 'settings.passwordTooShort': 'Password must be at least 8 characters', + 'settings.passwordMismatch': 'Passwords do not match', + 'settings.passwordWeak': + 'Password must contain uppercase, lowercase, a number, and a special character', + 'settings.passwordChanged': 'Password changed successfully', + 'settings.mustChangePassword': + 'You must change your password before you can continue. Please set a new password below.', + 'settings.deleteAccount': 'Hesabı sil', + 'settings.deleteAccountTitle': 'Delete your account?', + 'settings.deleteAccountWarning': + 'Your account and all your trips, places, and files will be permanently deleted. This action cannot be undone.', + 'settings.deleteAccountConfirm': 'Delete permanently', + 'settings.deleteBlockedTitle': 'Deletion not possible', + 'settings.deleteBlockedMessage': + 'You are the only administrator. Promote another user to admin before deleting your account.', + 'settings.roleUser': 'Kullanıcı', + 'settings.saveProfile': 'Profili kaydet', + 'settings.toast.mapSaved': 'Map settings saved', + 'settings.toast.keysSaved': 'API keys saved', + 'settings.toast.displaySaved': 'Display settings saved', + 'settings.toast.profileSaved': 'Profile saved', + 'settings.uploadAvatar': 'Upload Profile Picture', + 'settings.removeAvatar': 'Remove Profile Picture', + 'settings.avatarUploaded': 'Profile picture updated', + 'settings.avatarRemoved': 'Profile picture removed', + 'settings.avatarError': 'Upload failed', + 'settings.mfa.title': 'Two-factor authentication (2FA)', + 'settings.mfa.description': + 'Adds a second step when you sign in with email and password. Use an authenticator app (Google Authenticator, Authy, etc.).', + 'settings.mfa.requiredByPolicy': + 'Your administrator requires two-factor authentication. Set up an authenticator app below before continuing.', + 'settings.mfa.backupTitle': 'Backup codes', + 'settings.mfa.backupDescription': + 'Use these one-time backup codes if you lose access to your authenticator app.', + 'settings.mfa.backupWarning': + 'Save these codes now. Each code can only be used once.', + 'settings.mfa.backupCopy': 'Copy codes', + 'settings.mfa.backupDownload': 'Download TXT', + 'settings.mfa.backupPrint': 'Print / PDF', + 'settings.mfa.backupCopied': 'Backup codes copied', + 'settings.mfa.enabled': '2FA is enabled on your account.', + 'settings.mfa.disabled': '2FA is not enabled.', + 'settings.mfa.setup': 'Set up authenticator', + 'settings.mfa.scanQr': + 'Scan this QR code with your app, or enter the secret manually.', + 'settings.mfa.secretLabel': 'Secret key (manual entry)', + 'settings.mfa.codePlaceholder': '6-digit code', + 'settings.mfa.enable': 'Enable 2FA', + 'settings.mfa.cancelSetup': 'Cancel', + 'settings.mfa.disableTitle': 'Disable 2FA', + 'settings.mfa.disableHint': + 'Enter your account password and a current code from your authenticator.', + 'settings.mfa.disable': 'Disable 2FA', + 'settings.mfa.toastEnabled': 'Two-factor authentication enabled', + 'settings.mfa.toastDisabled': 'Two-factor authentication disabled', + 'settings.mfa.demoBlocked': 'Not available in demo mode', + 'settings.oauth.modal.machineClient': + 'Makine istemcisi (tarayıcı girişi yok)', + 'settings.oauth.modal.machineClientHint': + "client_credentials iznini kullanın — yönlendirme URI'lerine gerek yoktur. Belirteç doğrudan client_id + client_secret ile verilir ve seçilen kapsamlar dahilinde sizin adınıza hareket eder.", + 'settings.oauth.modal.machineClientUsage': + 'Belirteç alın: grant_type=client_credentials, client_id ve client_secret ile POST /oauth/token gönderin. Tarayıcı yok, yenileme belirteci yok.', + 'settings.oauth.badge.machine': 'makine', +}; +export default settings; diff --git a/shared/src/i18n/tr/share.ts b/shared/src/i18n/tr/share.ts new file mode 100644 index 00000000..694a8d37 --- /dev/null +++ b/shared/src/i18n/tr/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Public Link', + 'share.linkHint': + 'Create a link anyone can use to view this trip without logging in. Read-only — no editing possible.', + 'share.createLink': 'Create link', + 'share.deleteLink': 'Delete link', + 'share.createError': 'Could not create link', + 'share.permMap': 'Map & Plan', + 'share.permBookings': 'Bookings', + 'share.permPacking': 'Packing', + 'share.permBudget': 'Budget', + 'share.permCollab': 'Chat', +}; +export default share; diff --git a/shared/src/i18n/tr/shared.ts b/shared/src/i18n/tr/shared.ts new file mode 100644 index 00000000..1ca02418 --- /dev/null +++ b/shared/src/i18n/tr/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Link expired or invalid', + 'shared.expiredHint': 'This shared trip link is no longer active.', + 'shared.readOnly': 'Read-only shared view', + 'shared.tabPlan': 'Plan', + 'shared.tabBookings': 'Bookings', + 'shared.tabPacking': 'Packing', + 'shared.tabBudget': 'Budget', + 'shared.tabChat': 'Chat', + 'shared.days': 'days', + 'shared.places': 'places', + 'shared.other': 'Other', + 'shared.totalBudget': 'Total Budget', + 'shared.messages': 'messages', + 'shared.sharedVia': 'Shared via', + 'shared.confirmed': 'Confirmed', + 'shared.pending': 'Pending', +}; +export default shared; diff --git a/shared/src/i18n/tr/stats.ts b/shared/src/i18n/tr/stats.ts new file mode 100644 index 00000000..642a59f2 --- /dev/null +++ b/shared/src/i18n/tr/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Countries', + 'stats.cities': 'Cities', + 'stats.trips': 'Trips', + 'stats.places': 'Places', + 'stats.worldProgress': 'World Progress', + 'stats.visited': 'visited', + 'stats.remaining': 'remaining', + 'stats.visitedCountries': 'Visited Countries', +}; +export default stats; diff --git a/shared/src/i18n/tr/system_notice.ts b/shared/src/i18n/tr/system_notice.ts new file mode 100644 index 00000000..616a1f21 --- /dev/null +++ b/shared/src/i18n/tr/system_notice.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.v3_photos.title': 'Photos have moved in 3.0', + 'system_notice.v3_photos.body': + '**Photos** in the Trip Planner have been removed. Your photos are safe — TREK never modified your Immich or Synology library.\n\nPhotos now live in the **Journey** addon. Journey is optional — if it is not yet available, ask your admin to enable it under Admin → Addons.', + 'system_notice.v3_journey.title': 'Meet Journey — travel journal', + 'system_notice.v3_journey.body': + 'Document your trips as rich travel stories with timelines, photo galleries, and interactive maps.', + 'system_notice.v3_journey.cta_label': 'Open Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Day-by-day timeline & gallery', + 'system_notice.v3_journey.highlight_photos': 'Import from Immich or Synology', + 'system_notice.v3_journey.highlight_share': + 'Share publicly — no login needed', + 'system_notice.v3_journey.highlight_export': 'Export as a PDF photo book', + 'system_notice.v3_features.title': 'More highlights in 3.0', + 'system_notice.v3_features.body': + 'A few more things worth knowing about this release.', + 'system_notice.v3_features.highlight_dashboard': + 'Mobile-first dashboard redesign', + 'system_notice.v3_features.highlight_offline': 'Full offline mode as a PWA', + 'system_notice.v3_features.highlight_search': + 'Real-time place search autocomplete', + 'system_notice.v3_features.highlight_import': + 'Import places from KMZ/KML files', + 'system_notice.v3_mcp.title': 'MCP: OAuth 2.1 upgrade', + 'system_notice.v3_mcp.body': + 'The MCP integration has been fully overhauled. OAuth 2.1 is now the recommended auth method. Legacy static tokens (trek_…) are deprecated and will be removed in a future release.', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 recommended (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 fine-grained permission scopes', + 'system_notice.v3_mcp.highlight_deprecated': 'Static trek_ tokens deprecated', + 'system_notice.v3_mcp.highlight_tools': 'Expanded toolset & prompts', + 'system_notice.v3_thankyou.title': 'A personal note from me', + 'system_notice.v3_thankyou.body': + "Before you go — I want to take a moment.\n\nTREK started as a side project I built for my own trips. I never imagined it would grow into something that 4,000 of you now trust to plan your adventures. Every star, every issue, every feature request — I read them all, and they keep me going through late nights between a full-time job and university.\n\nI want you to know: TREK will always be open source, always self-hosted, always yours. No tracking, no subscriptions, no strings attached. Just a tool built by someone who loves traveling as much as you do.\n\nSpecial thanks to [jubnl](https://github.com/jubnl) — you have become an incredible collaborator. So much of what makes 3.0 great carries your fingerprints. Thank you for believing in this project when it was still rough around the edges.\n\nAnd to every single one of you who filed a bug, translated a string, shared TREK with a friend, or simply used it to plan a trip — **thank you**. You are the reason this exists.\n\nHere's to many more adventures together.\n\n— Maurice\n\n---\n\n[Join the community on Discord](https://discord.gg/7Q6M6jDwzf)\n\nIf TREK makes your travels better, a [small coffee](https://ko-fi.com/mauriceboe) always keeps the lights on.", + 'system_notice.v3014_whitespace_collision.title': + 'Action required: user account conflict', + 'system_notice.v3014_whitespace_collision.body': + 'The 3.0.14 upgrade detected one or more username or email collisions caused by leading/trailing whitespace in stored accounts. Affected accounts were renamed automatically. Check the server logs for lines starting with **[migration] WHITESPACE COLLISION** to identify which accounts need review.', + 'system_notice.welcome_v1.title': 'Welcome to TREK', + 'system_notice.welcome_v1.body': + 'Your all-in-one travel planner. Build itineraries, share trips with friends, and stay organized — online or offline.', + 'system_notice.welcome_v1.cta_label': 'Plan a trip', + 'system_notice.welcome_v1.hero_alt': + 'A scenic travel destination with TREK planning UI overlay', + 'system_notice.welcome_v1.highlight_plan': + 'Day-by-day itineraries for any trip', + 'system_notice.welcome_v1.highlight_share': + 'Collaborate with travel partners', + 'system_notice.welcome_v1.highlight_offline': 'Works offline on mobile', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Previous notice', + 'system_notice.pager.next': 'Next notice', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Go to notice {n}', + 'system_notice.pager.position': 'Notice {current} of {total}', +}; +export default system_notice; diff --git a/shared/src/i18n/tr/todo.ts b/shared/src/i18n/tr/todo.ts new file mode 100644 index 00000000..0ab4e502 --- /dev/null +++ b/shared/src/i18n/tr/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Packing List', + 'todo.subtab.todo': 'To-Do', + 'todo.completed': 'completed', + 'todo.filter.all': 'All', + 'todo.filter.open': 'Open', + 'todo.filter.done': 'Done', + 'todo.uncategorized': 'Uncategorized', + 'todo.namePlaceholder': 'Task name', + 'todo.descriptionPlaceholder': 'Description (optional)', + 'todo.unassigned': 'Unassigned', + 'todo.noCategory': 'No category', + 'todo.hasDescription': 'Has description', + 'todo.addItem': 'Add new task', + 'todo.sidebar.sortBy': 'Sort by', + 'todo.priority': 'Priority', + 'todo.newCategoryLabel': 'new', + 'todo.newCategory': 'Category name', + 'todo.addCategory': 'Add category', + 'todo.newItem': 'New task', + 'todo.empty': 'No tasks yet. Add a task to get started!', + 'todo.filter.my': 'My Tasks', + 'todo.filter.overdue': 'Overdue', + 'todo.sidebar.tasks': 'Tasks', + 'todo.sidebar.categories': 'Categories', + 'todo.detail.title': 'Task', + 'todo.detail.description': 'Description', + 'todo.detail.category': 'Category', + 'todo.detail.dueDate': 'Due date', + 'todo.detail.assignedTo': 'Assigned to', + 'todo.detail.delete': 'Delete', + 'todo.detail.save': 'Save changes', + 'todo.sortByPrio': 'Priority', + 'todo.detail.priority': 'Priority', + 'todo.detail.noPriority': 'None', + 'todo.detail.create': 'Create task', +}; +export default todo; diff --git a/shared/src/i18n/tr/transport.ts b/shared/src/i18n/tr/transport.ts new file mode 100644 index 00000000..7038b558 --- /dev/null +++ b/shared/src/i18n/tr/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Add transport', + 'transport.modalTitle.create': 'Add transport', + 'transport.modalTitle.edit': 'Edit transport', + 'transport.title': 'Transports', + 'transport.addManual': 'Manual Transport', +}; +export default transport; diff --git a/shared/src/i18n/tr/trip.ts b/shared/src/i18n/tr/trip.ts new file mode 100644 index 00000000..cb62f959 --- /dev/null +++ b/shared/src/i18n/tr/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'Plan', + 'trip.tabs.transports': 'Transports', + 'trip.tabs.reservations': 'Bookings', + 'trip.tabs.reservationsShort': 'Book', + 'trip.tabs.packing': 'Packing List', + 'trip.tabs.packingShort': 'Packing', + 'trip.tabs.lists': 'Lists', + 'trip.tabs.listsShort': 'Lists', + 'trip.tabs.budget': 'Budget', + 'trip.tabs.files': 'Files', + 'trip.loading': 'Loading trip...', + 'trip.loadingPhotos': 'Loading place photos...', + 'trip.mobilePlan': 'Plan', + 'trip.mobilePlaces': 'Places', + 'trip.toast.placeUpdated': 'Place updated', + 'trip.toast.placeAdded': 'Place added', + 'trip.toast.placeDeleted': 'Place deleted', + 'trip.toast.selectDay': 'Please select a day first', + 'trip.toast.assignedToDay': 'Place assigned to day', + 'trip.toast.reorderError': 'Failed to reorder', + 'trip.toast.reservationUpdated': 'Reservation updated', + 'trip.toast.reservationAdded': 'Reservation added', + 'trip.toast.deleted': 'Deleted', + 'trip.confirm.deletePlace': 'Are you sure you want to delete this place?', + 'trip.confirm.deletePlaces': 'Delete {count} places?', + 'trip.toast.placesDeleted': '{count} places deleted', +}; +export default trip; diff --git a/shared/src/i18n/tr/trips.ts b/shared/src/i18n/tr/trips.ts new file mode 100644 index 00000000..f3cf7cd3 --- /dev/null +++ b/shared/src/i18n/tr/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} kaldırıldı', + 'trips.memberRemoveError': 'Kaldırılamadı', + 'trips.memberAdded': '{username} eklendi', + 'trips.memberAddError': 'Eklenemedi', + 'trips.reminder': 'Hatırlatma', + 'trips.reminderNone': 'Yok', + 'trips.reminderDay': 'gün', + 'trips.reminderDays': 'gün', + 'trips.reminderCustom': 'Özel', + 'trips.reminderDaysBefore': 'kalkıştan önceki gün sayısı', + 'trips.reminderDisabledHint': + 'Gezi hatırlatmaları devre dışı. Yönetim > Ayarlar > Bildirimler bölümünden etkinleştirin.', +}; +export default trips; diff --git a/shared/src/i18n/tr/undo.ts b/shared/src/i18n/tr/undo.ts new file mode 100644 index 00000000..54d92bc6 --- /dev/null +++ b/shared/src/i18n/tr/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Undo', + 'undo.tooltip': 'Undo: {action}', + 'undo.assignPlace': 'Place assigned to day', + 'undo.removeAssignment': 'Place removed from day', + 'undo.reorder': 'Places reordered', + 'undo.optimize': 'Route optimized', + 'undo.deletePlace': 'Place deleted', + 'undo.deletePlaces': 'Places deleted', + 'undo.moveDay': 'Place moved to another day', + 'undo.lock': 'Place lock toggled', + 'undo.importGpx': 'GPX import', + 'undo.importKeyholeMarkup': 'KMZ/KML import', + 'undo.importGoogleList': 'Google Maps import', + 'undo.importNaverList': 'Naver Maps import', + 'undo.addPlace': 'Place added', + 'undo.done': 'Undone: {action}', +}; +export default undo; diff --git a/shared/src/i18n/tr/vacay.ts b/shared/src/i18n/tr/vacay.ts new file mode 100644 index 00000000..3dbd4f60 --- /dev/null +++ b/shared/src/i18n/tr/vacay.ts @@ -0,0 +1,103 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Plan and manage vacation days', + 'vacay.settings': 'Settings', + 'vacay.year': 'Year', + 'vacay.addYear': 'Add next year', + 'vacay.addPrevYear': 'Add previous year', + 'vacay.removeYear': 'Remove year', + 'vacay.removeYearConfirm': 'Remove {year}?', + 'vacay.removeYearHint': + 'All vacation entries and company holidays for this year will be permanently deleted.', + 'vacay.remove': 'Remove', + 'vacay.persons': 'Persons', + 'vacay.noPersons': 'No persons added', + 'vacay.addPerson': 'Add Person', + 'vacay.editPerson': 'Edit Person', + 'vacay.removePerson': 'Remove Person', + 'vacay.removePersonConfirm': 'Remove {name}?', + 'vacay.removePersonHint': + 'All vacation entries for this person will be permanently deleted.', + 'vacay.personName': 'Name', + 'vacay.personNamePlaceholder': 'Enter name', + 'vacay.color': 'Color', + 'vacay.add': 'Add', + 'vacay.legend': 'Legend', + 'vacay.publicHoliday': 'Public Holiday', + 'vacay.companyHoliday': 'Company Holiday', + 'vacay.weekend': 'Weekend', + 'vacay.modeVacation': 'Vacation', + 'vacay.modeCompany': 'Company Holiday', + 'vacay.entitlement': 'Entitlement', + 'vacay.entitlementDays': 'Days', + 'vacay.used': 'Used', + 'vacay.remaining': 'Left', + 'vacay.carriedOver': 'from {year}', + 'vacay.blockWeekends': 'Block Weekends', + 'vacay.blockWeekendsHint': 'Prevent vacation entries on weekend days', + 'vacay.weekendDays': 'Weekend days', + 'vacay.mon': 'Mon', + 'vacay.tue': 'Tue', + 'vacay.wed': 'Wed', + 'vacay.thu': 'Thu', + 'vacay.fri': 'Fri', + 'vacay.sat': 'Sat', + 'vacay.sun': 'Sun', + 'vacay.publicHolidays': 'Public Holidays', + 'vacay.publicHolidaysHint': 'Mark public holidays in the calendar', + 'vacay.selectCountry': 'Select country', + 'vacay.selectRegion': 'Select region (optional)', + 'vacay.addCalendar': 'Add calendar', + 'vacay.calendarLabel': 'Label (optional)', + 'vacay.calendarColor': 'Color', + 'vacay.noCalendars': 'No holiday calendars added yet', + 'vacay.companyHolidays': 'Company Holidays', + 'vacay.companyHolidaysHint': 'Allow marking company-wide holiday days', + 'vacay.companyHolidaysNoDeduct': + 'Company holidays do not count towards vacation days.', + 'vacay.weekStart': 'Week starts on', + 'vacay.weekStartHint': + 'Choose whether the calendar week starts on Monday or Sunday', + 'vacay.carryOver': 'Carry Over', + 'vacay.carryOverHint': + 'Automatically carry remaining vacation days into the next year', + 'vacay.sharing': 'Sharing', + 'vacay.sharingHint': 'Share your vacation plan with other TREK users', + 'vacay.owner': 'Owner', + 'vacay.shareEmailPlaceholder': 'Email of TREK user', + 'vacay.shareSuccess': 'Plan shared successfully', + 'vacay.shareError': 'Could not share plan', + 'vacay.dissolve': 'Dissolve Fusion', + 'vacay.dissolveHint': 'Separate calendars again. Your entries will be kept.', + 'vacay.dissolveAction': 'Dissolve', + 'vacay.dissolved': 'Calendar separated', + 'vacay.fusedWith': 'Fused with', + 'vacay.you': 'you', + 'vacay.noData': 'No data', + 'vacay.changeColor': 'Change color', + 'vacay.inviteUser': 'Invite User', + 'vacay.inviteHint': + 'Invite another TREK user to share a combined vacation calendar.', + 'vacay.selectUser': 'Select user', + 'vacay.sendInvite': 'Send Invite', + 'vacay.inviteSent': 'Invite sent', + 'vacay.inviteError': 'Could not send invite', + 'vacay.pending': 'pending', + 'vacay.noUsersAvailable': 'No users available', + 'vacay.accept': 'Accept', + 'vacay.decline': 'Decline', + 'vacay.acceptFusion': 'Accept & Fuse', + 'vacay.inviteTitle': 'Fusion Request', + 'vacay.inviteWantsToFuse': 'wants to share a vacation calendar with you.', + 'vacay.fuseInfo1': + 'Both of you will see all vacation entries in one shared calendar.', + 'vacay.fuseInfo2': 'Both parties can create and edit entries for each other.', + 'vacay.fuseInfo3': + 'Both parties can delete entries and change vacation entitlements.', + 'vacay.fuseInfo4': + 'Settings like public holidays and company holidays are shared.', + 'vacay.fuseInfo5': + 'The fusion can be dissolved at any time by either party. Your entries will be preserved.', +}; +export default vacay; diff --git a/shared/src/i18n/types.ts b/shared/src/i18n/types.ts new file mode 100644 index 00000000..5bba13e9 --- /dev/null +++ b/shared/src/i18n/types.ts @@ -0,0 +1,2 @@ +export type TranslationValue = string | { name: string; category: string }[]; +export type TranslationStrings = Record; diff --git a/shared/src/i18n/uk/admin.ts b/shared/src/i18n/uk/admin.ts new file mode 100644 index 00000000..472d515e --- /dev/null +++ b/shared/src/i18n/uk/admin.ts @@ -0,0 +1,374 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': 'n', + 'admin.notifications.hint': + 'Виберіть канал сповіщень. Одночасно може бути активним лише один.', + 'admin.notifications.none': 'Вимкнено', + 'admin.notifications.email': 'Ел. пошта (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': 'Зберегти налаштування сповіщень', + 'admin.notifications.saved': 'Налаштування сповіщень збережені', + 'admin.notifications.testWebhook': 'Відправити тестовий webhook', + 'admin.notifications.testWebhookSuccess': + 'Тестовий webhook успішно відправлено', + 'admin.notifications.testWebhookFailed': + "Помилка відправки тестового webhook'а", + 'admin.smtp.title': 'Пошта та сповіщення', + 'admin.smtp.hint': + 'Налаштування SMTP для відправки сповіщень електронною поштою.', + 'admin.smtp.testButton': 'Відправити тестовий лист', + 'admin.webhook.hint': + 'Відправляти сповіщення через зовнішній webhook (Discord, Slack тощо).', + 'admin.smtp.testSuccess': 'Тестовий лист успішно відправлено', + 'admin.smtp.testFailed': 'Помилка відправки тестового листа', + 'admin.title': 'Адміністрування', + 'admin.subtitle': 'Управління користувачами та системні налаштування', + 'admin.tabs.users': 'Користувачі', + 'admin.tabs.categories': 'Категорії', + 'admin.tabs.backup': 'Резервна копія', + 'admin.tabs.audit': 'Аудит', + 'admin.stats.users': 'Користувачі', + 'admin.stats.trips': 'Подорожі', + 'admin.stats.places': 'Місця', + 'admin.stats.photos': 'Фото', + 'admin.stats.files': 'Файли', + 'admin.table.user': 'Користувач', + 'admin.table.email': 'Ел. пошта', + 'admin.table.role': 'Роль', + 'admin.table.created': 'Створено', + 'admin.table.lastLogin': 'Останній вхід', + 'admin.table.actions': 'Дії', + 'admin.you': '(Ви)', + 'admin.editUser': 'Редагувати користувача', + 'admin.newPassword': 'Новий пароль', + 'admin.newPasswordHint': 'Залиште порожнім, щоб зберегти поточний пароль', + 'admin.deleteUser': + 'Видалити користувача «{name}»? Усі подорожі будуть безповоротно видалені.', + 'admin.deleteUserTitle': 'Видалити користувача', + 'admin.newPasswordPlaceholder': 'Введіть новий пароль…', + 'admin.toast.loadError': 'Не вдалося завантажити дані адміністрування', + 'admin.toast.userUpdated': 'Користувача оновлено', + 'admin.toast.updateError': 'Помилка оновлення', + 'admin.toast.userDeleted': 'Користувача видалено', + 'admin.toast.deleteError': 'Помилка видалення', + 'admin.toast.cannotDeleteSelf': 'Неможливо видалити власний акаунт', + 'admin.toast.userCreated': 'Користувача створено', + 'admin.toast.createError': 'Помилка створення користувача', + 'admin.toast.fieldsRequired': + 'Ім’я користувача, ел. пошта та пароль обов’язкові', + 'admin.createUser': 'Створити користувача', + 'admin.invite.title': 'Запрошення за посиланням', + 'admin.invite.subtitle': 'Створення одноразових посилань для реєстрації', + 'admin.invite.create': 'Створити посилання', + 'admin.invite.createAndCopy': 'Створити та скопіювати', + 'admin.invite.empty': 'Посилання-запрошення ще не створені', + 'admin.invite.maxUses': 'Макс. використань', + 'admin.invite.expiry': 'Дійсне', + 'admin.invite.uses': 'використано', + 'admin.invite.expiresAt': 'закінчується', + 'admin.invite.createdBy': 'від', + 'admin.invite.active': 'Активне', + 'admin.invite.expired': 'Закінчилося', + 'admin.invite.usedUp': 'Вичерпано', + 'admin.invite.copied': 'Посилання-запрошення скопійовано', + 'admin.invite.copyLink': 'Скопіювати посилання', + 'admin.invite.deleted': 'Посилання-запрошення видалено', + 'admin.invite.createError': 'Помилка створення посилання', + 'admin.invite.deleteError': 'Помилка видалення посилання', + 'admin.tabs.settings': 'Налаштування', + 'admin.allowRegistration': 'Дозволити реєстрацію', + 'admin.allowRegistrationHint': + 'Нові користувачі можуть реєструватися самостійно', + 'admin.authMethods': 'Методи автентифікації', + 'admin.passwordLogin': 'Вхід за паролем', + 'admin.passwordLoginHint': + 'Дозволити користувачам входити за ел. поштою та паролем', + 'admin.passwordRegistration': 'Реєстрація за паролем', + 'admin.passwordRegistrationHint': + 'Дозволити новим користувачам реєструватися за ел. поштою та паролем', + 'admin.oidcLogin': 'Вхід через SSO', + 'admin.oidcLoginHint': 'Дозволити користувачам входити через SSO', + 'admin.oidcRegistration': 'Автоматичне створення SSO-акаунтів', + 'admin.oidcRegistrationHint': + 'Автоматично створювати акаунти для нових SSO-користувачів', + 'admin.envOverrideHint': + 'Налаштування входу за паролем контролюються змінною середовища OIDC_ONLY і не можуть бути змінені тут.', + 'admin.lockoutWarning': + 'Потрібно залишити увімкненим принаймні один метод входу', + 'admin.requireMfa': 'Вимагати двофакторну автентифікацію (2FA)', + 'admin.requireMfaHint': + 'Користувачі без 2FA повинні завершити налаштування в розділі «Налаштування» перед використанням програми.', + 'admin.apiKeys': 'API-ключі', + 'admin.apiKeysHint': + 'Необов’язково. Включає розширені дані про місця, такі як фото та погода.', + 'admin.mapsKey': 'API-ключ Google Maps', + 'admin.mapsKeyHint': + 'Необхідний для пошуку місць. Отримайте на console.cloud.google.com', + 'admin.mapsKeyHintLong': + 'Без API-ключа для пошуку місць використовується OpenStreetMap. З ключем Google API можна завантажувати фото, рейтинги та години роботи. Отримайте ключ на console.cloud.google.com.', + 'admin.recommended': 'Рекомендується', + 'admin.weatherKey': 'API-ключ OpenWeatherMap', + 'admin.weatherKeyHint': + 'Для даних про погоду. Безкоштовно на openweathermap.org', + 'admin.validateKey': 'Перевірити', + 'admin.keyValid': 'Підключено', + 'admin.keyInvalid': 'Недійсний', + 'admin.keySaved': 'API-ключі збережено', + 'admin.oidcTitle': 'Єдиний вхід (OIDC)', + 'admin.oidcSubtitle': + 'Дозволити вхід через зовнішніх провайдерів, таких як Google, Apple, Authentik або Keycloak.', + 'admin.oidcDisplayName': 'Відображуване ім’я', + 'admin.oidcIssuer': 'URL видавця', + 'admin.oidcIssuerHint': + 'URL видавця OpenID Connect провайдера. Напр. https://accounts.google.com', + 'admin.oidcSaved': 'Конфігурацію OIDC збережено', + 'admin.oidcOnlyMode': 'Вимкнути вхід за паролем', + 'admin.oidcOnlyModeHint': + 'При ввімкненні дозволено тільки вхід через SSO. Вхід та реєстрація за паролем будуть заблоковані.', + 'admin.fileTypes': 'Дозволені типи файлів', + 'admin.fileTypesHint': + 'Налаштуйте, які типи файлів можуть завантажувати користувачі.', + 'admin.fileTypesFormat': + 'Розширення через кому (напр. jpg,png,pdf,doc). Використовуйте * для дозволу всіх типів.', + 'admin.fileTypesSaved': 'Налаштування типів файлів збережено', + 'admin.placesPhotos.title': 'Фотографії місць', + 'admin.placesPhotos.subtitle': + 'Завантаження фотографій з Google Places API. Вимкніть для економії квоти API. Фотографії Wikimedia не зачіпаються.', + 'admin.placesAutocomplete.title': 'Автодоповнення місць', + 'admin.placesAutocomplete.subtitle': + 'Використання Google Places API для підказок пошуку. Вимкніть для економії квоти API.', + 'admin.placesDetails.title': 'Відомості про місце', + 'admin.placesDetails.subtitle': + 'Завантаження детальної інформації про місце (години роботи, рейтинг, веб-сайт) з Google Places API. Вимкніть для економії квоти API.', + 'admin.bagTracking.title': 'Відстеження багажу', + 'admin.bagTracking.subtitle': + 'Увімкнути вагу та прив’язку до багажу для речей', + 'admin.collab.chat.title': 'Чат', + 'admin.collab.chat.subtitle': 'Обмін повідомленнями для спільної роботи', + 'admin.collab.notes.title': 'Нотатки', + 'admin.collab.notes.subtitle': 'Спільні нотатки та документи', + 'admin.collab.polls.title': 'Опитування', + 'admin.collab.polls.subtitle': 'Групові опитування та голосування', + 'admin.collab.whatsnext.title': 'Що далі', + 'admin.collab.whatsnext.subtitle': 'Пропозиції активностей та наступні кроки', + 'admin.tabs.config': 'Персоналізація', + 'admin.tabs.defaults': 'Налаштування за замовчуванням', + 'admin.defaultSettings.title': 'Налаштування користувачів за замовчуванням', + 'admin.defaultSettings.description': + 'Визначте значення за замовчуванням для всієї інстанції. Користувачі, які не змінювали параметр, побачать ці значення. Їхні власні зміни завжди мають пріоритет.', + 'admin.defaultSettings.saved': 'Значення за замовчуванням збережено', + 'admin.defaultSettings.reset': 'Скинути до вбудованого значення', + 'admin.defaultSettings.resetToBuiltIn': 'скинути', + 'admin.tabs.templates': 'Шаблони пакування', + 'admin.packingTemplates.title': 'Шаблони пакування', + 'admin.packingTemplates.subtitle': + 'Створюйте багаторазові списки речей для подорожей', + 'admin.packingTemplates.create': 'Новий шаблон', + 'admin.packingTemplates.namePlaceholder': + 'Назва шаблону (наприклад, Відпочинок на пляжі)', + 'admin.packingTemplates.empty': 'Шаблони ще не створені', + 'admin.packingTemplates.items': 'речей', + 'admin.packingTemplates.categories': 'категорії(й)', + 'admin.packingTemplates.itemName': 'Назва речі', + 'admin.packingTemplates.itemCategory': 'Категорія', + 'admin.packingTemplates.categoryName': 'Назва категорії (наприклад, Одяг)', + 'admin.packingTemplates.addCategory': 'Додати категорію', + 'admin.packingTemplates.created': 'Шаблон створено', + 'admin.packingTemplates.deleted': 'Шаблон видалено', + 'admin.packingTemplates.loadError': 'Помилка завантаження шаблонів', + 'admin.packingTemplates.createError': 'Помилка створення шаблону', + 'admin.packingTemplates.deleteError': 'Помилка видалення шаблону', + 'admin.packingTemplates.saveError': 'Помилка збереження', + 'admin.tabs.addons': 'Дополнения', + 'admin.addons.title': 'Доповнення', + 'admin.addons.subtitle': + 'Увімкніть або вимкніть функції, щоб налаштувати TREK під себе.', + 'admin.addons.catalog.memories.name': 'Фото (Immich)', + 'admin.addons.catalog.memories.description': + 'Діліться фотографіями з подорожей через Immich', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': + 'Протокол контексту моделі для інтеграції з ІІ-асистентами', + 'admin.addons.catalog.packing.name': 'Списки', + 'admin.addons.catalog.packing.description': + 'Списки речей та завдання для ваших подорожей', + 'admin.addons.catalog.budget.name': 'Бюджет', + 'admin.addons.catalog.budget.description': + 'Відстежуйте витрати та плануйте бюджет подорожі', + 'admin.addons.catalog.documents.name': 'Документи', + 'admin.addons.catalog.documents.description': + 'Зберігайте та керуйте документами для подорожей', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': + 'Особистий планувальник відпусток з календарем', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + 'Карта світу з відвіданими країнами та статистикою подорожей', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': + 'Нотатки в реальному часі, опитування та чат для планування подорожей', + 'admin.addons.subtitleBefore': + 'Увімкніть або вимкніть функції для налаштування ', + 'admin.addons.subtitleAfter': ' під себе.', + 'admin.addons.enabled': 'Увімкнено', + 'admin.addons.disabled': 'Вимкнено', + 'admin.addons.type.trip': 'Поїздка', + 'admin.addons.type.global': 'Глобально', + 'admin.addons.type.integration': 'Інтеграція', + 'admin.addons.tripHint': 'Доступно як вкладка всередині кожної подорожі', + 'admin.addons.globalHint': 'Доступно як окремий розділ у головній навігації', + 'admin.addons.integrationHint': + 'Фонові сервіси та API-інтеграції без окремої сторінки', + 'admin.addons.toast.updated': 'Доповнення оновлено', + 'admin.addons.toast.error': 'Не вдалося оновити доповнення', + 'admin.addons.noAddons': 'Немає доступних доповнень', + 'admin.weather.title': 'Дані про погоду', + 'admin.weather.badge': 'З 24 березня 2026', + 'admin.weather.description': + 'TREK використовує Open-Meteo як джерело даних про погоду. Open-Meteo — безкоштовний сервіс з відкритим кодом, API-ключ не потрібен.', + 'admin.weather.forecast': 'Прогноз на 16 днів', + 'admin.weather.forecastDesc': 'Раніше 5 днів (OpenWeatherMap)', + 'admin.weather.climate': 'Історичні кліматичні дані', + 'admin.weather.climateDesc': + 'Середні значення за останні 85 років для днів поза межами 16-денного прогнозу', + 'admin.weather.requests': '10 000 запитів / день', + 'admin.weather.requestsDesc': 'Безкоштовно, API-ключ не потрібен', + 'admin.weather.locationHint': + 'Погода базується на першому місці з координатами в кожному дні. Якщо жодне місце не призначено на день, як орієнтир використовується будь-яке місце зі списку.', + 'admin.tabs.mcpTokens': 'MCP-доступ', + 'admin.mcpTokens.title': 'MCP-доступ', + 'admin.mcpTokens.subtitle': + 'Управління OAuth-сеансами та API-токенами всіх користувачів', + 'admin.mcpTokens.sectionTitle': 'API-токени', + 'admin.mcpTokens.owner': 'Власник', + 'admin.mcpTokens.tokenName': 'Назва токена', + 'admin.mcpTokens.created': 'Створено', + 'admin.mcpTokens.lastUsed': 'Останнє використання', + 'admin.mcpTokens.never': 'Ніколи', + 'admin.mcpTokens.empty': 'MCP-токени ще не створені', + 'admin.mcpTokens.deleteTitle': 'Видалити токен', + 'admin.mcpTokens.deleteMessage': + 'Токен буде негайно відкликано. Користувач втратить доступ до MCP через цей токен.', + 'admin.mcpTokens.deleteSuccess': 'Токен видалено', + 'admin.mcpTokens.deleteError': 'Не вдалося видалити токен', + 'admin.mcpTokens.loadError': 'Не вдалося завантажити токени', + 'admin.oauthSessions.sectionTitle': 'OAuth-сеанси', + 'admin.oauthSessions.clientName': 'Клієнт', + 'admin.oauthSessions.owner': 'Власник', + 'admin.oauthSessions.scopes': 'Права доступу', + 'admin.oauthSessions.created': 'Створено', + 'admin.oauthSessions.empty': 'Немає активних OAuth-сеансів', + 'admin.oauthSessions.revokeTitle': 'Відкликати сеанс', + 'admin.oauthSessions.revokeMessage': + 'Цей OAuth-сеанс буде негайно відкликаний. Клієнт втратить доступ до MCP.', + 'admin.oauthSessions.revokeSuccess': 'Сеанс відкликано', + 'admin.oauthSessions.revokeError': 'Не вдалося відкликати сеанс', + 'admin.oauthSessions.loadError': 'Не вдалося завантажити OAuth-сеанси', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': + 'Події, пов’язані з безпекою та адмініструванням (резервні копії, користувачі, MFA, налаштування).', + 'admin.audit.empty': 'Записів аудиту поки немає.', + 'admin.audit.refresh': 'Оновити', + 'admin.audit.loadMore': 'Завантажити ще', + 'admin.audit.showing': 'Завантажено: {count} · всього {total}', + 'admin.audit.col.time': 'Час', + 'admin.audit.col.user': 'Користувач', + 'admin.audit.col.action': 'Дія', + 'admin.audit.col.resource': 'Об’єкт', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': 'Деталі', + 'admin.github.title': 'Історія релізів', + 'admin.github.subtitle': 'Останні оновлення з {repo}', + 'admin.github.latest': 'Останній', + 'admin.github.prerelease': 'Пререліз', + 'admin.github.showDetails': 'Показати деталі', + 'admin.github.hideDetails': 'Сховати деталі', + 'admin.github.loadMore': 'Завантажити ще', + 'admin.github.loading': 'Завантаження...', + 'admin.github.support': 'Допомагає продовжувати розробку TREK', + 'admin.github.error': 'Не вдалося завантажити релізи', + 'admin.github.by': 'від', + 'admin.update.available': 'Доступне оновлення', + 'admin.update.text': + 'Доступна версія TREK {version}. У вас встановлено {current}.', + 'admin.update.button': 'Переглянути на GitHub', + 'admin.update.install': 'Встановити оновлення', + 'admin.update.confirmTitle': 'Встановити оновлення?', + 'admin.update.confirmText': + 'TREK буде оновлено з {current} до {version}. Сервер перезапуститься автоматично.', + 'admin.update.dataInfo': + 'Усі ваші дані (поїздки, користувачі, API-ключі, завантаження, Vacay, Atlas, бюджети) будуть збережені.', + 'admin.update.warning': + 'Застосунок буде тимчасово недоступний під час перезапуску.', + 'admin.update.confirm': 'Оновити зараз', + 'admin.update.installing': 'Оновлення…', + 'admin.update.success': 'Оновлення встановлено! Сервер перезапускається…', + 'admin.update.failed': 'Помилка оновлення', + 'admin.update.backupHint': + 'Рекомендуємо створити резервну копію перед оновленням.', + 'admin.update.backupLink': 'Перейти до резервних копій', + 'admin.update.howTo': 'Як оновити', + 'admin.update.dockerText': + 'Ваш екземпляр TREK працює в Docker. Для оновлення до {version} виконайте ці команди на сервері:', + 'admin.update.reloadHint': 'Перезавантажте сторінку через кілька секунд.', + 'admin.tabs.permissions': 'Дозволи', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + 'Сповіщення в додатку завжди активні і не можуть бути вимкнені глобально.', + 'admin.notifications.adminWebhookPanel.title': 'Webhook адміністратора', + 'admin.notifications.adminWebhookPanel.hint': + 'Цей webhook використовується виключно для сповіщень адміністратора (наприклад, повідомлень про версії). Він незалежить від користувацьких вебхуків і надсилається автоматично при наявності URL.', + 'admin.notifications.adminWebhookPanel.saved': + 'URL вебхука адміністратора збережено', + 'admin.notifications.adminWebhookPanel.testSuccess': + 'Тестовий webhook успішно надіслано', + 'admin.notifications.adminWebhookPanel.testFailed': + 'Помилка тестового вебхука', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + 'Webhook адміністратора надсилається автоматично при наявності URL', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + 'Дозволяє користувачам налаштовувати власні теми Ntfy для push-сповіщень. Встановіть сервер за замовчуванням нижче, щоб попередньо заповнити налаштування користувачів.', + 'admin.notifications.testNtfy': 'Надіслати тестовий Ntfy', + 'admin.notifications.testNtfySuccess': 'Тестовий Ntfy успішно надіслано', + 'admin.notifications.testNtfyFailed': 'Помилка надсилання тестового Ntfy', + 'admin.notifications.adminNtfyPanel.title': 'Ntfy адміністратора', + 'admin.notifications.adminNtfyPanel.hint': + 'Ця тема Ntfy використовується виключно для сповіщень адміністратора (наприклад, повідомлень про версії). Вона незалежна від тем користувачів і завжди надсилається при наявності налаштування.', + 'admin.notifications.adminNtfyPanel.serverLabel': 'URL сервера Ntfy', + 'admin.notifications.adminNtfyPanel.serverHint': + 'Також використовується як сервер за замовчуванням для ntfy-сповіщень користувачів. Залиште пустим, щоб використовувати ntfy.sh. Користувачі можуть змінити це в своїх налаштуваннях.', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': 'Тема адміністратора', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': + 'Токен доступу (необов’язково)', + 'admin.notifications.adminNtfyPanel.tokenCleared': + 'Токен доступу адміністратора очищено', + 'admin.notifications.adminNtfyPanel.saved': + 'Налаштування Ntfy адміністратора збережено', + 'admin.notifications.adminNtfyPanel.test': 'Надіслати тестовий Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': + 'Тестовий Ntfy успішно надіслано', + 'admin.notifications.adminNtfyPanel.testFailed': + 'Помилка надсилання тестового Ntfy', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + 'Ntfy адміністратора завжди надсилається при наявності налаштованої теми', + 'admin.notifications.adminNotificationsHint': + 'Налаштуйте, які канали доставляють сповіщення адміністратора (наприклад, повідомлення про версії). Вебхук надсилається автоматично, якщо задано URL вебхука адміністратора.', + 'admin.notifications.tripReminders.title': 'Нагадування про поїздки', + 'admin.notifications.tripReminders.hint': + 'Надсилає нагадування перед початком поїздки (необхідно вказати дні нагадування в параметрах поїздки).', + 'admin.notifications.tripReminders.enabled': + 'Нагадування про поїздки увімкнено', + 'admin.notifications.tripReminders.disabled': + 'Нагадування про поїздки вимкнено', + 'admin.tabs.notifications': 'Сповіщення', + 'admin.addons.catalog.journey.name': 'Journey', + 'admin.addons.catalog.journey.description': + 'Відстеження поїздок і щоденник подорожей з позначками, фото та щоденними історіями', +}; +export default admin; diff --git a/shared/src/i18n/uk/airport.ts b/shared/src/i18n/uk/airport.ts new file mode 100644 index 00000000..4e76558e --- /dev/null +++ b/shared/src/i18n/uk/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': 'Код аеропорту або місто (наприклад, FRA)', +}; +export default airport; diff --git a/shared/src/i18n/uk/atlas.ts b/shared/src/i18n/uk/atlas.ts new file mode 100644 index 00000000..ebe4a117 --- /dev/null +++ b/shared/src/i18n/uk/atlas.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': 'Ваш слід подорожей по всьому світу', + 'atlas.countries': 'Країни', + 'atlas.trips': 'Подорожі', + 'atlas.places': 'Місця', + 'atlas.days': 'Дні', + 'atlas.visitedCountries': 'Відвідані країни', + 'atlas.cities': 'Міста', + 'atlas.noData': 'Даних про подорожі поки немає', + 'atlas.noDataHint': + 'Створіть подорож і додайте місця, щоб побачити карту світу', + 'atlas.lastTrip': 'Остання подорож', + 'atlas.nextTrip': 'Наступна подорож', + 'atlas.daysLeft': 'днів залишилося', + 'atlas.streak': 'Серія', + 'atlas.year': 'рік', + 'atlas.years': 'років', + 'atlas.yearInRow': 'рік підряд', + 'atlas.yearsInRow': 'років підряд', + 'atlas.tripIn': 'подорожі до', + 'atlas.tripsIn': 'подорожей до', + 'atlas.since': 'з', + 'atlas.europe': 'Європа', + 'atlas.asia': 'Азія', + 'atlas.northAmerica': 'Пн. Америка', + 'atlas.southAmerica': 'Пд. Америка', + 'atlas.africa': 'Африка', + 'atlas.oceania': 'Океанія', + 'atlas.other': 'Інше', + 'atlas.firstVisit': 'Перша поїздка', + 'atlas.lastVisitLabel': 'Остання поїздка', + 'atlas.tripSingular': 'Поїздка', + 'atlas.tripPlural': 'Поїздки', + 'atlas.placeVisited': 'Відвідане місце', + 'atlas.placesVisited': 'Відвідані місця', + 'atlas.statsTab': 'Статистика', + 'atlas.bucketTab': 'Список бажань', + 'atlas.addBucket': 'Додати до списку бажань', + 'atlas.bucketNamePlaceholder': 'Місце або напрямок...', + 'atlas.bucketNotesPlaceholder': 'Нотатки (необов’язково)', + 'atlas.bucketEmpty': 'Ваш список бажань порожній', + 'atlas.bucketEmptyHint': 'Додайте місця, які мрієте відвідати', + 'atlas.unmark': 'Видалити', + 'atlas.confirmMark': 'Позначити цю країну як відвідану?', + 'atlas.confirmUnmark': 'Видалити цю країну зі списку відвіданих?', + 'atlas.confirmUnmarkRegion': 'Видалити цей регіон зі списку відвіданих?', + 'atlas.markVisited': 'Позначити як відвідану', + 'atlas.markVisitedHint': 'Додати цю країну до списку відвіданих', + 'atlas.markRegionVisitedHint': 'Додати цей регіон до списку відвіданих', + 'atlas.addToBucket': 'До списку бажань', + 'atlas.addPoi': 'Додати місце', + 'atlas.searchCountry': 'Пошук країни...', + 'atlas.month': 'Місяць', + 'atlas.addToBucketHint': 'Зберегти як місце для відвідування', + 'atlas.bucketWhen': 'Коли ви плануєте поїхати?', +}; +export default atlas; diff --git a/shared/src/i18n/uk/backup.ts b/shared/src/i18n/uk/backup.ts new file mode 100644 index 00000000..04646ee7 --- /dev/null +++ b/shared/src/i18n/uk/backup.ts @@ -0,0 +1,76 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': 'Резервна копія', + 'backup.subtitle': 'База даних та всі завантажені файли', + 'backup.refresh': 'Оновити', + 'backup.upload': 'Завантажити копію', + 'backup.uploading': 'Завантаження…', + 'backup.create': 'Створити копію', + 'backup.creating': 'Створення…', + 'backup.empty': 'Резервних копій немає', + 'backup.createFirst': 'Створити першу копію', + 'backup.download': 'Завантажити', + 'backup.restore': 'Відновити', + 'backup.confirm.restore': + 'Відновити копію «{name}»?\n\nУсі поточні дані будуть замінені даними з копії.', + 'backup.confirm.uploadRestore': + 'Завантажити та відновити файл копії «{name}»?\n\nУсі поточні дані будуть перезаписані.', + 'backup.confirm.delete': 'Видалити копію «{name}»?', + 'backup.toast.loadError': 'Не вдалося завантажити резервні копії', + 'backup.toast.created': 'Резервну копію створено', + 'backup.toast.createError': 'Не вдалося створити резервну копію', + 'backup.toast.restored': 'Копію відновлено. Сторінка перезавантажиться…', + 'backup.toast.restoreError': 'Помилка відновлення', + 'backup.toast.uploadError': 'Помилка завантаження', + 'backup.toast.deleted': 'Резервну копію видалено', + 'backup.toast.deleteError': 'Помилка видалення', + 'backup.toast.downloadError': 'Помилка завантаження', + 'backup.toast.settingsSaved': 'Налаштування автокопіювання збережено', + 'backup.toast.settingsError': 'Не вдалося зберегти налаштування', + 'backup.auto.title': 'Автокопіювання', + 'backup.auto.subtitle': 'Автоматичне резервне копіювання за розкладом', + 'backup.auto.enable': 'Увімкнути автокопіювання', + 'backup.auto.enableHint': + 'Резервні копії створюватимуться автоматично за обраним графіком', + 'backup.auto.interval': 'Інтервал', + 'backup.auto.hour': 'Запуск о', + 'backup.auto.hourHint': 'Місцевий час сервера (формат {format})', + 'backup.auto.dayOfWeek': 'День тижня', + 'backup.auto.dayOfMonth': 'День місяця', + 'backup.auto.dayOfMonthHint': 'Обмежено 1–28 для сумісності з усіма місяцями', + 'backup.auto.scheduleSummary': 'Графік', + 'backup.auto.summaryDaily': 'Щодня о {hour}:00', + 'backup.auto.summaryWeekly': 'Щотижня в {day} о {hour}:00', + 'backup.auto.summaryMonthly': '{day}-го числа кожного місяця о {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + 'Автокопіювання налаштовано через змінні оточення Docker. Щоб змінити параметри, оновіть docker-compose.yml і перезапустіть контейнер.', + 'backup.auto.copyEnv': 'Скопіювати змінні оточення Docker', + 'backup.auto.envCopied': 'Змінні оточення Docker скопійовано в буфер обміну', + 'backup.auto.keepLabel': 'Видаляти старі копії через', + 'backup.dow.sunday': 'Нд', + 'backup.dow.monday': 'Пн', + 'backup.dow.tuesday': 'Вт', + 'backup.dow.wednesday': 'Ср', + 'backup.dow.thursday': 'Чт', + 'backup.dow.friday': 'Пт', + 'backup.dow.saturday': 'Сб', + 'backup.interval.hourly': 'Кожну годину', + 'backup.interval.daily': 'Щодня', + 'backup.interval.weekly': 'Щотижня', + 'backup.interval.monthly': 'Щомісяця', + 'backup.keep.1day': '1 день', + 'backup.keep.3days': '3 дні', + 'backup.keep.7days': '7 днів', + 'backup.keep.14days': '14 днів', + 'backup.keep.30days': '30 днів', + 'backup.keep.forever': 'Зберігати вічно', + 'backup.restoreConfirmTitle': 'Відновити копію?', + 'backup.restoreWarning': + 'Усі поточні дані (поїздки, місця, користувачі, завантаження) будуть безповоротно замінені даними з копії. Цю дію неможливо скасувати.', + 'backup.restoreTip': + 'Порада: створіть резервну копію поточного стану перед відновленням.', + 'backup.restoreConfirm': 'Так, відновити', +}; +export default backup; diff --git a/shared/src/i18n/uk/budget.ts b/shared/src/i18n/uk/budget.ts new file mode 100644 index 00000000..22a9a603 --- /dev/null +++ b/shared/src/i18n/uk/budget.ts @@ -0,0 +1,43 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': 'Бюджет', + 'budget.exportCsv': 'Експорт CSV', + 'budget.emptyTitle': 'Бюджет ще не створено', + 'budget.emptyText': + 'Створіть категорії й записи для планування бюджету поїздки', + 'budget.emptyPlaceholder': 'Введіть назву категорії...', + 'budget.createCategory': 'Створити категорію', + 'budget.category': 'Категорія', + 'budget.categoryName': 'Назва категорії', + 'budget.table.name': 'Назва', + 'budget.table.total': 'Всього', + 'budget.table.persons': 'Осіб', + 'budget.table.days': 'Днів', + 'budget.table.perPerson': 'На особу', + 'budget.table.perDay': 'На день', + 'budget.table.perPersonDay': 'Ос./день', + 'budget.table.note': 'Нотатка', + 'budget.table.date': 'Дата', + 'budget.newEntry': 'Новий запис', + 'budget.defaultEntry': 'Новий запис', + 'budget.defaultCategory': 'Нова категорія', + 'budget.total': 'Всього', + 'budget.totalBudget': 'Загальний бюджет', + 'budget.byCategory': 'За категоріями', + 'budget.editTooltip': 'Натисніть для редагування', + 'budget.linkedToReservation': 'Пов’язано з бронюванням — редагуйте назву там', + 'budget.confirm.deleteCategory': + 'Ви впевнені, що хочете видалити категорію «{name}» з {count} записами?', + 'budget.deleteCategory': 'Видалити категорію', + 'budget.perPerson': 'На особу', + 'budget.paid': 'Оплачено', + 'budget.open': 'Не оплачено', + 'budget.noMembers': 'Учасники не призначені', + 'budget.settlement': 'Взаєморозрахунок', + 'budget.settlementInfo': + 'Натисніть на аватар учасника в рядку бюджету, щоб відзначити його зеленим — це означає, що він заплатив. Взаєморозрахунок покаже, хто кому і скільки винен.', + 'budget.netBalances': 'Чисті баланси', + 'budget.categoriesLabel': 'категорії', +}; +export default budget; diff --git a/shared/src/i18n/uk/categories.ts b/shared/src/i18n/uk/categories.ts new file mode 100644 index 00000000..f0f2617d --- /dev/null +++ b/shared/src/i18n/uk/categories.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': 'Категорії', + 'categories.subtitle': 'Управління категоріями місць', + 'categories.new': 'Нова категорія', + 'categories.empty': 'Категорій поки немає', + 'categories.namePlaceholder': 'Назва категорії', + 'categories.icon': 'Іконка', + 'categories.color': 'Колір', + 'categories.customColor': 'Вибрати свій колір', + 'categories.preview': 'Попередній перегляд', + 'categories.defaultName': 'Категорія', + 'categories.update': 'Оновити', + 'categories.create': 'Створити', + 'categories.confirm.delete': + 'Видалити категорію? Місця в цій категорії не будуть видалені.', + 'categories.toast.loadError': 'Не вдалося завантажити категорії', + 'categories.toast.nameRequired': 'Введіть назву', + 'categories.toast.updated': 'Категорію оновлено', + 'categories.toast.created': 'Категорію створено', + 'categories.toast.saveError': 'Помилка збереження', + 'categories.toast.deleted': 'Категорію видалено', + 'categories.toast.deleteError': 'Помилка видалення', +}; +export default categories; diff --git a/shared/src/i18n/uk/collab.ts b/shared/src/i18n/uk/collab.ts new file mode 100644 index 00000000..710342f9 --- /dev/null +++ b/shared/src/i18n/uk/collab.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': 'Чат', + 'collab.tabs.notes': 'Нотатки', + 'collab.tabs.polls': 'Опитування', + 'collab.whatsNext.title': 'Що далі', + 'collab.whatsNext.today': 'Сьогодні', + 'collab.whatsNext.tomorrow': 'Завтра', + 'collab.whatsNext.empty': 'Немає майбутніх активностей', + 'collab.whatsNext.until': 'до', + 'collab.whatsNext.emptyHint': 'Активності з часом відображатимуться тут', + 'collab.chat.send': 'Відправити', + 'collab.chat.placeholder': 'Введіть повідомлення...', + 'collab.chat.empty': 'Розпочніть розмову', + 'collab.chat.emptyHint': 'Повідомлення видимі всім учасникам поїздки', + 'collab.chat.emptyDesc': + 'Діліться ідеями, планами та новинами з вашою групою', + 'collab.chat.today': 'Сьогодні', + 'collab.chat.yesterday': 'Вчора', + 'collab.chat.deletedMessage': 'видалив(ла) повідомлення', + 'collab.chat.reply': 'Відповісти', + 'collab.chat.loadMore': 'Завантажити старі повідомлення', + 'collab.chat.justNow': 'щойно', + 'collab.chat.minutesAgo': '{n} хв. тому', + 'collab.chat.hoursAgo': '{n} год. тому', + 'collab.notes.title': 'Нотатки', + 'collab.notes.new': 'Нова нотатка', + 'collab.notes.empty': 'Нотаток поки що немає', + 'collab.notes.emptyHint': 'Почніть записувати ідеї та плани', + 'collab.notes.all': 'Усі', + 'collab.notes.titlePlaceholder': 'Назва нотатки', + 'collab.notes.contentPlaceholder': 'Напишіть щось...', + 'collab.notes.categoryPlaceholder': 'Категорія', + 'collab.notes.newCategory': 'Нова категорія...', + 'collab.notes.category': 'Категорія', + 'collab.notes.noCategory': 'Без категорії', + 'collab.notes.color': 'Колір', + 'collab.notes.save': 'Зберегти', + 'collab.notes.cancel': 'Скасувати', + 'collab.notes.edit': 'Редагувати', + 'collab.notes.delete': 'Видалити', + 'collab.notes.pin': 'Закріпити', + 'collab.notes.unpin': 'Відкріпити', + 'collab.notes.daysAgo': '{n} дн. тому', + 'collab.notes.categorySettings': 'Керування категоріями', + 'collab.notes.create': 'Створити', + 'collab.notes.website': 'Сайт', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': 'Прикріпити файли', + 'collab.notes.noCategoriesYet': 'Категорій поки що немає', + 'collab.notes.emptyDesc': 'Створіть нотатку, щоб почати', + 'collab.polls.title': 'Опитування', + 'collab.polls.new': 'Нове опитування', + 'collab.polls.empty': 'Опитувань поки що немає', + 'collab.polls.emptyHint': 'Поставте питання групі і голосуйте разом', + 'collab.polls.question': 'Питання', + 'collab.polls.questionPlaceholder': 'Що нам робити?', + 'collab.polls.addOption': '+ Додати варіант', + 'collab.polls.optionPlaceholder': 'Варіант {n}', + 'collab.polls.create': 'Створити опитування', + 'collab.polls.close': 'Закрити', + 'collab.polls.closed': 'Закрите', + 'collab.polls.votes': '{n} голосів', + 'collab.polls.vote': '{n} голос', + 'collab.polls.multipleChoice': 'Багатоваріантний вибір', + 'collab.polls.multiChoice': 'Багатоваріантний вибір', + 'collab.polls.deadline': 'Термін', + 'collab.polls.option': 'Варіант', + 'collab.polls.options': 'Варіанти', + 'collab.polls.delete': 'Видалити', + 'collab.polls.closedSection': 'Закриті', +}; +export default collab; diff --git a/shared/src/i18n/uk/common.ts b/shared/src/i18n/uk/common.ts new file mode 100644 index 00000000..3b19358d --- /dev/null +++ b/shared/src/i18n/uk/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': 'Зберегти', + 'common.showMore': 'Показати більше', + 'common.showLess': 'Показати менше', + 'common.cancel': 'Скасувати', + 'common.clear': 'Очистити', + 'common.delete': 'Видалити', + 'common.edit': 'Редагувати', + 'common.add': 'Додати', + 'common.loading': 'Завантаження...', + 'common.import': 'Імпорт', + 'common.select': 'Вибрати', + 'common.selectAll': 'Вибрати все', + 'common.deselectAll': 'Зняти виділення з усіх', + 'common.error': 'Помилка', + 'common.unknownError': 'Невідома помилка', + 'common.tooManyAttempts': 'Занадто багато спроб. Спробуйте пізніше.', + 'common.back': 'Назад', + 'common.all': 'Все', + 'common.close': 'Закрити', + 'common.open': 'Відкрити', + 'common.upload': 'Завантажити', + 'common.search': 'Пошук', + 'common.confirm': 'Підтвердити', + 'common.ok': 'ОК', + 'common.yes': 'Так', + 'common.no': 'Ні', + 'common.or': 'або', + 'common.none': 'Немає', + 'common.date': 'Дата', + 'common.rename': 'Перейменувати', + 'common.discardChanges': 'Скасувати зміни', + 'common.discard': 'Скасувати', + 'common.name': "Ім'я", + 'common.email': 'Ел. пошта', + 'common.password': 'Пароль', + 'common.saving': 'Збереження...', + 'common.saved': 'Збережено', + 'common.expand': 'Розгорнути', + 'common.collapse': 'Згорнути', + 'common.update': 'Оновити', + 'common.change': 'Змінити', + 'common.uploading': 'Завантаження…', + 'common.backToPlanning': 'Повернутися до планування', + 'common.reset': 'Скинути', + 'common.copy': 'Копіювати', + 'common.copied': 'Скопійовано', + 'common.justNow': 'щойно', + 'common.hoursAgo': '{count} год. тому', + 'common.daysAgo': '{count} дн. тому', +}; +export default common; diff --git a/shared/src/i18n/uk/dashboard.ts b/shared/src/i18n/uk/dashboard.ts new file mode 100644 index 00000000..fc59238e --- /dev/null +++ b/shared/src/i18n/uk/dashboard.ts @@ -0,0 +1,121 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': 'Мої поїздки', + 'dashboard.subtitle.loading': 'Завантаження поїздок...', + 'dashboard.subtitle.trips': '{count} поїздок ({archived} в архіві)', + 'dashboard.subtitle.empty': 'Почніть свою першу поїздку', + 'dashboard.subtitle.activeOne': '{count} активна поїздка', + 'dashboard.subtitle.activeMany': '{count} активних поїздок', + 'dashboard.subtitle.archivedSuffix': ' · {count} в архіві', + 'dashboard.newTrip': 'Нова поїздка', + 'dashboard.gridView': 'Плитка', + 'dashboard.listView': 'Список', + 'dashboard.currency': 'Валюта', + 'dashboard.timezone': 'Часові пояси', + 'dashboard.localTime': 'Місцеве', + 'dashboard.timezoneCustomTitle': 'Свій часовий пояс', + 'dashboard.timezoneCustomLabelPlaceholder': "Назва (необов'язково)", + 'dashboard.timezoneCustomTzPlaceholder': 'напр. America/New_York', + 'dashboard.timezoneCustomAdd': 'Додати', + 'dashboard.timezoneCustomErrorEmpty': 'Введіть ідентифікатор часового поясу', + 'dashboard.timezoneCustomErrorInvalid': + 'Невірний часовий пояс. Використовуйте формат Europe/Berlin', + 'dashboard.timezoneCustomErrorDuplicate': 'Вже додано', + 'dashboard.emptyTitle': 'Немає поїздок', + 'dashboard.emptyText': 'Створіть свою першу поїздку і почніть планувати!', + 'dashboard.emptyButton': 'Створити першу поїздку', + 'dashboard.nextTrip': 'Наступна поїздка', + 'dashboard.shared': 'Спільна', + 'dashboard.sharedBy': 'Поділився {name}', + 'dashboard.days': 'Дні', + 'dashboard.places': 'Місця', + 'dashboard.members': 'Попутники', + 'dashboard.archive': 'Архівувати', + 'dashboard.copyTrip': 'Копіювати', + 'dashboard.copySuffix': 'копія', + 'dashboard.restore': 'Відновити', + 'dashboard.archived': 'В архіві', + 'dashboard.status.ongoing': 'Триває', + 'dashboard.status.today': 'Сьогодні', + 'dashboard.status.tomorrow': 'Завтра', + 'dashboard.status.past': 'Минуло', + 'dashboard.status.daysLeft': 'залишилось {count} дн.', + 'dashboard.toast.loadError': 'Не вдалося завантажити поїздки', + 'dashboard.toast.created': 'Поїздка створена!', + 'dashboard.toast.createError': 'Не вдалося створити поїздку', + 'dashboard.toast.updated': 'Поїздка оновлена!', + 'dashboard.toast.updateError': 'Не вдалося оновити поїздку', + 'dashboard.toast.deleted': 'Поїздка видалена', + 'dashboard.toast.deleteError': 'Не вдалося видалити поїздку', + 'dashboard.toast.archived': 'Поїздка архівована', + 'dashboard.toast.archiveError': 'Не вдалося архівувати поїздку', + 'dashboard.toast.restored': 'Поїздка відновлена', + 'dashboard.toast.restoreError': 'Не вдалося відновити поїздку', + 'dashboard.toast.copied': 'Поїздка скопійована!', + 'dashboard.toast.copyError': 'Не вдалося скопіювати поїздку', + 'dashboard.confirm.copy.confirm': 'Скопіювати поїздку', + 'dashboard.confirm.copy.title': 'Скопіювати цю поїздку?', + 'dashboard.confirm.copy.will1': 'Дні, місця та призначення за днями', + 'dashboard.confirm.copy.will2': 'Проживання та бронювання', + 'dashboard.confirm.copy.will3': 'Статті бюджету та порядок категорій', + 'dashboard.confirm.copy.will4': 'Списки речей (непозначені)', + 'dashboard.confirm.copy.will5': 'Задачі (непризначені та непозначені)', + 'dashboard.confirm.copy.will6': 'Нотатки дня', + 'dashboard.confirm.copy.willCopy': 'Буде скопійовано', + 'dashboard.confirm.copy.wont1': 'Учасники та їх призначення', + 'dashboard.confirm.copy.wont2': 'Спільні нотатки, опитування та повідомлення', + 'dashboard.confirm.copy.wont3': 'Файли та фото', + 'dashboard.confirm.copy.wont4': 'Токени доступу', + 'dashboard.confirm.copy.wontCopy': 'Не буде скопійовано', + 'dashboard.confirm.delete': + 'Видалити поїздку «{title}»? Всі місця та плани будуть безповоротно видалені.', + 'dashboard.editTrip': 'Редагувати поїздку', + 'dashboard.createTrip': 'Створити нову поїздку', + 'dashboard.tripTitle': 'Назва', + 'dashboard.tripTitlePlaceholder': 'напр. Літо в Японії', + 'dashboard.tripDescription': 'Опис', + 'dashboard.tripDescriptionPlaceholder': 'Про що ця поїздка?', + 'dashboard.startDate': 'Дата початку', + 'dashboard.endDate': 'Дата закінчення', + 'dashboard.dayCount': 'Кількість днів', + 'dashboard.dayCountHint': + 'Скільки днів планувати, якщо дати поїздки не вказані.', + 'dashboard.noDateHint': + 'Дата не вказана — буде створено 7 днів за замовчуванням. Ви можете змінити це в будь-який час.', + 'dashboard.coverImage': 'Обкладинка', + 'dashboard.addCoverImage': 'Додати обкладинку', + 'dashboard.addMembers': 'Учасники', + 'dashboard.addMember': 'Додати учасника', + 'dashboard.coverSaved': 'Обкладинка збережена', + 'dashboard.coverUploadError': 'Помилка завантаження', + 'dashboard.coverRemoveError': 'Помилка видалення', + 'dashboard.titleRequired': "Назва обов'язкова", + 'dashboard.endDateError': 'Дата закінчення повинна бути пізніше дати початку', + 'dashboard.greeting.morning': 'Доброго ранку,', + 'dashboard.greeting.afternoon': 'Доброго дня,', + 'dashboard.greeting.evening': 'Доброго вечора,', + 'dashboard.mobile.liveNow': 'Зараз у дорозі', + 'dashboard.mobile.tripProgress': 'Прогрес поїздки', + 'dashboard.mobile.daysLeft': 'залишилось {count} дн.', + 'dashboard.mobile.places': 'Місця', + 'dashboard.mobile.buddies': 'Учасники', + 'dashboard.mobile.newTrip': 'Нова поїздка', + 'dashboard.mobile.currency': 'Валюта', + 'dashboard.mobile.timezone': 'Часовий пояс', + 'dashboard.mobile.upcomingTrips': 'Найближчі поїздки', + 'dashboard.mobile.yourTrips': 'Ваші поїздки', + 'dashboard.mobile.trips': 'поїздок', + 'dashboard.mobile.starts': 'Початок', + 'dashboard.mobile.duration': 'Тривалість', + 'dashboard.mobile.day': 'день', + 'dashboard.mobile.days': 'днів', + 'dashboard.mobile.ongoing': 'Триває', + 'dashboard.mobile.startsToday': 'Починається сьогодні', + 'dashboard.mobile.tomorrow': 'Завтра', + 'dashboard.mobile.inDays': 'Через {count} дн.', + 'dashboard.mobile.inMonths': 'Через {count} міс.', + 'dashboard.mobile.completed': 'Завершено', + 'dashboard.mobile.currencyConverter': 'Конвертер валют', +}; +export default dashboard; diff --git a/shared/src/i18n/uk/day.ts b/shared/src/i18n/uk/day.ts new file mode 100644 index 00000000..13533546 --- /dev/null +++ b/shared/src/i18n/uk/day.ts @@ -0,0 +1,26 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': 'Ймовірність опадів', + 'day.precipitation': 'Опади', + 'day.wind': 'Вітер', + 'day.sunrise': 'Схід', + 'day.sunset': 'Захід', + 'day.hourlyForecast': 'Погодний прогноз по годинах', + 'day.climateHint': + 'Історичні середні — реальний прогноз доступний за 16 днів до цієї дати.', + 'day.noWeather': 'Дані про погоду недоступні. Додайте місце з координатами.', + 'day.overview': 'Огляд дня', + 'day.accommodation': 'Проживання', + 'day.addAccommodation': 'Додати проживання', + 'day.hotelDayRange': 'Застосувати до днів', + 'day.noPlacesForHotel': 'Спочатку додайте місця до поїздки', + 'day.allDays': 'Усі', + 'day.checkIn': 'Заїзд', + 'day.checkInUntil': 'До', + 'day.checkOut': 'Виїзд', + 'day.confirmation': 'Підтвердження', + 'day.editAccommodation': 'Редагувати проживання', + 'day.reservations': 'Бронювання', +}; +export default day; diff --git a/shared/src/i18n/uk/dayplan.ts b/shared/src/i18n/uk/dayplan.ts new file mode 100644 index 00000000..8185dfb6 --- /dev/null +++ b/shared/src/i18n/uk/dayplan.ts @@ -0,0 +1,48 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': 'Експорт календаря (ICS)', + 'dayplan.emptyDay': 'На цей день місць не заплановано', + 'dayplan.addNote': 'Додати нотатку', + 'dayplan.editNote': 'Редагувати нотатку', + 'dayplan.noteAdd': 'Додати нотатку', + 'dayplan.noteEdit': 'Редагувати нотатку', + 'dayplan.noteTitle': 'Нотатка', + 'dayplan.noteSubtitle': 'Нотатка на день', + 'dayplan.collapseAll': 'Згорнути всі дні', + 'dayplan.expandAll': 'Розгорнути всі дні', + 'dayplan.totalCost': 'Загальна вартість', + 'dayplan.days': 'Дни', + 'dayplan.dayN': 'День {n}', + 'dayplan.calculating': 'Розрахунок...', + 'dayplan.route': 'Маршрут', + 'dayplan.optimize': 'Оптимізувати', + 'dayplan.optimized': 'Маршрут оптимізовано', + 'dayplan.routeError': 'Не вдалося розрахувати маршрут', + 'dayplan.toast.needTwoPlaces': + 'Для оптимизации маршрута нужно минимум два места', + 'dayplan.toast.routeOptimized': 'Маршрут оптимизирован', + 'dayplan.toast.noGeoPlaces': + 'Не знайдено місць з координатами для розрахунку маршруту', + 'dayplan.confirmed': 'Подтверждено', + 'dayplan.pendingRes': 'Ожидание', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': 'Експортувати план дня в PDF', + 'dayplan.pdfError': 'Помилка експорту PDF', + 'dayplan.cannotReorderTransport': + 'Бронювання з фіксованим часом не можна переміщувати', + 'dayplan.confirmRemoveTimeTitle': 'Видалити час?', + 'dayplan.confirmRemoveTimeBody': + 'У цього місця фіксований час ({time}). При переміщенні час буде видалено, і стане доступне вільне сортування.', + 'dayplan.confirmRemoveTimeAction': 'Видалити час і перемістити', + 'dayplan.cannotDropOnTimed': + 'Елементи не можна розміщувати між записами з фіксованим часом', + 'dayplan.cannotBreakChronology': + 'Це порушить хронологічний порядок запланованих елементів і бронювань', + 'dayplan.mobile.addPlace': 'Додати місце', + 'dayplan.mobile.searchPlaces': 'Пошук місць...', + 'dayplan.mobile.allAssigned': 'Усі місця розподілені', + 'dayplan.mobile.noMatch': 'Немає збігів', + 'dayplan.mobile.createNew': 'Створити нове місце', +}; +export default dayplan; diff --git a/shared/src/i18n/uk/externalNotifications.ts b/shared/src/i18n/uk/externalNotifications.ts new file mode 100644 index 00000000..50d2b8e3 --- /dev/null +++ b/shared/src/i18n/uk/externalNotifications.ts @@ -0,0 +1,63 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const uk: NotificationLocale = { + email: { + footer: 'Ви отримали це, оскільки увімкнули сповіщення в TREK.', + manage: 'Керувати налаштуваннями у Налаштуваннях', + madeWith: 'Made with', + openTrek: 'Відкрити TREK', + }, + events: { + trip_invite: (p) => ({ + title: `Запрошення до "${p.trip}"`, + body: `${p.actor} запросив ${p.invitee || 'учасника'} до подорожі "${p.trip}".`, + }), + booking_change: (p) => ({ + title: `Нове бронювання: ${p.booking}`, + body: `${p.actor} додав бронювання "${p.booking}" (${p.type}) до "${p.trip}".`, + }), + trip_reminder: (p) => ({ + title: `Нагадування про подорож: ${p.trip}`, + body: `Ваша подорож "${p.trip}" наближається!`, + }), + todo_due: (p) => ({ + title: `Завдання з терміном: ${p.todo}`, + body: `"${p.todo}" у "${p.trip}" — термін ${p.due}.`, + }), + vacay_invite: (p) => ({ + title: 'Запрошення Vacay Fusion', + body: `${p.actor} запрошує вас об'єднати плани відпустки. Відкрийте TREK, щоб прийняти або відхилити.`, + }), + photos_shared: (p) => ({ + title: `${p.count} фото поділились`, + body: `${p.actor} поділився ${p.count} фото у "${p.trip}".`, + }), + collab_message: (p) => ({ + title: `Нове повідомлення у "${p.trip}"`, + body: `${p.actor}: ${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `Пакування: ${p.category}`, + body: `${p.actor} призначив вас до категорії "${p.category}" у "${p.trip}".`, + }), + version_available: (p) => ({ + title: 'Доступна нова версія TREK', + body: `TREK ${p.version} тепер доступний. Перейдіть до панелі адміністратора для оновлення.`, + }), + synology_session_cleared: () => ({ + title: 'Сеанс Synology скинуто', + body: 'Ваш обліковий запис або URL Synology змінився. Ви вийшли з Synology Photos.', + }), + }, + passwordReset: { + subject: 'Скидання пароля', + greeting: 'Привіт', + body: 'Ми отримали запит на скидання пароля вашого облікового запису TREK. Натисніть кнопку нижче, щоб встановити новий пароль.', + ctaIntro: 'Скинути пароль', + expiry: 'Це посилання дійсне протягом 60 хвилин.', + ignore: + 'Якщо ви не надсилали цей запит, просто проігноруйте цей лист — ваш пароль залишиться незмінним.', + }, +}; + +export default uk; diff --git a/shared/src/i18n/uk/files.ts b/shared/src/i18n/uk/files.ts new file mode 100644 index 00000000..7d04fd9f --- /dev/null +++ b/shared/src/i18n/uk/files.ts @@ -0,0 +1,62 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': 'Файли', + 'files.pageTitle': 'Файли та документи', + 'files.subtitle': '{count} файлів для {trip}', + 'files.download': 'Завантажити', + 'files.openError': 'Не вдалося відкрити файл', + 'files.downloadPdf': 'Завантажити PDF', + 'files.count': '{count} файлів', + 'files.countSingular': '1 файл', + 'files.uploaded': '{count} завантажено', + 'files.uploadError': 'Помилка завантаження', + 'files.dropzone': 'Перетягніть файли сюди', + 'files.dropzoneHint': 'або натисніть для вибору', + 'files.allowedTypes': + 'Зображення, PDF, DOC, DOCX, XLS, XLSX, TXT, CSV · Макс. 50 МБ', + 'files.uploading': 'Завантаження...', + 'files.filterAll': 'Усі', + 'files.filterPdf': 'PDF', + 'files.filterImages': 'Зображення', + 'files.filterDocs': 'Документи', + 'files.filterCollab': 'Нотатки Collab', + 'files.sourceCollab': 'З нотаток Collab', + 'files.empty': 'Файлів поки немає', + 'files.emptyHint': 'Завантажте файли, щоб прикріпити їх до поїздки', + 'files.openTab': 'Відкрити в новій вкладці', + 'files.confirm.delete': 'Ви впевнені, що хочете видалити цей файл?', + 'files.toast.deleted': 'Файл видалено', + 'files.toast.deleteError': 'Не вдалося видалити файл', + 'files.sourcePlan': 'План дня', + 'files.sourceBooking': 'Бронювання', + 'files.attach': 'Прикріпити', + 'files.pasteHint': 'Також можна вставити зображення з буфера обміну (Ctrl+V)', + 'files.trash': 'Кошик', + 'files.trashEmpty': 'Кошик порожній', + 'files.emptyTrash': 'Очистити кошик', + 'files.restore': 'Відновити', + 'files.star': 'У фаворити', + 'files.unstar': 'Зняти з фаворитів', + 'files.assign': 'Призначити', + 'files.assignTitle': 'Призначити файл', + 'files.assignPlace': 'Місце', + 'files.assignBooking': 'Бронювання', + 'files.unassigned': 'Не призначено', + 'files.unlink': 'Видалити зв’язок', + 'files.toast.trashed': 'Переміщено до кошика', + 'files.toast.restored': 'Файл відновлено', + 'files.toast.trashEmptied': 'Кошик очищено', + 'files.toast.assigned': 'Файл призначено', + 'files.toast.assignError': 'Помилка призначення', + 'files.toast.restoreError': 'Помилка відновлення', + 'files.confirm.permanentDelete': + 'Безповоротно видалити цей файл? Цю дію неможливо скасувати.', + 'files.confirm.emptyTrash': + 'Безповоротно видалити всі файли з кошика? Цю дію неможливо скасувати.', + 'files.noteLabel': 'Нотатка', + 'files.notePlaceholder': 'Додати нотатку...', + 'files.assignTransport': 'Транспорт', + 'files.sourceTransport': 'Транспорт', +}; +export default files; diff --git a/shared/src/i18n/uk/index.ts b/shared/src/i18n/uk/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/uk/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/uk/inspector.ts b/shared/src/i18n/uk/inspector.ts new file mode 100644 index 00000000..ff0ef9d7 --- /dev/null +++ b/shared/src/i18n/uk/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': 'Відкрито', + 'inspector.closed': 'Закрито', + 'inspector.openingHours': 'Години роботи', + 'inspector.showHours': 'Показати години роботи', + 'inspector.files': 'Файли', + 'inspector.filesCount': '{count} файлів', + 'inspector.removeFromDay': 'Прибрати з дня', + 'inspector.remove': 'Видалити', + 'inspector.addToDay': 'Додати до дня', + 'inspector.confirmedRes': 'Підтверджене бронювання', + 'inspector.pendingRes': 'Очікуване бронювання', + 'inspector.google': 'Відкрити в Google Maps', + 'inspector.website': 'Відкрити сайт', + 'inspector.addRes': 'Бронювання', + 'inspector.editRes': 'Редагувати бронювання', + 'inspector.participants': 'Учасники', + 'inspector.trackStats': 'Дані маршруту', +}; +export default inspector; diff --git a/shared/src/i18n/uk/journey.ts b/shared/src/i18n/uk/journey.ts new file mode 100644 index 00000000..161533e3 --- /dev/null +++ b/shared/src/i18n/uk/journey.ts @@ -0,0 +1,245 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': 'Пошук подорожей…', + 'journey.search.noResults': 'Подорожей за запитом «{query}» не знайдено', + 'journey.title': 'Journey', + 'journey.subtitle': 'Відстежуйте свої подорожі в реальному часі', + 'journey.new': 'Нова подорож', + 'journey.create': 'Створити', + 'journey.titlePlaceholder': 'Куди ви їдете?', + 'journey.empty': 'Поки що немає подорожей', + 'journey.emptyHint': 'Почніть документувати свою наступну поїздку', + 'journey.deleted': 'Подорож видалено', + 'journey.createError': 'Не вдалося створити подорож', + 'journey.deleteError': 'Не вдалося видалити подорож', + 'journey.deleteConfirmTitle': 'Видалити', + 'journey.deleteConfirmMessage': + 'Видалити «{title}»? Цю дію не можна скасувати.', + 'journey.deleteConfirmGeneric': 'Ви впевнені, що хочете це видалити?', + 'journey.notFound': 'Подорож не знайдено', + 'journey.photos': 'Фото', + 'journey.timelineEmpty': 'Поки що немає зупинок', + 'journey.timelineEmptyHint': 'Додайте позначку або напишіть запис у щоденник', + 'journey.status.draft': 'Чернетка', + 'journey.status.active': 'Активно', + 'journey.status.completed': 'Завершено', + 'journey.status.upcoming': 'Найближчим часом', + 'journey.status.archived': 'В архіві', + 'journey.checkin.add': 'Відзначитись', + 'journey.checkin.namePlaceholder': 'Назва місця', + 'journey.checkin.notesPlaceholder': 'Нотатки (необов’язково)', + 'journey.checkin.save': 'Зберегти', + 'journey.checkin.error': 'Не вдалося зберегти позначку', + 'journey.entry.add': 'Щоденник', + 'journey.entry.edit': 'Редагувати запис', + 'journey.entry.titlePlaceholder': 'Заголовок (необов’язково)', + 'journey.entry.bodyPlaceholder': 'Що сталося сьогодні?', + 'journey.entry.save': 'Зберегти', + 'journey.entry.error': 'Не вдалося зберегти запис', + 'journey.photo.add': 'Фото', + 'journey.photo.uploadError': 'Завантаження не вдалося', + 'journey.share.share': 'Поділитися', + 'journey.share.public': 'Публічний', + 'journey.share.linkCopied': 'Публічне посилання скопійовано', + 'journey.share.disabled': 'Публічний доступ вимкнено', + 'journey.editor.titlePlaceholder': 'Дайте назву цьому моменту...', + 'journey.editor.bodyPlaceholder': 'Розкажіть історію цього дня...', + 'journey.editor.placePlaceholder': 'Місце розташування (необов’язково)', + 'journey.editor.tagsPlaceholder': + 'Теги: прихована перлина, найкраща їжа, варто повернутися...', + 'journey.visibility.private': 'Приватний', + 'journey.visibility.shared': 'Спільний', + 'journey.visibility.public': 'Публічний', + 'journey.emptyState.title': 'Ваша історія починається тут', + 'journey.emptyState.subtitle': + 'Позначтесь у місці або напишіть перший запис у щоденник', + 'journey.frontpage.subtitle': + 'Перетворюйте поїздки на історії, які ви ніколи не забудете', + 'journey.frontpage.createJourney': 'Створити подорож', + 'journey.frontpage.activeJourney': 'Активна подорож', + 'journey.frontpage.allJourneys': 'Усі подорожі', + 'journey.frontpage.journeys': 'подорожей', + 'journey.frontpage.createNew': 'Створити нову подорож', + 'journey.frontpage.createNewSub': + 'Оберіть поїздки, пишіть історії, діліться пригодами', + 'journey.frontpage.live': 'В ефірі', + 'journey.frontpage.synced': 'Синхронізовано', + 'journey.frontpage.continueWriting': 'Продовжити писати', + 'journey.frontpage.updated': 'Оновлено {time}', + 'journey.frontpage.suggestionLabel': 'Поїздка щойно завершилась', + 'journey.frontpage.suggestionText': + 'Перетворіть {title} на подорож', + 'journey.frontpage.dismiss': 'Приховати', + 'journey.frontpage.journeyName': 'Назва подорожі', + 'journey.frontpage.namePlaceholder': 'наприклад, Південно-Східна Азія 2026', + 'journey.frontpage.selectTrips': 'Оберіть поїздки', + 'journey.frontpage.tripsSelected': 'поїздок вибрано', + 'journey.frontpage.trips': 'поїздок', + 'journey.frontpage.placesImported': 'місць буде імпортовано', + 'journey.frontpage.places': 'місць', + 'journey.detail.backToJourney': 'Назад до подорожі', + 'journey.detail.syncedWithTrips': 'Синхронізовано з поїздками', + 'journey.detail.addEntry': 'Додати запис', + 'journey.detail.newEntry': 'Новий запис', + 'journey.detail.editEntry': 'Редагувати запис', + 'journey.detail.noEntries': 'Поки що немає записів', + 'journey.detail.noEntriesHint': + 'Додайте подорож, щоб почати зі шаблонних записів', + 'journey.detail.noPhotos': 'Поки що немає фото', + 'journey.detail.noPhotosHint': + 'Завантажте фото до записів або перегляньте бібліотеку Immich/Synology', + 'journey.detail.journeyStats': 'Статистика подорожі', + 'journey.detail.syncedTrips': 'Синхронізовані поїздки', + 'journey.detail.noTripsLinked': 'Поки що немає прив’язаних поїздок', + 'journey.detail.contributors': 'Учасники', + 'journey.detail.journeyTab': 'Journey', + 'journey.detail.readMore': 'Читати далі', + 'journey.detail.prosCons': 'Плюси й мінуси', + 'journey.detail.photos': 'фото', + 'journey.detail.day': 'День {number}', + 'journey.detail.places': 'місць', + 'journey.stats.days': 'Днів', + 'journey.stats.cities': 'Міст', + 'journey.stats.entries': 'Записів', + 'journey.stats.photos': 'Фото', + 'journey.stats.places': 'Місць', + 'journey.skeletons.show': 'Показати пропозиції', + 'journey.skeletons.hide': 'Приховати пропозиції', + 'journey.verdict.lovedIt': 'Сподобалось', + 'journey.verdict.couldBeBetter': 'Могло бути краще', + 'journey.synced.places': 'місць', + 'journey.synced.synced': 'синхронізовано', + 'journey.editor.discardChangesConfirm': + 'У вас є незбережені зміни. Відмінити?', + 'journey.editor.uploadPhotos': 'Завантажити фото', + 'journey.editor.uploading': 'Завантаження...', + 'journey.editor.fromGallery': 'Зі галереї', + 'journey.editor.allPhotosAdded': 'Усі фото вже додано', + 'journey.editor.writeStory': 'Напишіть свою історію...', + 'journey.editor.prosCons': 'Плюси й мінуси', + 'journey.editor.pros': 'Плюси', + 'journey.editor.cons': 'Мінуси', + 'journey.editor.proPlaceholder': 'Щось чудове...', + 'journey.editor.conPlaceholder': 'Не дуже добре...', + 'journey.editor.addAnother': 'Додати ще', + 'journey.editor.date': 'Дата', + 'journey.editor.location': 'Місце розташування', + 'journey.editor.searchLocation': 'Пошук місця розташування...', + 'journey.editor.mood': 'Настрій', + 'journey.editor.weather': 'Погода', + 'journey.editor.photoFirst': '1-е', + 'journey.editor.makeFirst': 'Зробити першим', + 'journey.editor.searching': 'Пошук...', + 'journey.mood.amazing': 'Неймовірно', + 'journey.mood.good': 'Добре', + 'journey.mood.neutral': 'Нейтрально', + 'journey.mood.rough': 'Важко', + 'journey.weather.sunny': 'Сонячно', + 'journey.weather.partly': 'Перемінна хмарність', + 'journey.weather.cloudy': 'Хмарно', + 'journey.weather.rainy': 'Дощ', + 'journey.weather.stormy': 'Гроза', + 'journey.weather.cold': 'Сніжно', + 'journey.trips.linkTrip': 'Прив’язати поїздку', + 'journey.trips.searchTrip': 'Пошук поїздки', + 'journey.trips.searchPlaceholder': 'Назва поїздки або напрям...', + 'journey.trips.noTripsAvailable': 'Немає доступних поїздок', + 'journey.trips.link': 'Прив’язати', + 'journey.trips.tripLinked': 'Поїздка прив’язана', + 'journey.trips.linkFailed': 'Не вдалося прив’язати поїздку', + 'journey.trips.addTrip': 'Додати поїздку', + 'journey.trips.unlinkTrip': 'Відв’язати поїздку', + 'journey.trips.unlinkMessage': + 'Відв’язати «{title}»? Усі синхронізовані записи та фото з цієї поїздки будуть безповоротно видалені. Цю дію не можна скасувати.', + 'journey.trips.unlink': 'Відв’язати', + 'journey.trips.tripUnlinked': 'Поїздка відв’язана', + 'journey.trips.unlinkFailed': 'Не вдалося відв’язати поїздку', + 'journey.trips.noTripsLinkedSettings': 'Немає прив’язаних поїздок', + 'journey.contributors.invite': 'Запросити учасника', + 'journey.contributors.searchUser': 'Пошук користувача', + 'journey.contributors.searchPlaceholder': 'Ім’я користувача або email...', + 'journey.contributors.noUsers': 'Користувачів не знайдено', + 'journey.contributors.role': 'Роль', + 'journey.contributors.added': 'Учасника додано', + 'journey.contributors.addFailed': 'Не вдалося додати учасника', + 'journey.contributors.remove': 'Вилучити учасника', + 'journey.contributors.removeConfirm': 'Вилучити {username} з цієї подорожі?', + 'journey.contributors.removeFailed': 'Не вдалося вилучити учасника', + 'journey.contributors.removed': 'Учасника вилучено', + 'journey.share.publicShare': 'Публічний доступ', + 'journey.share.createLink': 'Створити посилання для спільного доступу', + 'journey.share.linkCreated': 'Посилання створено', + 'journey.share.createFailed': 'Не вдалося створити посилання', + 'journey.share.copy': 'Копіювати', + 'journey.share.copied': 'Скопійовано!', + 'journey.share.timeline': 'Хронологія', + 'journey.share.gallery': 'Галерея', + 'journey.share.map': 'Карта', + 'journey.share.removeLink': 'Видалити посилання', + 'journey.share.linkDeleted': 'Посилання видалено', + 'journey.share.deleteFailed': 'Не вдалося видалити', + 'journey.share.updateFailed': 'Не вдалося оновити', + 'journey.invite.role': 'Роль', + 'journey.invite.viewer': 'Спостерігач', + 'journey.invite.editor': 'Редактор', + 'journey.invite.invite': 'Запросити', + 'journey.invite.inviting': 'Запрошуємо...', + 'journey.settings.title': 'Налаштування подорожі', + 'journey.settings.coverImage': 'Обкладинка', + 'journey.settings.changeCover': 'Змінити обкладинку', + 'journey.settings.addCover': 'Додати обкладинку', + 'journey.settings.name': 'Назва', + 'journey.settings.subtitle': 'Підзаголовок', + 'journey.settings.subtitlePlaceholder': + 'наприклад Таїланд, В’єтнам і Камбоджа', + 'journey.settings.endJourney': 'Архівувати подорож', + 'journey.settings.reopenJourney': 'Відновити подорож', + 'journey.settings.archived': 'Подорож архівовано', + 'journey.settings.reopened': 'Подорож відновлено', + 'journey.settings.endDescription': + 'Приховує значок «В ефірі». Ви можете відновити у будь-який час.', + 'journey.settings.delete': 'Видалити', + 'journey.settings.deleteJourney': 'Видалити подорож', + 'journey.settings.deleteMessage': + 'Видалити «{title}»? Усі записи та фото будуть втрачені.', + 'journey.settings.saved': 'Налаштування збережено', + 'journey.settings.saveFailed': 'Не вдалося зберегти', + 'journey.settings.coverUpdated': 'Обкладинка оновлено', + 'journey.settings.coverFailed': 'Завантаження не вдалося', + 'journey.settings.failedToDelete': 'Не вдалося видалити', + 'journey.entries.deleteTitle': 'Видалити запис', + 'journey.photosUploaded': '{count} фото завантажено', + 'journey.photosAdded': '{count} фото додано', + 'journey.public.notFound': 'Не знайдено', + 'journey.public.notFoundMessage': + 'Ця подорож не існує або посилання застаріло.', + 'journey.public.readOnly': 'Тільки для читання · Публічна подорож', + 'journey.public.tagline': 'Інструмент планування та дослідження подорожей', + 'journey.public.sharedVia': 'Опубліковано через', + 'journey.public.madeWith': 'Зроблено з допомогою', + 'journey.pdf.journeyBook': 'Книга подорожі', + 'journey.pdf.madeWith': 'Зроблено з допомогою TREK', + 'journey.pdf.day': 'День', + 'journey.pdf.theEnd': 'Кінець', + 'journey.pdf.saveAsPdf': 'Зберегти як PDF', + 'journey.pdf.pages': 'сторінок', + 'journey.picker.tripPeriod': 'Період подорожі', + 'journey.picker.dateRange': 'Діапазон дат', + 'journey.picker.allPhotos': 'Усі фото', + 'journey.picker.albums': 'Альбоми', + 'journey.picker.selected': 'вибрано', + 'journey.picker.addTo': 'Додати до', + 'journey.picker.newGallery': 'Нова галерея', + 'journey.picker.selectAll': 'Вибрати все', + 'journey.picker.deselectAll': 'Скасувати вибір', + 'journey.picker.noAlbums': 'Альбомів не знайдено', + 'journey.picker.selectDate': 'Оберіть дату', + 'journey.picker.search': 'Пошук', + 'journey.editor.uploadingProgress': 'Завантаження {done}/{total}…', + 'journey.editor.uploadFailed': 'Не вдалося завантажити фото', + 'journey.editor.uploadPartialFailed': + '{failed} з {total} фото не вдалося завантажити — збережіть ще раз, щоб повторити', + 'journey.photosUploadFailed': 'Деякі фото не вдалося завантажити', +}; +export default journey; diff --git a/shared/src/i18n/uk/login.ts b/shared/src/i18n/uk/login.ts new file mode 100644 index 00000000..4138d964 --- /dev/null +++ b/shared/src/i18n/uk/login.ts @@ -0,0 +1,98 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': 'Помилка входу. Перевірте свої облікові дані.', + 'login.tagline': 'Ваші поїздки.\nВаш план.', + 'login.description': + 'Плануйте поїздки разом з інтерактивними картами, бюджетами та синхронізацією в реальному часі.', + 'login.features.maps': 'Інтерактивні карти', + 'login.features.mapsDesc': 'Google Places, маршрути та кластеризація', + 'login.features.realtime': 'Синхронізація в реальному часі', + 'login.features.realtimeDesc': 'Плануйте разом через WebSocket', + 'login.features.budget': 'Контроль бюджету', + 'login.features.budgetDesc': 'Категорії, графіки та витрати на особу', + 'login.features.collab': 'Спільна робота', + 'login.features.collabDesc': + 'Багатокористувацький режим зі спільними поїздками', + 'login.features.packing': 'Списки речей', + 'login.features.packingDesc': 'Категорії, прогрес та підказки', + 'login.features.bookings': 'Бронювання', + 'login.features.bookingsDesc': + 'Авіаквитки, готелі, ресторани та багато іншого', + 'login.features.files': 'Документи', + 'login.features.filesDesc': 'Завантажуйте та керуйте документами', + 'login.features.routes': 'Розумні маршрути', + 'login.features.routesDesc': 'Автооптимізація та експорт з Google Maps', + 'login.selfHosted': + 'Self-hosted · Відкритий код · Ваші дані залишаються у вас', + 'login.title': 'Вхід', + 'login.subtitle': 'З поверненням', + 'login.signingIn': 'Вхід…', + 'login.signIn': 'Увійти', + 'login.createAdmin': 'Створити акаунт адміністратора', + 'login.createAdminHint': 'Налаштуйте перший акаунт адміністратора для TREK.', + 'login.setNewPassword': 'Встановити новий пароль', + 'login.setNewPasswordHint': 'Ви повинні змінити пароль, перш ніж продовжити.', + 'login.createAccount': 'Створити акаунт', + 'login.createAccountHint': 'Зареєструйте новий акаунт.', + 'login.creating': 'Створення…', + 'login.noAccount': 'Немає акаунту?', + 'login.hasAccount': 'Вже є акаунт?', + 'login.register': 'Реєстрація', + 'login.emailPlaceholder': 'ваш@email.com', + 'login.username': 'Ім’я користувача', + 'login.oidc.registrationDisabled': + 'Реєстрацію вимкнено. Зверніться до адміністратора.', + 'login.oidc.noEmail': 'Провайдер не надав адресу електронної пошти.', + 'login.mfaTitle': 'Двофакторна автентифікація', + 'login.mfaSubtitle': 'Введіть 6-значний код з додатка-автентифікатора.', + 'login.mfaCodeLabel': 'Код підтвердження', + 'login.mfaCodeRequired': 'Введіть код із додатка-автентифікатора.', + 'login.mfaHint': + 'Відкрийте Google Authenticator, Authy або інший TOTP-додаток.', + 'login.mfaBack': '← Назад до входу', + 'login.mfaVerify': 'Підтвердити', + 'login.invalidInviteLink': 'Недійсне або прострочене посилання-запрошення', + 'login.oidcFailed': 'Помилка входу через OIDC', + 'login.usernameRequired': 'Ім’я користувача обов’язкове', + 'login.passwordMinLength': 'Пароль має містити щонайменше 8 символів', + 'login.forgotPassword': 'Забули пароль?', + 'login.forgotPasswordTitle': 'Скидання пароля', + 'login.forgotPasswordBody': + 'Введіть електронну пошту, з якою ви реєструвалися. Якщо акаунт існує — буде надіслано посилання для скидання.', + 'login.forgotPasswordSubmit': 'Надіслати посилання', + 'login.forgotPasswordSentTitle': 'Перевірте пошту', + 'login.forgotPasswordSentBody': + 'Якщо акаунт існує, посилання для скидання має бути вже відправлено. Воно дійсне 60 хвилин.', + 'login.forgotPasswordSmtpHintOff': + 'Зверніть увагу: адміністратор не налаштував SMTP, тому посилання для скидання буде записано в консоль сервера замість відправки електронною поштою.', + 'login.backToLogin': 'Повернутись до входу', + 'login.newPassword': 'Новий пароль', + 'login.confirmPassword': 'Підтвердіть новий пароль', + 'login.passwordsDontMatch': 'Паролі не співпадають', + 'login.mfaCode': 'Код 2FA', + 'login.resetPasswordTitle': 'Встановіть новий пароль', + 'login.resetPasswordBody': + 'Виберіть надійний пароль, який ви ще не використовували. Мінімум 8 символів.', + 'login.resetPasswordMfaBody': + 'Введіть код 2FA або резервний код, щоб завершити скидання.', + 'login.resetPasswordSubmit': 'Скинути пароль', + 'login.resetPasswordVerify': 'Перевірити та скинути', + 'login.resetPasswordSuccessTitle': 'Пароль оновлено', + 'login.resetPasswordSuccessBody': 'Тепер ви можете увійти з новим паролем.', + 'login.resetPasswordInvalidLink': 'Неправильне посилання для скидання', + 'login.resetPasswordInvalidLinkBody': + 'Посилання відсутнє або пошкоджене. Запитайте нове, щоб продовжити.', + 'login.resetPasswordFailed': + 'Скидання не вдалося. Можливо, термін дії посилання минув.', + 'login.oidc.tokenFailed': 'Автентифікація не вдалася.', + 'login.oidc.invalidState': 'Недійсна сесія. Спробуйте знову.', + 'login.demoFailed': 'Помилка демо-входу', + 'login.oidcSignIn': 'Увійти через {name}', + 'login.oidcOnly': + 'Вхід за паролем вимкнено. Використайте вашого SSO-провайдера для входу.', + 'login.oidcLoggedOut': + 'Ви вийшли з системи. Увійдіть знову через вашого SSO-провайдера.', + 'login.demoHint': 'Спробуйте демо — реєстрація не потрібна', +}; +export default login; diff --git a/shared/src/i18n/uk/map.ts b/shared/src/i18n/uk/map.ts new file mode 100644 index 00000000..39974d40 --- /dev/null +++ b/shared/src/i18n/uk/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': 'Сполучення', + 'map.showConnections': 'Показати маршрути бронювань', + 'map.hideConnections': 'Приховати маршрути бронювань', +}; +export default map; diff --git a/shared/src/i18n/uk/members.ts b/shared/src/i18n/uk/members.ts new file mode 100644 index 00000000..ac28a9fa --- /dev/null +++ b/shared/src/i18n/uk/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': 'Поділитися поїздкою', + 'members.inviteUser': 'Запросити користувача', + 'members.selectUser': 'Оберіть користувача…', + 'members.invite': 'Запросити', + 'members.allHaveAccess': 'У всіх користувачів вже є доступ.', + 'members.access': 'Доступ', + 'members.person': 'особа', + 'members.persons': 'осіб', + 'members.you': 'ви', + 'members.owner': 'Власник', + 'members.leaveTrip': 'Покинути поїздку', + 'members.removeAccess': 'Відкликати доступ', + 'members.confirmLeave': 'Покинути поїздку? Ви втратите доступ.', + 'members.confirmRemove': 'Відкликати доступ у цього користувача?', + 'members.loadError': 'Не вдалося завантажити учасників', + 'members.added': 'додано', + 'members.addError': 'Помилка додавання', + 'members.removed': 'Учасник видалений', + 'members.removeError': 'Помилка видалення', +}; +export default members; diff --git a/shared/src/i18n/uk/memories.ts b/shared/src/i18n/uk/memories.ts new file mode 100644 index 00000000..40f50b43 --- /dev/null +++ b/shared/src/i18n/uk/memories.ts @@ -0,0 +1,81 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': 'Фото', + 'memories.notConnected': 'Immich не підключено', + 'memories.notConnectedHint': + 'Підключіть Immich у налаштуваннях, щоб бачити фотографії з поїздок.', + 'memories.notConnectedMultipleHint': + 'Підключіть одного з цих фотопровайдерів: {provider_names} у Налаштуваннях, щоб додавати фотографії до цієї подорожі.', + 'memories.noDates': 'Додайте дати поїздки, щоб завантажити фотографії.', + 'memories.noPhotos': 'Фотографій не знайдено', + 'memories.noPhotosHint': 'В Immich немає фотографій за період цієї поїздки.', + 'memories.photosFound': 'фото', + 'memories.fromOthers': 'від інших', + 'memories.sharePhotos': 'Поділитися фото', + 'memories.sharing': 'Спільний доступ', + 'memories.reviewTitle': 'Перевірте свої фото', + 'memories.reviewHint': + 'Натисніть на фото, щоб виключити його зі спільного доступу.', + 'memories.shareCount': 'Поділитися ({count} фото)', + 'memories.providerUrl': 'URL сервера', + 'memories.providerApiKey': 'API-ключ', + 'memories.providerUsername': 'Ім’я користувача', + 'memories.providerPassword': 'Пароль', + 'memories.providerOTP': 'Код MFA (якщо увімкнено)', + 'memories.skipSSLVerification': 'Пропустити перевірку SSL-сертифіката', + 'memories.immichAutoUpload': + 'Дублювати фото journey в Immich при завантаженні', + 'memories.providerUrlHintSynology': + 'Включіть шлях додатку Photos в URL, наприклад https://nas:5001/photo', + 'memories.testConnection': 'Перевірити підключення', + 'memories.testShort': 'Перевірити', + 'memories.testFirst': 'Спершу перевірте підключення', + 'memories.connected': 'Підключено', + 'memories.disconnected': 'Не підключено', + 'memories.connectionSuccess': 'Підключення до Immich встановлено', + 'memories.connectionError': 'Не вдалося підключитися до Immich', + 'memories.saved': 'Налаштування {provider_name} збережено', + 'memories.providerDisconnectedBanner': + 'З’єднання з {provider_name} втрачено. Перепідключіться в Налаштуваннях для перегляду фото.', + 'memories.saveError': 'Не вдалося зберегти налаштування {provider_name}', + 'memories.oldest': 'Спочатку старі', + 'memories.newest': 'Спочатку нові', + 'memories.allLocations': 'Усі місця', + 'memories.addPhotos': 'Додати фото', + 'memories.linkAlbum': 'Прив’язати альбом', + 'memories.selectAlbum': 'Вибрати альбом Immich', + 'memories.selectAlbumMultiple': 'Вибрати альбом', + 'memories.noAlbums': 'Альбомів не знайдено', + 'memories.syncAlbum': 'Синхронізувати', + 'memories.unlinkAlbum': 'Відв’язати', + 'memories.photos': 'фото', + 'memories.selectPhotos': 'Вибрати фото з Immich', + 'memories.selectPhotosMultiple': 'Вибрати фотографії', + 'memories.selectHint': 'Натисніть на фото, щоб вибрати їх.', + 'memories.selected': 'вибрано', + 'memories.addSelected': 'Додати {count} фото', + 'memories.alreadyAdded': 'Додано', + 'memories.private': 'Приватне', + 'memories.stopSharing': 'Припинити доступ', + 'memories.tripDates': 'Дати поїздки', + 'memories.allPhotos': 'Усі фото', + 'memories.confirmShareTitle': 'Поділитися з учасниками поїздки?', + 'memories.confirmShareHint': + '{count} фото стануть видимі всім учасникам цієї поїздки. Ви зможете зробити окремі фото приватними пізніше.', + 'memories.confirmShareButton': 'Поділитися фото', + 'memories.error.loadAlbums': 'Не вдалося завантажити альбоми', + 'memories.error.linkAlbum': "Не вдалося прив'язати альбом", + 'memories.error.unlinkAlbum': "Не вдалося від'вязати альбом", + 'memories.error.syncAlbum': 'Не вдалося синхронізувати альбом', + 'memories.error.loadPhotos': 'Не вдалося завантажити фотографії', + 'memories.error.addPhotos': 'Не вдалося додати фотографії', + 'memories.error.removePhoto': 'Не вдалося видалити фотографію', + 'memories.error.toggleSharing': 'Не вдалося оновити налаштування доступу', + 'memories.saveRouteNotConfigured': + 'Маршрут збереження не налаштовано для цього провайдера', + 'memories.testRouteNotConfigured': + 'Маршрут тестування не налаштовано для цього провайдера', + 'memories.fillRequiredFields': 'Будь ласка, заповніть усі обов’язкові поля', +}; +export default memories; diff --git a/shared/src/i18n/uk/nav.ts b/shared/src/i18n/uk/nav.ts new file mode 100644 index 00000000..90ddf147 --- /dev/null +++ b/shared/src/i18n/uk/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': 'Поїздка', + 'nav.share': 'Поділитися', + 'nav.settings': 'Налаштування', + 'nav.admin': 'Адмін', + 'nav.logout': 'Вийти', + 'nav.lightMode': 'Світла тема', + 'nav.darkMode': 'Темна тема', + 'nav.autoMode': 'Авто', + 'nav.administrator': 'Адміністратор', + 'nav.myTrips': 'Мої поїздки', + 'nav.profile': 'Профіль', + 'nav.bottomSettings': 'Налаштування', + 'nav.bottomAdmin': 'Адміністрування', + 'nav.bottomLogout': 'Вийти', + 'nav.bottomAdminBadge': 'Адмін', +}; +export default nav; diff --git a/shared/src/i18n/uk/notif.ts b/shared/src/i18n/uk/notif.ts new file mode 100644 index 00000000..d84d7f2d --- /dev/null +++ b/shared/src/i18n/uk/notif.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[Тест] Сповіщення', + 'notif.test.simple.text': 'Це просте тестове сповіщення.', + 'notif.test.boolean.text': 'Ви приймаєте це тестове сповіщення?', + 'notif.test.navigate.text': + 'Натисніть нижче, щоб перейти на панель управління.', + 'notif.trip_invite.title': 'Запрошення до поїздки', + 'notif.trip_invite.text': '{actor} запросив вас до {trip}', + 'notif.booking_change.title': 'Бронювання оновлено', + 'notif.booking_change.text': '{actor} оновив бронювання в {trip}', + 'notif.trip_reminder.title': 'Нагадування про поїздку', + 'notif.trip_reminder.text': 'Ваша поїздка {trip} скоро почнеться!', + 'notif.todo_due.title': 'Завдання до терміну', + 'notif.todo_due.text': '{todo} у {trip} — термін {due}', + 'notif.vacay_invite.title': 'Запрошення Vacay Fusion', + 'notif.vacay_invite.text': '{actor} запрошує вас об’єднати плани відпустки', + 'notif.photos_shared.title': 'Фото опубліковано', + 'notif.photos_shared.text': '{actor} поділився {count} фото у {trip}', + 'notif.collab_message.title': 'Нове повідомлення', + 'notif.collab_message.text': '{actor} надіслав повідомлення в {trip}', + 'notif.packing_tagged.title': 'Завдання для пакування', + 'notif.packing_tagged.text': '{actor} призначив вас у {category} у {trip}', + 'notif.version_available.title': 'Доступна нова версія', + 'notif.version_available.text': 'TREK {version} тепер доступний', + 'notif.action.view_trip': 'Відкрити поїздку', + 'notif.action.view_collab': 'Відкрити повідомлення', + 'notif.action.view_packing': 'Відкрити пакування', + 'notif.action.view_photos': 'Відкрити фото', + 'notif.action.view_vacay': 'Відкрити Vacay', + 'notif.action.view_admin': 'Перейти в адмін', + 'notif.action.view': 'Відкрити', + 'notif.action.accept': 'Прийняти', + 'notif.action.decline': 'Відхилити', + 'notif.generic.title': 'Сповіщення', + 'notif.generic.text': 'У вас нове сповіщення', + 'notif.dev.unknown_event.title': '[DEV] Невідома подія', + 'notif.dev.unknown_event.text': + 'Тип події "{event}" не зареєстровано в EVENT_NOTIFICATION_CONFIG', +}; +export default notif; diff --git a/shared/src/i18n/uk/notifications.ts b/shared/src/i18n/uk/notifications.ts new file mode 100644 index 00000000..28a882d5 --- /dev/null +++ b/shared/src/i18n/uk/notifications.ts @@ -0,0 +1,37 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': 'Сповіщення', + 'notifications.markAllRead': 'Позначити всі прочитаними', + 'notifications.deleteAll': 'Видалити всі', + 'notifications.showAll': 'Показати всі сповіщення', + 'notifications.empty': 'Немає сповіщень', + 'notifications.emptyDescription': 'Ви в курсі всіх подій!', + 'notifications.all': 'Усі', + 'notifications.unreadOnly': 'Непрочитані', + 'notifications.markRead': 'Позначити як прочитане', + 'notifications.markUnread': 'Позначити як непрочитане', + 'notifications.delete': 'Видалити', + 'notifications.system': 'Система', + 'notifications.synologySessionCleared.title': 'Synology Photos відключено', + 'notifications.synologySessionCleared.text': + 'Ваш сервер або акаунт змінено — перейдіть у Налаштування, щоб перевірити з’єднання знову.', + 'notifications.test.title': 'Тестове сповіщення від {actor}', + 'notifications.test.text': 'Це просте тестове сповіщення.', + 'notifications.test.booleanTitle': '{actor} запрошує підтвердження', + 'notifications.test.booleanText': 'Тестове сповіщення з вибором.', + 'notifications.test.accept': 'Підтвердити', + 'notifications.test.decline': 'Відхилити', + 'notifications.test.navigateTitle': 'Подивіться на це', + 'notifications.test.navigateText': 'Тестове сповіщення з переходом.', + 'notifications.test.goThere': 'Перейти', + 'notifications.test.adminTitle': 'Розсилка адміністратора', + 'notifications.test.adminText': + '{actor} надіслав тестове сповіщення всім адміністраторам.', + 'notifications.test.tripTitle': '{actor} написав у вашій поїздці', + 'notifications.test.tripText': 'Тестове сповіщення для поїздки "{trip}".', + 'notifications.versionAvailable.title': 'Доступне оновлення', + 'notifications.versionAvailable.text': 'TREK {version} тепер доступний.', + 'notifications.versionAvailable.button': 'Докладніше', +}; +export default notifications; diff --git a/shared/src/i18n/uk/oauth.ts b/shared/src/i18n/uk/oauth.ts new file mode 100644 index 00000000..18a51534 --- /dev/null +++ b/shared/src/i18n/uk/oauth.ts @@ -0,0 +1,99 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': 'Поїздки', + 'oauth.scope.group.places': 'Місця', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': 'Речі', + 'oauth.scope.group.todos': 'Задачі', + 'oauth.scope.group.budget': 'Бюджет', + 'oauth.scope.group.reservations': 'Бронювання', + 'oauth.scope.group.collab': 'Співпраця', + 'oauth.scope.group.notifications': 'Сповіщення', + 'oauth.scope.group.vacay': 'Відпустка', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': 'Погода', + 'oauth.scope.group.journey': 'Подорожі', + 'oauth.scope.trips:read.label': 'Перегляд поїздок і маршрутів', + 'oauth.scope.trips:read.description': + 'Читання поїздок, днів, нотаток і учасників', + 'oauth.scope.trips:write.label': 'Редагування поїздок і маршрутів', + 'oauth.scope.trips:write.description': + 'Створення та оновлення поїздок, днів, нотаток і керування учасниками', + 'oauth.scope.trips:delete.label': 'Видалення поїздок', + 'oauth.scope.trips:delete.description': + 'Безповоротне видалення поїздок — ця дія незворотна', + 'oauth.scope.trips:share.label': 'Керування посиланнями спільного доступу', + 'oauth.scope.trips:share.description': + 'Створення, оновлення та відкликання публічних посилань на поїздки', + 'oauth.scope.places:read.label': 'Перегляд місць і даних карти', + 'oauth.scope.places:read.description': + 'Читання місць, призначень за днями, тегів і категорій', + 'oauth.scope.places:write.label': 'Керування місцями', + 'oauth.scope.places:write.description': + 'Створення, оновлення та видалення місць, призначень і тегів', + 'oauth.scope.atlas:read.label': 'Перегляд Atlas', + 'oauth.scope.atlas:read.description': + 'Читання відвіданих країн, регіонів і списку бажань', + 'oauth.scope.atlas:write.label': 'Керування Atlas', + 'oauth.scope.atlas:write.description': + 'Позначення відвіданих країн і регіонів, керування списком бажань', + 'oauth.scope.packing:read.label': 'Перегляд списків речей', + 'oauth.scope.packing:read.description': + 'Читання речей, сумок і призначень категорій', + 'oauth.scope.packing:write.label': 'Керування списками речей', + 'oauth.scope.packing:write.description': + 'Додавання, оновлення, видалення, позначення і переставлення речей та сумок', + 'oauth.scope.todos:read.label': 'Перегляд списків задач', + 'oauth.scope.todos:read.description': + 'Читання задач поїздки і призначень категорій', + 'oauth.scope.todos:write.label': 'Керування списками задач', + 'oauth.scope.todos:write.description': + 'Створення, оновлення, позначення, видалення і переставлення задач', + 'oauth.scope.budget:read.label': 'Перегляд бюджету', + 'oauth.scope.budget:read.description': + 'Читання статей бюджету і розбивки витрат', + 'oauth.scope.budget:write.label': 'Керування бюджетом', + 'oauth.scope.budget:write.description': + 'Створення, оновлення і видалення статей бюджету', + 'oauth.scope.reservations:read.label': 'Перегляд бронювань', + 'oauth.scope.reservations:read.description': + 'Читання бронювань і відомостей про проживання', + 'oauth.scope.reservations:write.label': 'Керування бронюваннями', + 'oauth.scope.reservations:write.description': + 'Створення, оновлення, видалення і переставлення бронювань', + 'oauth.scope.collab:read.label': 'Перегляд співпраці', + 'oauth.scope.collab:read.description': + 'Читання спільних нотаток, опитувань і повідомлень', + 'oauth.scope.collab:write.label': 'Керування співпрацею', + 'oauth.scope.collab:write.description': + 'Створення, оновлення і видалення нотаток, опитувань і повідомлень', + 'oauth.scope.notifications:read.label': 'Перегляд сповіщень', + 'oauth.scope.notifications:read.description': + 'Читання сповіщень у додатку та кількості непрочитаних', + 'oauth.scope.notifications:write.label': 'Керування сповіщеннями', + 'oauth.scope.notifications:write.description': + 'Позначення сповіщень як прочитаних і відповіді на них', + 'oauth.scope.vacay:read.label': 'Перегляд планів відпустки', + 'oauth.scope.vacay:read.description': + 'Читання даних планування відпустки, записів і статистики', + 'oauth.scope.vacay:write.label': 'Керування планами відпустки', + 'oauth.scope.vacay:write.description': + 'Створення і керування записами відпустки, святами і командними планами', + 'oauth.scope.geo:read.label': 'Карти і геокодування', + 'oauth.scope.geo:read.description': + 'Пошук місць, розв’язання URL карт і зворотне геокодування координат', + 'oauth.scope.weather:read.label': 'Прогнози погоди', + 'oauth.scope.weather:read.description': + 'Отримання прогнозів погоди для місць і дат поїздки', + 'oauth.scope.journey:read.label': 'Перегляд подорожей', + 'oauth.scope.journey:read.description': + 'Читання подорожей, записів і списку учасників', + 'oauth.scope.journey:write.label': 'Керування подорожами', + 'oauth.scope.journey:write.description': + 'Створення, оновлення і видалення подорожей та їх записів', + 'oauth.scope.journey:share.label': 'Керування посиланнями на подорожі', + 'oauth.scope.journey:share.description': + 'Створення, оновлення і відкликання публічних посилань на подорожі', +}; +export default oauth; diff --git a/shared/src/i18n/uk/packing.ts b/shared/src/i18n/uk/packing.ts new file mode 100644 index 00000000..416f16f3 --- /dev/null +++ b/shared/src/i18n/uk/packing.ts @@ -0,0 +1,186 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': 'Список речей', + 'packing.empty': 'Список речей порожній', + 'packing.import': 'Імпорт', + 'packing.importTitle': 'Імпорт списку речей', + 'packing.importHint': + 'Один предмет у рядок. Категорію та кількість — через кому, крапку з комою або табуляцію: Назва, Категорія, Кількість', + 'packing.importPlaceholder': + 'Зубна щітка\nСонцезахисний крем, Гігієна\nФутболки, Одяг, 5\nПаспорт, Документи', + 'packing.importCsv': 'Завантажити CSV/TXT', + 'packing.importAction': 'Імпортувати {count}', + 'packing.importSuccess': '{count} предметів імпортовано', + 'packing.importError': 'Помилка імпорту', + 'packing.importEmpty': 'Немає предметів для імпорту', + 'packing.progress': '{packed} з {total} зібрано ({percent}%)', + 'packing.clearChecked': 'Видалити {count} позначених', + 'packing.clearCheckedShort': 'Видалити {count}', + 'packing.suggestions': 'Підказки', + 'packing.suggestionsTitle': 'Додати підказки', + 'packing.allSuggested': 'Усі підказки додано', + 'packing.allPacked': 'Усе зібрано!', + 'packing.addPlaceholder': 'Додати річ...', + 'packing.categoryPlaceholder': 'Категорія...', + 'packing.filterAll': 'Усі', + 'packing.filterOpen': 'Не зібрано', + 'packing.filterDone': 'Зібрано', + 'packing.emptyTitle': 'Список речей порожній', + 'packing.emptyHint': 'Додайте речі або використайте підказки', + 'packing.emptyFiltered': 'Немає речей, що відповідають фільтру', + 'packing.menuRename': 'Перейменувати', + 'packing.menuCheckAll': 'Позначити все', + 'packing.menuUncheckAll': 'Зняти позначки', + 'packing.menuDeleteCat': 'Видалити категорію', + 'packing.addItem': 'Додати річ', + 'packing.addItemPlaceholder': 'Назва...', + 'packing.addCategory': 'Додати категорію', + 'packing.newCategoryPlaceholder': 'Назва категорії (наприклад, Одяг)', + 'packing.applyTemplate': 'Застосувати шаблон', + 'packing.template': 'Шаблон', + 'packing.templateApplied': '{count} речей додано з шаблону', + 'packing.templateError': 'Помилка застосування шаблону', + 'packing.saveAsTemplate': 'Зберегти як шаблон', + 'packing.templateName': 'Назва шаблону', + 'packing.templateSaved': 'Список речей збережено як шаблон', + 'packing.noMembers': 'Немає учасників', + 'packing.bags': 'Багаж', + 'packing.noBag': 'Не призначено', + 'packing.totalWeight': 'Загальна вага', + 'packing.bagName': 'Назва...', + 'packing.addBag': 'Додати багаж', + 'packing.changeCategory': 'Змінити категорію', + 'packing.confirm.clearChecked': + 'Ви впевнені, що хочете видалити {count} позначених речей?', + 'packing.confirm.deleteCat': + 'Ви впевнені, що хочете видалити категорію «{name}» з {count} речами?', + 'packing.defaultCategory': 'Інше', + 'packing.toast.saveError': 'Помилка збереження', + 'packing.toast.deleteError': 'Помилка видалення', + 'packing.toast.renameError': 'Помилка перейменування', + 'packing.toast.addError': 'Помилка додавання', + 'packing.suggestions.items': [ + { + name: 'Паспорт', + category: 'Документи', + }, + { + name: 'Посвідчення особи', + category: 'Документи', + }, + { + name: 'Страхування', + category: 'Документи', + }, + { + name: 'Авіаквитки', + category: 'Документи', + }, + { + name: 'Банківська картка', + category: 'Фінанси', + }, + { + name: 'Готівка', + category: 'Фінанси', + }, + { + name: 'Віза', + category: 'Документи', + }, + { + name: 'Футболки', + category: 'Одяг', + }, + { + name: 'Штани', + category: 'Одяг', + }, + { + name: 'Нижня білизна', + category: 'Одяг', + }, + { + name: 'Шкарпетки', + category: 'Одяг', + }, + { + name: 'Куртка', + category: 'Одяг', + }, + { + name: 'Піжама', + category: 'Одяг', + }, + { + name: 'Купальник', + category: 'Одяг', + }, + { + name: 'Дощовик', + category: 'Одяг', + }, + { + name: 'Зручне взуття', + category: 'Одяг', + }, + { + name: 'Зубна щітка', + category: 'Гігієна', + }, + { + name: 'Зубна паста', + category: 'Гігієна', + }, + { + name: 'Шампунь', + category: 'Гігієна', + }, + { + name: 'Дезодорант', + category: 'Гігієна', + }, + { + name: 'Сонцезахисний крем', + category: 'Гігієна', + }, + { + name: 'Пасок', + category: 'Гігієна', + }, + { + name: 'Зарядний пристрій', + category: 'Електроніка', + }, + { + name: 'Зовнішній акумулятор', + category: 'Електроніка', + }, + { + name: 'Навушники', + category: 'Електроніка', + }, + { + name: 'Адаптер для розеток', + category: 'Електроніка', + }, + { + name: 'Фотоапарат', + category: 'Електроніка', + }, + { + name: 'Знеболювальні', + category: 'Здоров’я', + }, + { + name: 'Пластирі', + category: 'Здоров’я', + }, + { + name: 'Антисептик', + category: 'Здоров’я', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/uk/pdf.ts b/shared/src/i18n/uk/pdf.ts new file mode 100644 index 00000000..30073b81 --- /dev/null +++ b/shared/src/i18n/uk/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': 'План поїздки', + 'pdf.planned': 'Заплановано', + 'pdf.costLabel': 'Вартість EUR', + 'pdf.preview': 'Попередній перегляд PDF', + 'pdf.saveAsPdf': 'Зберегти як PDF', +}; +export default pdf; diff --git a/shared/src/i18n/uk/perm.ts b/shared/src/i18n/uk/perm.ts new file mode 100644 index 00000000..3c484c94 --- /dev/null +++ b/shared/src/i18n/uk/perm.ts @@ -0,0 +1,63 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': 'Налаштування дозволів', + 'perm.subtitle': 'Керуйте тим, хто може виконувати дії в додатку', + 'perm.saved': 'Налаштування дозволів збережено', + 'perm.resetDefaults': 'Скинути за замовчуванням', + 'perm.customized': 'змінено', + 'perm.level.admin': 'Тільки адміністратор', + 'perm.level.tripOwner': 'Власник поїздки', + 'perm.level.tripMember': 'Учасники поїздки', + 'perm.level.everybody': 'Усі', + 'perm.cat.trip': 'Керування поїздками', + 'perm.cat.members': 'Керування учасниками', + 'perm.cat.files': 'Файли', + 'perm.cat.content': 'Контент та розклад', + 'perm.cat.extras': 'Бюджет, збори та співпраця', + 'perm.action.trip_create': 'Створювати поїздки', + 'perm.action.trip_edit': 'Редагувати деталі поїздки', + 'perm.action.trip_delete': 'Видаляти поїздки', + 'perm.action.trip_archive': 'Архівувати / розархівувати поїздки', + 'perm.action.trip_cover_upload': 'Завантажувати обкладинку', + 'perm.action.member_manage': 'Додавати / видаляти учасників', + 'perm.action.file_upload': 'Завантажувати файли', + 'perm.action.file_edit': 'Редагувати метадані файлів', + 'perm.action.file_delete': 'Видаляти файли', + 'perm.action.place_edit': 'Додавати / редагувати / видаляти місця', + 'perm.action.day_edit': 'Редагувати дні, нотатки та призначення', + 'perm.action.reservation_edit': 'Керувати бронюваннями', + 'perm.action.budget_edit': 'Керувати бюджетом', + 'perm.action.packing_edit': 'Керувати списками речей', + 'perm.action.collab_edit': 'Співпраця (нотатки, опитування, чат)', + 'perm.action.share_manage': 'Керувати посиланнями для обміну', + 'perm.actionHint.trip_create': 'Хто може створювати нові поїздки', + 'perm.actionHint.trip_edit': + 'Хто може змінювати назву, дати, опис і валюту поїздки', + 'perm.actionHint.trip_delete': 'Хто може безповоротно видалити поїздку', + 'perm.actionHint.trip_archive': + 'Хто може архівувати або розархівувати поїздку', + 'perm.actionHint.trip_cover_upload': + 'Хто може завантажувати або змінювати обкладинку', + 'perm.actionHint.member_manage': + 'Хто може запрошувати або видаляти учасників поїздки', + 'perm.actionHint.file_upload': 'Хто може завантажувати файли до поїздки', + 'perm.actionHint.file_edit': 'Хто може редагувати описи та посилання файлів', + 'perm.actionHint.file_delete': + 'Хто може переміщувати файли до кошика або безповоротно видаляти', + 'perm.actionHint.place_edit': + 'Хто може додавати, редагувати або видаляти місця', + 'perm.actionHint.day_edit': + 'Хто може редагувати дні, нотатки до днів та призначення місць', + 'perm.actionHint.reservation_edit': + 'Хто може створювати, редагувати або видаляти бронювання', + 'perm.actionHint.budget_edit': + 'Хто може створювати, редагувати або видаляти статті бюджету', + 'perm.actionHint.packing_edit': + 'Хто може керувати речами для зборів і сумками', + 'perm.actionHint.collab_edit': + 'Хто може створювати нотатки, опитування та надсилати повідомлення', + 'perm.actionHint.share_manage': + 'Хто може створювати або видаляти публічні посилання для обміну', +}; +export default perm; diff --git a/shared/src/i18n/uk/photos.ts b/shared/src/i18n/uk/photos.ts new file mode 100644 index 00000000..94c6904c --- /dev/null +++ b/shared/src/i18n/uk/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': 'Фотографії', + 'photos.subtitle': '{count} фото для {trip}', + 'photos.dropHere': 'Перетягніть фото сюди...', + 'photos.dropHereActive': 'Перетягніть фото сюди', + 'photos.captionForAll': 'Підпис (для всіх)', + 'photos.captionPlaceholder': 'Необов’язковий підпис...', + 'photos.addCaption': 'Додати підпис...', + 'photos.allDays': 'Усі дні', + 'photos.noPhotos': 'Фото поки немає', + 'photos.uploadHint': 'Завантажте фото з поїздки', + 'photos.clickToSelect': 'або натисніть для вибору', + 'photos.linkPlace': 'Прив’язати місце', + 'photos.noPlace': 'Без місця', + 'photos.uploadN': '{n} фото завантажено', + 'photos.linkDay': 'Прив’язати день', + 'photos.noDay': 'Немає дня', + 'photos.dayLabel': 'День {number}', + 'photos.photoSelected': 'Фото вибрано', + 'photos.photosSelected': 'Фото вибрано', + 'photos.fileTypeHint': 'JPG, PNG, WebP · макс. 10 МБ · до 30 фото', +}; +export default photos; diff --git a/shared/src/i18n/uk/places.ts b/shared/src/i18n/uk/places.ts new file mode 100644 index 00000000..e00619db --- /dev/null +++ b/shared/src/i18n/uk/places.ts @@ -0,0 +1,92 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': 'Додати місце/активність', + 'places.importFile': 'Імпортувати файл', + 'places.sidebarDrop': 'Відпустіть для імпорту', + 'places.importFileHint': + 'Імпортуйте файли .gpx, .kml або .kmz із інструментів, таких як Google My Maps, Google Earth або GPS-трекер.', + 'places.importFileDropHere': + 'Натисніть, щоб вибрати файл, або перетягніть його сюди', + 'places.importFileDropActive': 'Відпустіть файл для вибору', + 'places.importFileUnsupported': + 'Непідтримуваний тип файлу. Використовуйте .gpx, .kml або .kmz.', + 'places.importFileTooLarge': + 'Файл занадто великий. Максимальний розмір завантаження — {maxMb} МБ.', + 'places.importFileError': 'Помилка імпорту', + 'places.importAllSkipped': 'Усі місця вже були в подорожі.', + 'places.gpxImported': '{count} місць імпортовано з GPX', + 'places.gpxImportTypes': 'Що імпортувати?', + 'places.gpxImportWaypoints': 'Путеві точки', + 'places.gpxImportRoutes': 'Маршрути', + 'places.gpxImportTracks': 'Треки (з геометрією маршруту)', + 'places.gpxImportNoneSelected': 'Оберіть щонайменше один тип для імпорту.', + 'places.kmlImportTypes': 'Що ви хочете імпортувати?', + 'places.kmlImportPoints': 'Точки (Placemark)', + 'places.kmlImportPaths': 'Маршрути (LineStrings)', + 'places.kmlImportNoneSelected': 'Виберіть щонайменше один тип.', + 'places.selectionCount': '{count} вибрано', + 'places.deleteSelected': 'Видалити вибране', + 'places.kmlKmzImported': '{count} місць імпортовано з KMZ/KML', + 'places.urlResolved': 'Місце імпортовано з URL', + 'places.importList': 'Імпорт списку', + 'places.kmlKmzSummaryValues': + 'Позначки: {total} • Імпортовано: {created} • Пропущено: {skipped}', + 'places.importGoogleList': 'Список Google', + 'places.importNaverList': 'Список Naver', + 'places.googleListHint': + 'Вставте посилання на спільний список Google Maps для імпорту всіх місць.', + 'places.googleListImported': '{count} місць імпортовано з "{list}"', + 'places.googleListError': 'Не вдалося імпортувати список Google Maps', + 'places.naverListHint': + 'Вставте посилання на спільний список Naver Maps для імпорту всіх місць.', + 'places.naverListImported': '{count} місць імпортовано з "{list}"', + 'places.naverListError': 'Не вдалося імпортувати список Naver Maps', + 'places.viewDetails': 'Деталі', + 'places.assignToDay': 'Додати на який день?', + 'places.all': 'Усі', + 'places.unplanned': 'Незаплановані', + 'places.filterTracks': 'Треки', + 'places.search': 'Пошук місць...', + 'places.allCategories': 'Всі категорії', + 'places.categoriesSelected': 'категорій', + 'places.clearFilter': 'Скинути фільтр', + 'places.count': '{count} місць', + 'places.countSingular': '1 місце', + 'places.allPlanned': 'Усі місця заплановані', + 'places.noneFound': 'Місця не знайдені', + 'places.editPlace': 'Редагувати місце', + 'places.formName': 'Назва', + 'places.formNamePlaceholder': 'наприклад, Ейфелева вежа', + 'places.formDescription': 'Опис', + 'places.formDescriptionPlaceholder': 'Короткий опис...', + 'places.formAddress': 'Адреса', + 'places.formAddressPlaceholder': 'Вулиця, місто, країна', + 'places.formLat': 'Широта (наприклад, 48.8566)', + 'places.formLng': 'Довгота (наприклад, 2.3522)', + 'places.formCategory': 'Категорія', + 'places.noCategory': 'Без категорії', + 'places.categoryNamePlaceholder': 'Назва категорії', + 'places.formTime': 'Час', + 'places.startTime': 'Початок', + 'places.endTime': 'Кінець', + 'places.endTimeBeforeStart': 'Час закінчення раніше за час початку', + 'places.timeCollision': 'Перетин за часом із:', + 'places.formWebsite': 'Веб-сайт', + 'places.formNotes': 'Нотатки', + 'places.formNotesPlaceholder': 'Особисті нотатки...', + 'places.formReservation': 'Бронювання', + 'places.reservationNotesPlaceholder': + 'Нотатки про бронювання, номер підтвердження...', + 'places.mapsSearchPlaceholder': 'Пошук місць...', + 'places.mapsSearchError': 'Помилка пошуку місць.', + 'places.loadingDetails': 'Завантаження даних про місце…', + 'places.osmHint': + 'Пошук через OpenStreetMap (без фото, годин роботи та рейтингів). Додайте API-ключ Google у налаштуваннях для повної інформації.', + 'places.osmActive': + 'Пошук через OpenStreetMap (без фото, рейтингів та годин роботи). Додайте API-ключ Google у налаштуваннях для розширених даних.', + 'places.categoryCreateError': 'Не вдалося створити категорію', + 'places.nameRequired': 'Введіть назву', + 'places.saveError': 'Помилка збереження', +}; +export default places; diff --git a/shared/src/i18n/uk/planner.ts b/shared/src/i18n/uk/planner.ts new file mode 100644 index 00000000..14d5743e --- /dev/null +++ b/shared/src/i18n/uk/planner.ts @@ -0,0 +1,68 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': 'Місця', + 'planner.bookings': 'Бронювання', + 'planner.packingList': 'Список речей', + 'planner.documents': 'Документи', + 'planner.dayPlan': 'План дня', + 'planner.reservations': 'Бронювання', + 'planner.minTwoPlaces': 'Потрібно мінімум 2 місця з координатами', + 'planner.noGeoPlaces': 'Немає місць з координатами', + 'planner.routeCalculated': 'Маршрут розраховано', + 'planner.routeCalcFailed': 'Не вдалося розрахувати маршрут', + 'planner.routeError': 'Помилка розрахунку маршруту', + 'planner.icsExportFailed': 'Не вдалося експортувати ICS', + 'planner.routeOptimized': 'Маршрут оптимізовано', + 'planner.reservationUpdated': 'Бронювання оновлено', + 'planner.reservationAdded': 'Бронювання додано', + 'planner.confirmDeleteReservation': 'Видалити бронювання?', + 'planner.reservationDeleted': 'Бронювання видалено', + 'planner.days': 'Дні', + 'planner.allPlaces': 'Усі місця', + 'planner.totalPlaces': 'Усього {n} місць', + 'planner.noDaysPlanned': 'Дні ще не заплановано', + 'planner.editTrip': 'Редагувати поїздку →', + 'planner.placeOne': '1 місце', + 'planner.placeN': '{n} місць', + 'planner.addNote': 'Додати нотатку', + 'planner.noEntries': 'На цей день записів немає', + 'planner.addPlace': 'Додати місце/активність', + 'planner.addPlaceShort': '+ Додати місце/активність', + 'planner.resPending': 'Бронювання очікує · ', + 'planner.resConfirmed': 'Бронювання підтверджено · ', + 'planner.notePlaceholder': 'Нотатка…', + 'planner.noteTimePlaceholder': 'Час (необов’язково)', + 'planner.noteExamplePlaceholder': + 'наприклад, S3 о 14:30 з вокзалу, пором з причалу 7, обідня перерва…', + 'planner.totalCost': 'Загальна вартість', + 'planner.searchPlaces': 'Пошук місць…', + 'planner.allCategories': 'Усі категорії', + 'planner.noPlacesFound': 'Місця не знайдені', + 'planner.addFirstPlace': 'Додати перше місце', + 'planner.noReservations': 'Немає бронювань', + 'planner.addFirstReservation': 'Додати перше бронювання', + 'planner.new': 'Нове', + 'planner.addToDay': '+ День', + 'planner.calculating': 'Розрахунок…', + 'planner.route': 'Маршрут', + 'planner.optimize': 'Оптимізувати', + 'planner.openGoogleMaps': 'Відкрити в Google Maps', + 'planner.selectDayHint': + 'Оберіть день зі списку ліворуч для перегляду плану дня', + 'planner.noPlacesForDay': 'На цей день місць поки немає', + 'planner.addPlacesLink': 'Додати місця →', + 'planner.minTotal': 'мін. всього', + 'planner.noReservation': 'Немає бронювання', + 'planner.removeFromDay': 'Прибрати з дня', + 'planner.addToThisDay': 'Додати до дня', + 'planner.overview': 'Огляд', + 'planner.noDays': 'Днів немає', + 'planner.editTripToAddDays': 'Відредагуйте поїздку, щоб додати дні', + 'planner.dayCount': '{n} днів', + 'planner.clickToUnlock': 'Натисніть, щоб розблокувати', + 'planner.keepPosition': 'Зберегти позицію при оптимізації маршруту', + 'planner.dayDetails': 'Подробиці дня', + 'planner.dayN': 'День {n}', +}; +export default planner; diff --git a/shared/src/i18n/uk/register.ts b/shared/src/i18n/uk/register.ts new file mode 100644 index 00000000..588e01e8 --- /dev/null +++ b/shared/src/i18n/uk/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': 'Паролі не співпадають', + 'register.passwordTooShort': 'Пароль має містити щонайменше 8 символів', + 'register.failed': 'Помилка реєстрації', + 'register.getStarted': 'Почати', + 'register.subtitle': 'Створіть акаунт та почніть планувати подорожі мрії.', + 'register.feature1': 'Необмежена кількість планів подорожей', + 'register.feature2': 'Інтерактивна карта', + 'register.feature3': 'Керування місцями та категоріями', + 'register.feature4': 'Відстеження бронювань', + 'register.feature5': 'Створення списків речей', + 'register.feature6': 'Зберігання фото та файлів', + 'register.createAccount': 'Створити акаунт', + 'register.startPlanning': 'Почніть планувати свої поїздки', + 'register.minChars': 'Мін. 6 символів', + 'register.confirmPassword': 'Підтвердіть пароль', + 'register.repeatPassword': 'Повторіть пароль', + 'register.registering': 'Реєстрація...', + 'register.register': 'Зареєструватися', + 'register.hasAccount': 'Вже є акаунт?', + 'register.signIn': 'Увійти', +}; +export default register; diff --git a/shared/src/i18n/uk/reservations.ts b/shared/src/i18n/uk/reservations.ts new file mode 100644 index 00000000..a71a878b --- /dev/null +++ b/shared/src/i18n/uk/reservations.ts @@ -0,0 +1,119 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': 'Бронювання', + 'reservations.empty': 'Поки немає бронювань', + 'reservations.emptyHint': 'Додайте бронювання на авіаквитки, готелі та інше', + 'reservations.add': 'Додати бронювання', + 'reservations.addManual': 'Ручне бронювання', + 'reservations.placeHint': + 'Порада: бронювання краще створювати безпосередньо з місця, щоб пов’язати їх з планом дня.', + 'reservations.confirmed': 'Підтверджено', + 'reservations.pending': 'Очікування', + 'reservations.summary': '{confirmed} підтвр., {pending} очікувань', + 'reservations.fromPlan': 'З плану', + 'reservations.showFiles': 'Показати файли', + 'reservations.editTitle': 'Редагувати бронювання', + 'reservations.status': 'Статус', + 'reservations.datetime': 'Дата і час', + 'reservations.startTime': 'Час початку', + 'reservations.endTime': 'Час закінчення', + 'reservations.date': 'Дата', + 'reservations.time': 'Час', + 'reservations.timeAlt': 'Час (альтернативний, напр. 19:30)', + 'reservations.notes': 'Нотатки', + 'reservations.notesPlaceholder': 'Додаткові нотатки...', + 'reservations.meta.airline': 'Авіакомпанія', + 'reservations.meta.flightNumber': 'Номер рейсу', + 'reservations.meta.from': 'Звідки', + 'reservations.meta.to': 'Куди', + 'reservations.needsReview': 'Перевірити', + 'reservations.needsReviewHint': + 'Аеропорт не вдалося визначити автоматично — підтвердіть місцезнаходження.', + 'reservations.searchLocation': 'Шукати станцію, порт, адресу...', + 'reservations.meta.trainNumber': 'Номер поїзда', + 'reservations.meta.platform': 'Платформа', + 'reservations.meta.seat': 'Місце', + 'reservations.meta.checkIn': 'Заїзд', + 'reservations.meta.checkInUntil': 'Заселення до', + 'reservations.meta.checkOut': 'Виїзд', + 'reservations.meta.linkAccommodation': 'Житло', + 'reservations.meta.pickAccommodation': 'Прив’язати до житла', + 'reservations.meta.noAccommodation': 'Ні', + 'reservations.meta.hotelPlace': 'Житло', + 'reservations.meta.pickHotel': 'Оберіть житло', + 'reservations.meta.fromDay': 'З', + 'reservations.meta.toDay': 'По', + 'reservations.meta.selectDay': 'Оберіть день', + 'reservations.type.flight': 'Авіаквиток', + 'reservations.type.hotel': 'Житло', + 'reservations.type.restaurant': 'Ресторан', + 'reservations.type.train': 'Поїзд', + 'reservations.type.car': 'Автомобіль', + 'reservations.type.cruise': 'Круїз', + 'reservations.type.event': 'Заходи', + 'reservations.type.tour': 'Екскурсія', + 'reservations.type.other': 'Інше', + 'reservations.confirm.delete': + 'Ви впевнені, що хочете видалити бронювання «{name}»?', + 'reservations.confirm.deleteTitle': 'Видалити бронювання?', + 'reservations.confirm.deleteBody': '«{name}» буде видалено назавжди.', + 'reservations.toast.updated': 'Бронювання оновлено', + 'reservations.toast.removed': 'Бронювання видалено', + 'reservations.toast.fileUploaded': 'Файл завантажено', + 'reservations.toast.uploadError': 'Помилка завантаження', + 'reservations.newTitle': 'Нове бронювання', + 'reservations.bookingType': 'Тип бронювання', + 'reservations.titleLabel': 'Назва', + 'reservations.titlePlaceholder': + 'наприклад, Lufthansa LH123, Hotel Adlon, ...', + 'reservations.locationAddress': 'Місцезнаходження / Адреса', + 'reservations.locationPlaceholder': 'Адреса, аеропорт, готель...', + 'reservations.confirmationCode': 'Код бронювання', + 'reservations.confirmationPlaceholder': 'наприклад, ABC12345', + 'reservations.day': 'День', + 'reservations.noDay': 'Без дня', + 'reservations.place': 'Місце', + 'reservations.noPlace': 'Без місця', + 'reservations.pendingSave': 'буде збережено…', + 'reservations.uploading': 'Завантаження...', + 'reservations.attachFile': 'Прикріпити файл', + 'reservations.linkExisting': 'Прив’язати існуючий файл', + 'reservations.toast.saveError': 'Помилка збереження', + 'reservations.toast.updateError': 'Помилка оновлення', + 'reservations.toast.deleteError': 'Помилка видалення', + 'reservations.confirm.remove': 'Видалити бронювання для «{name}»?', + 'reservations.linkAssignment': 'Прив’язати до призначення дня', + 'reservations.pickAssignment': 'Оберіть призначення з вашого плану...', + 'reservations.noAssignment': 'Без прив’язки (самостійно)', + 'reservations.price': 'Ціна', + 'reservations.budgetCategory': 'Категорія бюджету', + 'reservations.budgetCategoryPlaceholder': 'наприклад, Транспорт, Проживання', + 'reservations.budgetCategoryAuto': 'Авто (за типом бронювання)', + 'reservations.budgetHint': + 'При збереженні буде автоматично створено запис бюджету.', + 'reservations.departureDate': 'Виліт', + 'reservations.arrivalDate': 'Приліт', + 'reservations.departureTime': 'Час вильоту', + 'reservations.arrivalTime': 'Час прильоту', + 'reservations.pickupDate': 'Отримання', + 'reservations.returnDate': 'Повернення', + 'reservations.pickupTime': 'Час отримання', + 'reservations.returnTime': 'Час повернення', + 'reservations.endDate': 'Дата закінчення', + 'reservations.meta.departureTimezone': 'TZ вильоту', + 'reservations.meta.arrivalTimezone': 'TZ прильоту', + 'reservations.span.departure': 'Виліт', + 'reservations.span.arrival': 'Приліт', + 'reservations.span.inTransit': 'У дорозі', + 'reservations.span.pickup': 'Отримання', + 'reservations.span.return': 'Повернення', + 'reservations.span.active': 'Активно', + 'reservations.span.start': 'Початок', + 'reservations.span.end': 'Кінець', + 'reservations.span.ongoing': 'Триває', + 'reservations.validation.endBeforeStart': + 'Дата/час закінчення повинен бути пізніше дати/часу початку', + 'reservations.addBooking': 'Добавить бронирование', +}; +export default reservations; diff --git a/shared/src/i18n/uk/settings.ts b/shared/src/i18n/uk/settings.ts new file mode 100644 index 00000000..29d9569a --- /dev/null +++ b/shared/src/i18n/uk/settings.ts @@ -0,0 +1,297 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': 'Налаштування', + 'settings.subtitle': 'Налаштуйте свої персональні параметри', + 'settings.tabs.display': 'Відображення', + 'settings.tabs.map': 'Карта', + 'settings.tabs.notifications': 'Сповіщення', + 'settings.tabs.integrations': 'Інтеграції', + 'settings.tabs.account': 'Обліковий запис', + 'settings.tabs.offline': 'Офлайн', + 'settings.tabs.about': 'Про застосунок', + 'settings.map': 'Карта', + 'settings.mapTemplate': 'Шаблон карти', + 'settings.mapTemplatePlaceholder.select': 'Виберіть шаблон...', + 'settings.mapDefaultHint': + 'Залиште порожнім для OpenStreetMap (за замовчуванням)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': 'URL-шаблон для тайлів карти', + 'settings.mapProvider': 'Провайдер карти', + 'settings.mapProviderHint': + 'Застосовується до Trip Planner та Journey. Atlas завжди використовує Leaflet.', + 'settings.mapLeafletSubtitle': 'Класичні 2D, будь-які растрові тайли', + 'settings.mapMapboxSubtitle': 'Векторні тайли, 3D-будинки та рельєф', + 'settings.mapExperimental': 'Експериментально', + 'settings.mapMapboxToken': 'Токен доступу Mapbox', + 'settings.mapMapboxTokenHint': 'Публічний токен (pk.*) з', + 'settings.mapMapboxTokenLink': 'mapbox.com → Токени доступу', + 'settings.mapStyle': 'Стиль карти', + 'settings.mapStylePlaceholder': 'Виберіть стиль Mapbox', + 'settings.mapStyleHint': 'Preset або власний URL mapbox://styles/USER/ID', + 'settings.map3dBuildings': '3D-будинки та рельєф', + 'settings.map3dHint': + 'Нахил + справжні 3D-будинки — працює з усіма стилями, включаючи супутник.', + 'settings.mapHighQuality': 'Режим високої якості', + 'settings.mapHighQualityHint': + 'Згладжування + проекція глобуса для більш чітких країв та реалістичного вигляду світу.', + 'settings.mapHighQualityWarning': + 'Може вплинути на продуктивність на слабких пристроях.', + 'settings.mapTipLabel': 'Порада:', + 'settings.mapTip': + 'Затисніть праву кнопку миші та перетягніть, щоб повернути/нахилити карту. Клік середньою кнопкою — додати місце (права кнопка зарезервована для обертання).', + 'settings.latitude': 'Широта', + 'settings.longitude': 'Довгота', + 'settings.saveMap': 'Зберегти карту', + 'settings.apiKeys': 'API-ключі', + 'settings.mapsKey': 'API-ключ Google Maps', + 'settings.mapsKeyHint': + 'Для пошуку місць. Потрібен Places API (New). Отримайте на console.cloud.google.com', + 'settings.weatherKey': 'API-ключ OpenWeatherMap', + 'settings.weatherKeyHint': + 'Для даних про погоду. Безкоштовно на openweathermap.org/api', + 'settings.keyPlaceholder': 'Введіть ключ...', + 'settings.configured': 'Налаштовано', + 'settings.saveKeys': 'Зберегти ключі', + 'settings.display': 'Відображення', + 'settings.colorMode': 'Кольорова схема', + 'settings.light': 'Світла', + 'settings.dark': 'Темна', + 'settings.auto': 'Авто', + 'settings.language': 'Мова', + 'settings.temperature': 'Одиниця температури', + 'settings.timeFormat': 'Формат часу', + 'settings.blurBookingCodes': 'Приховати коди бронювання', + 'settings.notifications': 'Сповіщення', + 'settings.notifyTripInvite': 'Запрошення до поїздки', + 'settings.notifyBookingChange': 'Зміни бронювань', + 'settings.notifyTripReminder': 'Нагадування про поїздку', + 'settings.notifyTodoDue': 'Завдання до терміну', + 'settings.notifyVacayInvite': "Запрошення об'єднання Vacay", + 'settings.notifyPhotosShared': 'Спільні фото (Immich)', + 'settings.notifyCollabMessage': 'Повідомлення чату (Collab)', + 'settings.notifyPackingTagged': 'Список речей: призначення', + 'settings.notifyWebhook': 'Webhook-сповіщення', + 'settings.notificationsDisabled': + 'Сповіщення не налаштовані. Попросіть адміністратора ввімкнути сповіщення електронною поштою або webhook.', + 'settings.notificationsActive': 'Активний канал', + 'settings.notificationsManagedByAdmin': + 'Події сповіщень налаштовуються адміністратором.', + 'settings.on': 'Увімк.', + 'settings.off': 'Вимк.', + 'settings.mcp.title': 'Налаштування MCP', + 'settings.mcp.endpoint': 'MCP-ендпоінт', + 'settings.mcp.clientConfig': 'Конфігурація клієнта', + 'settings.mcp.clientConfigHint': + 'Замініть на API-токен зі списку нижче. Шлях до npx може потребувати налаштування для вашої системи (наприклад, C:\\PROGRA~1\\nodejs\\npx.cmd у Windows).', + 'settings.mcp.clientConfigHintOAuth': + 'Замініть і на облікові дані з вищезазначеного клієнта OAuth 2.1. При першому підключенні mcp-remote відкриє браузер для завершення авторизації. Шлях до npx може потребувати налаштування для вашої системи (наприклад, C:\\PROGRA~1\\nodejs\\npx.cmd у Windows).', + 'settings.mcp.copy': 'Копіювати', + 'settings.mcp.copied': 'Скопійовано!', + 'settings.mcp.apiTokens': 'API-токени', + 'settings.mcp.createToken': 'Створити токен', + 'settings.mcp.noTokens': + 'Токенів поки немає. Створіть один для підключення MCP-клієнтів.', + 'settings.mcp.tokenCreatedAt': 'Створено', + 'settings.mcp.tokenUsedAt': 'Використано', + 'settings.mcp.deleteTokenTitle': 'Видалити токен', + 'settings.mcp.deleteTokenMessage': + 'Цей токен перестане працювати одразу. Будь-який MCP-клієнт, що його використовує, втратить доступ.', + 'settings.mcp.modal.createTitle': 'Створити API-токен', + 'settings.mcp.modal.tokenName': 'Назва токена', + 'settings.mcp.modal.tokenNamePlaceholder': + 'наприклад Claude Desktop, Робочий ноутбук', + 'settings.mcp.modal.creating': 'Створення…', + 'settings.mcp.modal.create': 'Створити токен', + 'settings.mcp.modal.createdTitle': 'Токен створено', + 'settings.mcp.modal.createdWarning': + 'Цей токен буде показано лише один раз. Скопіюйте і збережіть його зараз — відновити його не можна.', + 'settings.mcp.modal.done': 'Готово', + 'settings.mcp.toast.created': 'Токен створено', + 'settings.mcp.toast.createError': 'Не вдалося створити токен', + 'settings.mcp.toast.deleted': 'Токен видалено', + 'settings.mcp.toast.deleteError': 'Не вдалося видалити токен', + 'settings.mcp.apiTokensDeprecated': + 'API-токени застаріли і будуть видалені в наступній версії. Будь ласка, використовуйте клієнти OAuth 2.1.', + 'settings.oauth.clients': 'Клієнти OAuth 2.1', + 'settings.oauth.clientsHint': + 'Зареєструйте клієнтів OAuth 2.1, щоб сторонні MCP-застосунки (Claude Web, Cursor тощо) могли підключатися без статичних токенів.', + 'settings.oauth.createClient': 'Новий клієнт', + 'settings.oauth.noClients': 'Немає зареєстрованих клієнтів OAuth.', + 'settings.oauth.clientId': 'ID клієнта', + 'settings.oauth.clientSecret': 'Секрет клієнта', + 'settings.oauth.deleteClient': 'Видалити клієнта', + 'settings.oauth.deleteClientMessage': + 'Цей клієнт і всі активні сесії будуть видалені назавжди. Будь-який застосунок, що його використовує, одразу втратить доступ.', + 'settings.oauth.rotateSecret': 'Оновити секрет', + 'settings.oauth.rotateSecretMessage': + 'Буде згенеровано новий секрет клієнта, а всі існуючі сесії будуть негайно анульовані. Оновіть застосунок перед закриттям цього діалогу.', + 'settings.oauth.rotateSecretConfirm': 'Оновити', + 'settings.oauth.rotateSecretConfirming': 'Оновлення…', + 'settings.oauth.rotateSecretDoneTitle': 'Новий секрет згенеровано', + 'settings.oauth.rotateSecretDoneWarning': + 'Цей секрет відображається лише один раз. Скопіюйте його зараз і оновіть застосунок — всі попередні сесії були анульовані.', + 'settings.oauth.activeSessions': 'Активні сесії OAuth', + 'settings.oauth.sessionScopes': 'Області доступу', + 'settings.oauth.sessionExpires': 'Закінчується', + 'settings.oauth.revoke': 'Відкликати', + 'settings.oauth.revokeSession': 'Відкликати сесію', + 'settings.oauth.revokeSessionMessage': + 'Це негайно відкличе доступ для даної сесії OAuth.', + 'settings.oauth.modal.createTitle': 'Зареєструвати клієнта OAuth', + 'settings.oauth.modal.presets': 'Швидкі налаштування', + 'settings.oauth.modal.clientName': 'Назва застосунку', + 'settings.oauth.modal.clientNamePlaceholder': + 'напр. Claude Web, Мій MCP-застосунок', + 'settings.oauth.modal.redirectUris': 'URI перенаправлення', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + 'Один URI на рядок. Потрібен HTTPS (localhost виключено). Потрібне точне співпадіння.', + 'settings.oauth.modal.scopes': 'Дозволені області доступу', + 'settings.oauth.modal.scopesHint': + 'list_trips і get_trip_summary завжди доступні — область не потрібна. Вони допомагають ШІ знаходити потрібні ID поїздок.', + 'settings.oauth.modal.selectAll': 'Вибрати все', + 'settings.oauth.modal.deselectAll': 'Зняти вибір', + 'settings.oauth.modal.creating': 'Реєстрація…', + 'settings.oauth.modal.create': 'Зареєструвати клієнта', + 'settings.oauth.modal.createdTitle': 'Клієнт зареєстровано', + 'settings.oauth.modal.createdWarning': + 'Секрет клієнта показується лише один раз. Скопіюйте його зараз — його не можна буде відновити.', + 'settings.oauth.toast.createError': 'Не вдалося зареєструвати клієнта OAuth', + 'settings.oauth.toast.deleted': 'Клієнт OAuth видалено', + 'settings.oauth.toast.deleteError': 'Не вдалося видалити клієнта OAuth', + 'settings.oauth.toast.revoked': 'Сесію відкликано', + 'settings.oauth.toast.revokeError': 'Не вдалося відкликати сесію', + 'settings.oauth.toast.rotateError': 'Не вдалося оновити секрет клієнта', + 'settings.account': 'Обліковий запис', + 'settings.about': 'Про застосунок', + 'settings.about.reportBug': 'Повідомити про помилку', + 'settings.about.reportBugHint': 'Знайшли проблему? Повідомте нам', + 'settings.about.featureRequest': 'Запропонувати функцію', + 'settings.about.featureRequestHint': 'Запропонуйте нову функцію', + 'settings.about.wikiHint': 'Документація та керівництва', + 'settings.about.supporters.badge': 'Щомісячні спонсори', + 'settings.about.supporters.title': 'Спутники TREK', + 'settings.about.supporters.subtitle': + 'Поки ти плануєш наступний маршрут, ці люди разом зі мною планують майбутнє TREK. Їхній щомісячний внесок йде безпосередньо в розробку та реальні витрачені години — щоб TREK залишався Open Source.', + 'settings.about.supporters.since': 'спонсор з {date}', + 'settings.about.supporters.tierEmpty': 'Стань першим', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK — це self-hosted планувальник подорожей, який допомагає організувати поїздки від першої ідеї до останнього спогаду. Планування по днях, бюджет, списки речей, фото та багато іншого — все в одному місці, на вашому власному сервері.', + 'settings.about.madeWith': 'Зроблено з', + 'settings.about.madeBy': 'Морісом і зростаючою open-source спільнотою.', + 'settings.username': "Ім'я користувача", + 'settings.email': 'Ел. пошта', + 'settings.role': 'Роль', + 'settings.roleAdmin': 'Адміністратор', + 'settings.oidcLinked': 'Пов’язаний з', + 'settings.changePassword': 'Змінити пароль', + 'settings.mustChangePassword': + 'Ви повинні змінити пароль перед продовженням. Будь ласка, встановіть новий пароль нижче.', + 'settings.currentPassword': 'Поточний пароль', + 'settings.currentPasswordRequired': 'Поточний пароль обов’язковий', + 'settings.newPassword': 'Новий пароль', + 'settings.confirmPassword': 'Підтвердіть новий пароль', + 'settings.updatePassword': 'Оновити пароль', + 'settings.passwordRequired': 'Введіть поточний та новий паролі', + 'settings.passwordTooShort': 'Пароль повинен містити не менше 8 символів', + 'settings.passwordMismatch': 'Паролі не збігаються', + 'settings.passwordWeak': + 'Пароль повинен містити великі й малі літери, цифру та спеціальний символ', + 'settings.passwordChanged': 'Пароль успішно змінено', + 'settings.deleteAccount': 'Видалити акаунт', + 'settings.deleteAccountTitle': 'Видалити ваш акаунт?', + 'settings.deleteAccountWarning': + 'Ваш акаунт і всі поїздки, місця та файли будуть безповоротно видалені. Цю дію неможливо скасувати.', + 'settings.deleteAccountConfirm': 'Видалити безповоротно', + 'settings.deleteBlockedTitle': 'Видалення неможливе', + 'settings.deleteBlockedMessage': + 'Ви єдиний адміністратор. Призначте іншого користувача адміністратором перед видаленням свого акаунта.', + 'settings.roleUser': 'Користувач', + 'settings.saveProfile': 'Зберегти профіль', + 'settings.mfa.title': 'Двофакторна аутентифікація (2FA)', + 'settings.mfa.description': + 'Додає другий крок при вході. Використовуйте додаток-аутентифікатор (Google Authenticator, Authy та ін.).', + 'settings.mfa.requiredByPolicy': + 'Адміністратор вимагає двофакторної аутентифікації. Налаштуйте додаток-аутентифікатор нижче, перш ніж продовжити.', + 'settings.mfa.backupTitle': 'Резервні коди', + 'settings.mfa.backupDescription': + 'Використовуйте ці одноразові коди, якщо втратите доступ до додатку-аутентифікатора.', + 'settings.mfa.backupWarning': + 'Збережіть їх зараз. Кожен код можна використати лише один раз.', + 'settings.mfa.backupCopy': 'Скопіювати коди', + 'settings.mfa.backupDownload': 'Завантажити TXT', + 'settings.mfa.backupPrint': 'Друк / PDF', + 'settings.mfa.backupCopied': 'Резервні коди скопійовано', + 'settings.mfa.enabled': '2FA увімкнено для вашого акаунта.', + 'settings.mfa.disabled': '2FA не увімкнено.', + 'settings.mfa.setup': 'Налаштувати автентифікатор', + 'settings.mfa.scanQr': 'Відскануйте QR-код додатком або введіть ключ вручну.', + 'settings.mfa.secretLabel': 'Секретний ключ (ручний ввід)', + 'settings.mfa.codePlaceholder': '6-значний код', + 'settings.mfa.enable': 'Увімкнути 2FA', + 'settings.mfa.cancelSetup': 'Скасувати', + 'settings.mfa.disableTitle': 'Вимкнути 2FA', + 'settings.mfa.disableHint': + 'Введіть пароль акаунта та поточний код з автентифікатора.', + 'settings.mfa.disable': 'Вимкнути 2FA', + 'settings.mfa.toastEnabled': 'Двофакторна автентифікація увімкнена', + 'settings.mfa.toastDisabled': 'Двофакторна автентифікація вимкнена', + 'settings.mfa.demoBlocked': 'Недоступно в демо-режимі', + 'settings.toast.mapSaved': 'Налаштування карти збережено', + 'settings.toast.keysSaved': 'API-ключі збережено', + 'settings.toast.displaySaved': 'Налаштування відображення збережено', + 'settings.toast.profileSaved': 'Профіль збережено', + 'settings.uploadAvatar': 'Завантажити фото профілю', + 'settings.removeAvatar': 'Видалити фото профілю', + 'settings.avatarUploaded': 'Фото профілю оновлено', + 'settings.avatarRemoved': 'Фото профілю видалено', + 'settings.avatarError': 'Помилка завантаження', + 'settings.bookingLabels': 'Підписи маршрутів бронювань', + 'settings.bookingLabelsHint': + 'Показує назви станцій / аеропортів на карті. Якщо вимкнено, показується лише значок.', + 'settings.notifyVersionAvailable': 'Доступна нова версія', + 'settings.notificationPreferences.noChannels': + 'Канали сповіщень не налаштовані. Попросіть адміністратора налаштувати сповіщення електронною поштою або через webhook.', + 'settings.webhookUrl.label': 'URL вебхука', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + 'Введіть URL вашого вебхука Discord, Slack або власного для отримання сповіщень.', + 'settings.webhookUrl.saved': 'URL вебхука збережено', + 'settings.webhookUrl.test': 'Тест', + 'settings.webhookUrl.testSuccess': 'Тестовий вебхук успішно надіслано', + 'settings.webhookUrl.testFailed': 'Помилка тестового вебхука', + 'settings.ntfyUrl.topicLabel': 'Тема Ntfy', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'URL сервера Ntfy (необов’язково)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + 'Введіть тему Ntfy для отримання push-сповіщень. Залиште поле сервера пустим, щоб використовувати налаштування за замовчуванням, задані адміністратором.', + 'settings.ntfyUrl.tokenLabel': 'Токен доступу (необов’язково)', + 'settings.ntfyUrl.tokenHint': 'Потрібно для тем, захищених паролем.', + 'settings.ntfyUrl.saved': 'Налаштування Ntfy збережені', + 'settings.ntfyUrl.test': 'Тест', + 'settings.ntfyUrl.testSuccess': 'Тестове сповіщення Ntfy успішно надіслано', + 'settings.ntfyUrl.testFailed': 'Помилка надсилання тестового сповіщення Ntfy', + 'settings.ntfyUrl.tokenCleared': 'Токен доступу очищено', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.oauth.modal.machineClient': + 'Машинний клієнт (без входу через браузер)', + 'settings.oauth.modal.machineClientHint': + 'Використовуйте надання client_credentials — URI перенаправлення не потрібні. Токен видається безпосередньо через client_id + client_secret і діє від вашого імені в межах вибраних областей.', + 'settings.oauth.modal.machineClientUsage': + 'Отримати токен: POST /oauth/token з grant_type=client_credentials, client_id і client_secret. Без браузера, без токена оновлення.', + 'settings.oauth.badge.machine': 'машина', +}; +export default settings; diff --git a/shared/src/i18n/uk/share.ts b/shared/src/i18n/uk/share.ts new file mode 100644 index 00000000..7ca7befd --- /dev/null +++ b/shared/src/i18n/uk/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': 'Публічне посилання', + 'share.linkHint': + 'Створіть посилання, за яким будь-хто зможе переглянути цю поїздку без входу. Тільки читання — редагувати не можна.', + 'share.createLink': 'Створити посилання', + 'share.deleteLink': 'Видалити посилання', + 'share.createError': 'Не вдалося створити посилання', + 'share.permMap': 'Карта і план', + 'share.permBookings': 'Бронювання', + 'share.permPacking': 'Речі', + 'share.permBudget': 'Бюджет', + 'share.permCollab': 'Чат', +}; +export default share; diff --git a/shared/src/i18n/uk/shared.ts b/shared/src/i18n/uk/shared.ts new file mode 100644 index 00000000..39717148 --- /dev/null +++ b/shared/src/i18n/uk/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': 'Посилання прострочено або недійсне', + 'shared.expiredHint': 'Це посилання на поїздку більше не активне.', + 'shared.readOnly': 'Тільки для читання', + 'shared.tabPlan': 'План', + 'shared.tabBookings': 'Бронювання', + 'shared.tabPacking': 'Багаж', + 'shared.tabBudget': 'Бюджет', + 'shared.tabChat': 'Чат', + 'shared.days': 'днів', + 'shared.places': 'місць', + 'shared.other': 'Інше', + 'shared.totalBudget': 'Загальний бюджет', + 'shared.messages': 'повідомлень', + 'shared.sharedVia': 'Поділено через', + 'shared.confirmed': 'Підтверджено', + 'shared.pending': 'Очікує', +}; +export default shared; diff --git a/shared/src/i18n/uk/stats.ts b/shared/src/i18n/uk/stats.ts new file mode 100644 index 00000000..bfd1cfb5 --- /dev/null +++ b/shared/src/i18n/uk/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': 'Країни', + 'stats.cities': 'Міста', + 'stats.trips': 'Поїздки', + 'stats.places': 'Місця', + 'stats.worldProgress': 'Прогрес по світу', + 'stats.visited': 'відвідано', + 'stats.remaining': 'залишилось', + 'stats.visitedCountries': 'Відвідані країни', +}; +export default stats; diff --git a/shared/src/i18n/uk/system_notice.ts b/shared/src/i18n/uk/system_notice.ts new file mode 100644 index 00000000..5477b8cb --- /dev/null +++ b/shared/src/i18n/uk/system_notice.ts @@ -0,0 +1,59 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': 'Ласкаво просимо в TREK', + 'system_notice.welcome_v1.body': + 'Ваш універсальний планувальник подорожей. Створюйте маршрути, діліться поїздками з друзями та залишайтесь організованими — онлайн та офлайн.', + 'system_notice.welcome_v1.cta_label': 'Спланувати поїздку', + 'system_notice.welcome_v1.hero_alt': + 'Живописне місце призначення з інтерфейсом TREK', + 'system_notice.welcome_v1.highlight_plan': + 'Детальні плани по днях для будь-яких поїздок', + 'system_notice.welcome_v1.highlight_share': 'Спільне планування', + 'system_notice.welcome_v1.highlight_offline': 'Працює офлайн на мобільному', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': 'Попереднє повідомлення', + 'system_notice.pager.next': 'Наступне повідомлення', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': 'Перейти до повідомлення {n}', + 'system_notice.pager.position': 'Повідомлення {current} із {total}', + 'system_notice.v3_photos.title': 'Фото переміщено у версії 3.0', + 'system_notice.v3_photos.body': + 'Вкладку **Фото** в Планувальнику подорожей видалено. Ваші фото в безпеці — TREK ніколи не змінював вашу бібліотеку Immich або Synology.\n\nФото тепер доступні у доповненні **Journey**. Journey необов’язковий — якщо він ще недоступний, попросіть адміністратора включити його в розділі Адмін → Додатки.', + 'system_notice.v3_journey.title': 'Знайомтесь із Journey', + 'system_notice.v3_journey.body': + 'Документуйте подорожі як історії з хронологіями, фотогалереями та інтерактивними картами.', + 'system_notice.v3_journey.cta_label': 'Відкрити Journey', + 'system_notice.v3_journey.highlight_timeline': + 'Щоденна хронологія та галерея', + 'system_notice.v3_journey.highlight_photos': 'Імпорт з Immich або Synology', + 'system_notice.v3_journey.highlight_share': 'Спільний доступ — без входу', + 'system_notice.v3_journey.highlight_export': 'Експорт у PDF-фотокнигу', + 'system_notice.v3_features.title': 'Ще більше нового у версії 3.0', + 'system_notice.v3_features.body': + 'Декілька інших важливих нововведень у цьому релізі.', + 'system_notice.v3_features.highlight_dashboard': + 'Перероблена панель у mobile-first стилі', + 'system_notice.v3_features.highlight_offline': 'Повний офлайн-режим як PWA', + 'system_notice.v3_features.highlight_search': + 'Автодоповнення пошуку місць у реальному часі', + 'system_notice.v3_features.highlight_import': 'Імпорт місць з KMZ/KML-файлів', + 'system_notice.v3_mcp.title': 'MCP: оновлення OAuth 2.1', + 'system_notice.v3_mcp.body': + 'Інтеграція MCP була повністю перероблена. OAuth 2.1 тепер є рекомендованим методом автентифікації. Статичні токени (trek_…) застаріли і будуть видалені в майбутній версії.', + 'system_notice.v3_mcp.highlight_oauth': + 'OAuth 2.1 рекомендовано (mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 детальні області дозволів', + 'system_notice.v3_mcp.highlight_deprecated': + 'Статичні токени trek_ застаріли', + 'system_notice.v3_mcp.highlight_tools': 'Розширений набір інструментів', + 'system_notice.v3_thankyou.title': 'Особливе слово від мене', + 'system_notice.v3_thankyou.body': + 'Перш ніж продовжити — хочу зупинитися на мить.\n\nTREK починався як сторонній проєкт, який я створив для власних поїздок. Я ніколи не думав, що він виросте в щось, чому 4 000 з вас довіряють планування своїх пригод. Кожна зірочка, кожен issue, кожен запит на фічу — я читаю їх усі, і саме вони підтримують мене у пізні ночі між основною роботою та університетом.\n\nХочу, щоб ви знали: TREK завжди буде open source, завжди self-hosted, завжди вашим. Жодного стеження, жодних підписок, жодних підводних каменів. Просто інструмент, створений людиною, яка любить подорожувати так само, як і ви.\n\nОсоблива подяка [jubnl](https://github.com/jubnl) — ти став неймовірним соратником. Багато з того, що робить версію 3.0 чудовою, несе твій відбиток. Дякую, що повірив у цей проєкт, коли він ще був сирим.\n\nІ кожному з вас, хто повідомив про помилку, переклав рядок, поділився TREK з другом або просто використовував його для планування поїздки — **дякую**. Ви — причина, чому все це існує.\n\nЗа багато нових пригод разом.\n\n— Maurice\n\n---\n\n[Приєднуйтесь до спільноти в Discord](https://discord.gg/7Q6M6jDwzf)\n\nЯкщо TREK робить ваші подорожі кращими, [невелика кава](https://ko-fi.com/mauriceboe) завжди допомагає тримати світло ввімкненим.', + 'system_notice.v3014_whitespace_collision.title': + 'Потрібна дія: конфлікт облікового запису користувача', + 'system_notice.v3014_whitespace_collision.body': + 'Оновлення 3.0.14 виявило один або кілька конфліктів імен користувачів або електронних адрес, спричинених початковими або кінцевими пробілами в збережених облікових записах. Уражені облікові записи було автоматично перейменовано. Перевірте журнали сервера на рядки, що починаються з **[migration] WHITESPACE COLLISION**, щоб визначити, які облікові записи потребують перевірки.', +}; +export default system_notice; diff --git a/shared/src/i18n/uk/todo.ts b/shared/src/i18n/uk/todo.ts new file mode 100644 index 00000000..3cea20b2 --- /dev/null +++ b/shared/src/i18n/uk/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': 'Список речей', + 'todo.subtab.todo': 'Задачі', + 'todo.completed': 'виконано', + 'todo.filter.all': 'Усі', + 'todo.filter.open': 'Відкриття', + 'todo.filter.done': 'Виконані', + 'todo.uncategorized': 'Без категорії', + 'todo.namePlaceholder': 'Назва завдання', + 'todo.descriptionPlaceholder': 'Опис (необов’язково)', + 'todo.unassigned': 'Не призначено', + 'todo.noCategory': 'Без категорії', + 'todo.hasDescription': 'Є опис', + 'todo.addItem': 'Нова задача', + 'todo.sidebar.sortBy': 'Сорзувати за', + 'todo.priority': 'Пріоритет', + 'todo.newCategoryLabel': 'нова', + 'todo.newCategory': 'Назва категорії', + 'todo.addCategory': 'Додати категорію', + 'todo.newItem': 'Нова задача', + 'todo.empty': 'Задач поки немає. Додайте задачу, щоб почати!', + 'todo.filter.my': 'Мої задачі', + 'todo.filter.overdue': 'Просрочені', + 'todo.sidebar.tasks': 'Задачі', + 'todo.sidebar.categories': 'Категорії', + 'todo.detail.title': 'Задача', + 'todo.detail.description': 'Опис', + 'todo.detail.category': 'Категорія', + 'todo.detail.dueDate': 'Строк виконання', + 'todo.detail.assignedTo': 'Назначено', + 'todo.detail.delete': 'Видалити', + 'todo.detail.save': 'Зберегти зміни', + 'todo.detail.create': 'Створити задачу', + 'todo.detail.priority': 'Пріоритет', + 'todo.detail.noPriority': 'Немає', + 'todo.sortByPrio': 'Пріоритет', +}; +export default todo; diff --git a/shared/src/i18n/uk/transport.ts b/shared/src/i18n/uk/transport.ts new file mode 100644 index 00000000..6a3915e1 --- /dev/null +++ b/shared/src/i18n/uk/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': 'Додати транспорт', + 'transport.modalTitle.create': 'Додати транспорт', + 'transport.modalTitle.edit': 'Змінити транспорт', + 'transport.title': 'Транспорт', + 'transport.addManual': 'Ручний транспорт', +}; +export default transport; diff --git a/shared/src/i18n/uk/trip.ts b/shared/src/i18n/uk/trip.ts new file mode 100644 index 00000000..44f3de6a --- /dev/null +++ b/shared/src/i18n/uk/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': 'План', + 'trip.tabs.transports': 'Транспорт', + 'trip.tabs.reservations': 'Бронювання', + 'trip.tabs.reservationsShort': 'Броні', + 'trip.tabs.packing': 'Список речей', + 'trip.tabs.packingShort': 'Речі', + 'trip.tabs.lists': 'Списки', + 'trip.tabs.listsShort': 'Списки', + 'trip.tabs.budget': 'Бюджет', + 'trip.tabs.files': 'Файли', + 'trip.loading': 'Завантаження поїздки...', + 'trip.loadingPhotos': 'Завантаження фото місць...', + 'trip.mobilePlan': 'План', + 'trip.mobilePlaces': 'Місця', + 'trip.toast.placeUpdated': 'Місце оновлено', + 'trip.toast.placeAdded': 'Місце додано', + 'trip.toast.placeDeleted': 'Місце видалено', + 'trip.toast.selectDay': 'Спочатку виберіть день', + 'trip.toast.assignedToDay': 'Місце призначено на день', + 'trip.toast.reorderError': 'Помилка зміни порядку', + 'trip.toast.reservationUpdated': 'Бронювання оновлено', + 'trip.toast.reservationAdded': 'Бронювання додано', + 'trip.toast.deleted': 'Видалено', + 'trip.confirm.deletePlace': 'Ви впевнені, що хочете видалити це місце?', + 'trip.confirm.deletePlaces': 'Видалити {count} місць?', + 'trip.toast.placesDeleted': '{count} місць видалено', +}; +export default trip; diff --git a/shared/src/i18n/uk/trips.ts b/shared/src/i18n/uk/trips.ts new file mode 100644 index 00000000..3a0f1732 --- /dev/null +++ b/shared/src/i18n/uk/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} видалений', + 'trips.memberRemoveError': 'Не вдалося видалити', + 'trips.memberAdded': '{username} доданий', + 'trips.memberAddError': 'Не вдалося додати', + 'trips.reminder': 'Нагадування', + 'trips.reminderNone': 'Немає', + 'trips.reminderDay': 'день', + 'trips.reminderDays': 'днів', + 'trips.reminderCustom': 'Інше', + 'trips.reminderDaysBefore': "днів до від'їзду", + 'trips.reminderDisabledHint': + 'Нагадування про поїздки вимкнено. Увімкніть їх в Адмін > Налаштування > Сповіщення.', +}; +export default trips; diff --git a/shared/src/i18n/uk/undo.ts b/shared/src/i18n/uk/undo.ts new file mode 100644 index 00000000..d06cb58d --- /dev/null +++ b/shared/src/i18n/uk/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': 'Відмінити', + 'undo.tooltip': 'Відмінити: {action}', + 'undo.assignPlace': 'Місце додано до дня', + 'undo.removeAssignment': 'Місце вилучено з дня', + 'undo.reorder': 'Місця переставлено', + 'undo.optimize': 'Маршрут оптимізовано', + 'undo.deletePlace': 'Місце видалено', + 'undo.deletePlaces': 'Місця видалено', + 'undo.moveDay': 'Місце переміщено в інший день', + 'undo.lock': 'Блокування місця змінено', + 'undo.importGpx': 'Імпорт GPX', + 'undo.importKeyholeMarkup': 'Імпорт KMZ/KML', + 'undo.importGoogleList': 'Імпорт з Google Maps', + 'undo.importNaverList': 'Імпорт з Naver Maps', + 'undo.addPlace': 'Місце додано', + 'undo.done': 'Відмінено: {action}', +}; +export default undo; diff --git a/shared/src/i18n/uk/vacay.ts b/shared/src/i18n/uk/vacay.ts new file mode 100644 index 00000000..3990d614 --- /dev/null +++ b/shared/src/i18n/uk/vacay.ts @@ -0,0 +1,107 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': 'Плануйте й керуйте днями відпустки', + 'vacay.settings': 'Налаштування', + 'vacay.year': 'Рік', + 'vacay.addYear': 'Додати наступний рік', + 'vacay.addPrevYear': 'Додати попередній рік', + 'vacay.removeYear': 'Видалити рік', + 'vacay.removeYearConfirm': 'Видалити {year}?', + 'vacay.removeYearHint': + 'Усі записи про відпустку і корпоративні вихідні за цей рік будуть безповоротно видалені.', + 'vacay.remove': 'Видалити', + 'vacay.persons': 'Люди', + 'vacay.noPersons': 'Ніхто не доданий', + 'vacay.addPerson': 'Додати людину', + 'vacay.editPerson': 'Редагувати', + 'vacay.removePerson': 'Видалити людину', + 'vacay.removePersonConfirm': 'Видалити {name}?', + 'vacay.removePersonHint': + 'Усі записи про відпустку цієї людини будуть безповоротно видалені.', + 'vacay.personName': 'Ім’я', + 'vacay.personNamePlaceholder': 'Введіть ім’я', + 'vacay.color': 'Колір', + 'vacay.add': 'Додати', + 'vacay.legend': 'Легенда', + 'vacay.publicHoliday': 'Державне свято', + 'vacay.companyHoliday': 'Корпоративний вихідний', + 'vacay.weekend': 'Вихідні', + 'vacay.modeVacation': 'Відпустка', + 'vacay.modeCompany': 'Корпоративний вихідний', + 'vacay.entitlement': 'Право на відпустку', + 'vacay.entitlementDays': 'Дні', + 'vacay.used': 'Використано', + 'vacay.remaining': 'Залишилося', + 'vacay.carriedOver': 'із {year}', + 'vacay.blockWeekends': 'Блокувати вихідні', + 'vacay.blockWeekendsHint': + 'Заборонити записи про відпустку в суботу та неділю', + 'vacay.weekendDays': 'Вихідні дні', + 'vacay.mon': 'Пн', + 'vacay.tue': 'Вт', + 'vacay.wed': 'Ср', + 'vacay.thu': 'Чт', + 'vacay.fri': 'Пт', + 'vacay.sat': 'Сб', + 'vacay.sun': 'Нд', + 'vacay.publicHolidays': 'Державні свята', + 'vacay.publicHolidaysHint': 'Позначати державні свята в календарі', + 'vacay.selectCountry': 'Виберіть країну', + 'vacay.selectRegion': 'Виберіть регіон (необов’язково)', + 'vacay.companyHolidays': 'Корпоративні вихідні', + 'vacay.companyHolidaysHint': 'Дозволити позначати корпоративні вихідні', + 'vacay.companyHolidaysNoDeduct': + 'Корпоративні вихідні не віднімаються від днів відпустки.', + 'vacay.weekStart': 'Тиждень починається з', + 'vacay.weekStartHint': + 'Виберіть, чи тиждень починається з понеділка чи неділі', + 'vacay.carryOver': 'Переносити', + 'vacay.carryOverHint': + 'Автоматично переносити залишкові дні відпустки на наступний рік', + 'vacay.sharing': 'Спільний доступ', + 'vacay.sharingHint': + 'Поділіться планом відпустки з іншими користувачами TREK', + 'vacay.owner': 'Власник', + 'vacay.shareEmailPlaceholder': 'Ел. пошта користувача TREK', + 'vacay.shareSuccess': 'План успішно надано', + 'vacay.shareError': 'Не вдалося поділитися планом', + 'vacay.dissolve': 'Розділити об’єднання', + 'vacay.dissolveHint': + 'Знову розділити календарі. Ваші записи будуть збережені.', + 'vacay.dissolveAction': 'Розділити', + 'vacay.dissolved': 'Календар розділено', + 'vacay.fusedWith': 'Об’єднано з', + 'vacay.you': 'ви', + 'vacay.noData': 'Немає даних', + 'vacay.changeColor': 'Змінити колір', + 'vacay.inviteUser': 'Запросити користувача', + 'vacay.inviteHint': + 'Запросіть іншого користувача TREK для спільного календаря відпусток.', + 'vacay.selectUser': 'Виберіть користувача', + 'vacay.sendInvite': 'Надіслати запрошення', + 'vacay.inviteSent': 'Запрошення надіслано', + 'vacay.inviteError': 'Не вдалося надіслати запрошення', + 'vacay.pending': 'очікування', + 'vacay.noUsersAvailable': 'Немає доступних користувачів', + 'vacay.accept': 'Прийняти', + 'vacay.decline': 'Відхилити', + 'vacay.acceptFusion': 'Прийняти й об’єднати', + 'vacay.inviteTitle': 'Запит на об’єднання', + 'vacay.inviteWantsToFuse': 'хоче об’єднати календар відпусток з вами.', + 'vacay.fuseInfo1': + 'Ви обидва бачитимете всі записи про відпустки в одному спільному календарі.', + 'vacay.fuseInfo2': + 'Обидві сторони можуть створювати та редагувати записи одна для одної.', + 'vacay.fuseInfo3': + 'Обидві сторони можуть видаляти записи та змінювати право на відпустку.', + 'vacay.fuseInfo4': + 'Налаштування, такі як свята та корпоративні вихідні, стають спільними.', + 'vacay.fuseInfo5': + 'Об’єднання можна скасувати будь-коли з будь-якого боку. Ваші записи будуть збережені.', + 'vacay.addCalendar': 'Добавить календарь', + 'vacay.calendarColor': 'Колір', + 'vacay.calendarLabel': 'Назва', + 'vacay.noCalendars': 'Немає календарів', +}; +export default vacay; diff --git a/shared/src/i18n/zh-TW/admin.ts b/shared/src/i18n/zh-TW/admin.ts new file mode 100644 index 00000000..17194cc2 --- /dev/null +++ b/shared/src/i18n/zh-TW/admin.ts @@ -0,0 +1,331 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': '通知', + 'admin.notifications.hint': '選擇一個通知渠道。一次只能啟用一個。', + 'admin.notifications.none': '已停用', + 'admin.notifications.email': '電子郵件 (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': '儲存通知設定', + 'admin.notifications.saved': '通知設定已儲存', + 'admin.notifications.testWebhook': '傳送測試 Webhook', + 'admin.notifications.testWebhookSuccess': '測試 Webhook 傳送成功', + 'admin.notifications.testWebhookFailed': '測試 Webhook 傳送失敗', + 'admin.notifications.emailPanel.title': '電子郵件 (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': '應用程式內通知', + 'admin.notifications.inappPanel.hint': + '應用程式內通知始終啟用,無法全域性停用。', + 'admin.notifications.adminWebhookPanel.title': '管理員 Webhook', + 'admin.notifications.adminWebhookPanel.hint': + '此 Webhook 專用於管理員通知(例如版本提醒)。它與每位使用者的 Webhook 分開,設定後始終會觸發。', + 'admin.notifications.adminWebhookPanel.saved': '管理員 Webhook URL 已儲存', + 'admin.notifications.adminWebhookPanel.testSuccess': '測試 Webhook 傳送成功', + 'admin.notifications.adminWebhookPanel.testFailed': '測試 Webhook 傳送失敗', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + '配置 URL 後,管理員 Webhook 始終觸發', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + '允許使用者設定自己的 ntfy 主題以接收推播通知。在下方設定預設伺服器以預先填入使用者設定。', + 'admin.notifications.testNtfy': '傳送測試 Ntfy', + 'admin.notifications.testNtfySuccess': '測試 Ntfy 傳送成功', + 'admin.notifications.testNtfyFailed': '測試 Ntfy 失敗', + 'admin.notifications.adminNtfyPanel.title': '管理員 Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + '此 Ntfy 主題專用於管理員通知(例如版本提醒)。它與每位使用者的主題分開,設定後始終會觸發。', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 伺服器 URL', + 'admin.notifications.adminNtfyPanel.serverHint': + '同時用作使用者 ntfy 通知的預設伺服器。留空則預設使用 ntfy.sh。使用者可在自己的設定中覆寫此項。', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': '管理員主題', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': '存取權杖(選填)', + 'admin.notifications.adminNtfyPanel.tokenCleared': '管理員存取權杖已清除', + 'admin.notifications.adminNtfyPanel.saved': '管理員 Ntfy 設定已儲存', + 'admin.notifications.adminNtfyPanel.test': '傳送測試 Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': '測試 Ntfy 傳送成功', + 'admin.notifications.adminNtfyPanel.testFailed': '測試 Ntfy 失敗', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + '設定主題後管理員 Ntfy 始終觸發', + 'admin.notifications.adminNotificationsHint': + '配置哪些渠道傳遞僅管理員通知(例如版本提醒)。', + 'admin.notifications.tripReminders.title': '行程提醒', + 'admin.notifications.tripReminders.hint': + '在行程開始前發送提醒通知(需要在行程中設定提醒天數)。', + 'admin.notifications.tripReminders.enabled': '行程提醒已啟用', + 'admin.notifications.tripReminders.disabled': '行程提醒已停用', + 'admin.smtp.title': '郵件與通知', + 'admin.smtp.hint': '用於傳送電子郵件通知的 SMTP 配置。', + 'admin.smtp.testButton': '傳送測試郵件', + 'admin.webhook.hint': + '允許使用者配置自己的 Webhook URL 以接收通知(Discord、Slack 等)。', + 'admin.smtp.testSuccess': '測試郵件傳送成功', + 'admin.smtp.testFailed': '測試郵件傳送失敗', + 'admin.title': '管理後臺', + 'admin.subtitle': '使用者管理和系統設定', + 'admin.tabs.users': '使用者', + 'admin.tabs.categories': '分類', + 'admin.tabs.backup': '備份', + 'admin.tabs.audit': '審計日誌', + 'admin.tabs.notifications': '通知', + 'admin.stats.users': '使用者', + 'admin.stats.trips': '旅行', + 'admin.stats.places': '地點', + 'admin.stats.photos': '照片', + 'admin.stats.files': '檔案', + 'admin.table.user': '使用者', + 'admin.table.email': '郵箱', + 'admin.table.role': '角色', + 'admin.table.created': '建立時間', + 'admin.table.lastLogin': '最後登入', + 'admin.table.actions': '操作', + 'admin.you': '(你)', + 'admin.editUser': '編輯使用者', + 'admin.newPassword': '新密碼', + 'admin.newPasswordHint': '留空則保持當前密碼', + 'admin.deleteUser': '刪除使用者「{name}」?所有旅行將被永久刪除。', + 'admin.deleteUserTitle': '刪除使用者', + 'admin.newPasswordPlaceholder': '輸入新密碼…', + 'admin.toast.loadError': '載入管理資料失敗', + 'admin.toast.userUpdated': '使用者已更新', + 'admin.toast.updateError': '更新失敗', + 'admin.toast.userDeleted': '使用者已刪除', + 'admin.toast.deleteError': '刪除失敗', + 'admin.toast.cannotDeleteSelf': '不能刪除自己的賬戶', + 'admin.toast.userCreated': '使用者已建立', + 'admin.toast.createError': '建立使用者失敗', + 'admin.toast.fieldsRequired': '使用者名稱、郵箱和密碼為必填項', + 'admin.createUser': '建立使用者', + 'admin.invite.title': '邀請連結', + 'admin.invite.subtitle': '建立一次性註冊連結', + 'admin.invite.create': '建立連結', + 'admin.invite.createAndCopy': '建立並複製', + 'admin.invite.empty': '尚未建立邀請連結', + 'admin.invite.maxUses': '最大使用次數', + 'admin.invite.expiry': '有效期', + 'admin.invite.uses': '已使用', + 'admin.invite.expiresAt': '過期時間', + 'admin.invite.createdBy': '由', + 'admin.invite.active': '有效', + 'admin.invite.expired': '已過期', + 'admin.invite.usedUp': '已用完', + 'admin.invite.copied': '邀請連結已複製', + 'admin.invite.copyLink': '複製連結', + 'admin.invite.deleted': '邀請連結已刪除', + 'admin.invite.createError': '建立連結失敗', + 'admin.invite.deleteError': '刪除連結失敗', + 'admin.tabs.settings': '設定', + 'admin.allowRegistration': '允許註冊', + 'admin.allowRegistrationHint': '新使用者可以自行註冊', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': '要求雙因素身份驗證(2FA)', + 'admin.requireMfaHint': + '未啟用 2FA 的使用者必須先完成設定中的配置才能使用應用。', + 'admin.apiKeys': 'API 金鑰', + 'admin.apiKeysHint': '可選。啟用地點的擴充套件資料,如照片和天氣。', + 'admin.mapsKey': 'Google Maps API 金鑰', + 'admin.mapsKeyHint': '用於地點搜尋。在 console.cloud.google.com 獲取', + 'admin.mapsKeyHintLong': + '沒有 API 金鑰時,使用 OpenStreetMap 搜尋地點。有了 Google API 金鑰,還可以載入照片、評分和營業時間。在 console.cloud.google.com 獲取。', + 'admin.recommended': '推薦', + 'admin.weatherKey': 'OpenWeatherMap API 金鑰', + 'admin.weatherKeyHint': '用於天氣資料。在 openweathermap.org 免費獲取', + 'admin.validateKey': '測試', + 'admin.keyValid': '已連線', + 'admin.keyInvalid': '無效', + 'admin.keySaved': 'API 金鑰已儲存', + 'admin.oidcTitle': '單點登入 (OIDC)', + 'admin.oidcSubtitle': + '允許透過 Google、Apple、Authentik 或 Keycloak 等外部提供商登入。', + 'admin.oidcDisplayName': '顯示名稱', + 'admin.oidcIssuer': '頒發者 URL', + 'admin.oidcIssuerHint': + '提供商的 OpenID Connect 頒發者 URL。如 https://accounts.google.com', + 'admin.oidcSaved': 'OIDC 配置已儲存', + 'admin.oidcOnlyMode': '停用密碼登入', + 'admin.oidcOnlyModeHint': '啟用後,僅允許 SSO 登入。密碼登入和註冊將被停用。', + 'admin.fileTypes': '允許的檔案型別', + 'admin.fileTypesHint': '配置使用者可以上傳的檔案型別。', + 'admin.fileTypesFormat': + '以逗號分隔的副檔名(如 jpg,png,pdf,doc)。使用 * 允許所有型別。', + 'admin.fileTypesSaved': '檔案型別設定已儲存', + 'admin.placesPhotos.title': '地點照片', + 'admin.placesPhotos.subtitle': + '從 Google Places API 獲取照片。停用可節省 API 配額。Wikimedia 照片不受影響。', + 'admin.placesAutocomplete.title': '地點自動補全', + 'admin.placesAutocomplete.subtitle': + '使用 Google Places API 提供搜尋建議。停用可節省 API 配額。', + 'admin.placesDetails.title': '地點詳情', + 'admin.placesDetails.subtitle': + '從 Google Places API 獲取地點詳細資訊(營業時間、評分、網站)。停用可節省 API 配額。', + 'admin.bagTracking.title': '行李追蹤', + 'admin.bagTracking.subtitle': '為打包物品啟用重量和行李分配', + 'admin.collab.chat.title': '聊天', + 'admin.collab.chat.subtitle': '即時訊息協作', + 'admin.collab.notes.title': '筆記', + 'admin.collab.notes.subtitle': '共享筆記和文件', + 'admin.collab.polls.title': '投票', + 'admin.collab.polls.subtitle': '群組投票和表決', + 'admin.collab.whatsnext.title': '下一步', + 'admin.collab.whatsnext.subtitle': '活動建議和後續步驟', + 'admin.tabs.config': '配置', + 'admin.tabs.defaults': '用戶預設設定', + 'admin.defaultSettings.title': '用戶預設設定', + 'admin.defaultSettings.description': + '設定整個執行個體的預設值。未更改設定的用戶將看到這些值。用戶自己的更改始終優先。', + 'admin.defaultSettings.saved': '預設值已儲存', + 'admin.defaultSettings.reset': '重設為內建預設值', + 'admin.defaultSettings.resetToBuiltIn': '重設', + 'admin.tabs.templates': '打包模板', + 'admin.packingTemplates.title': '打包模板', + 'admin.packingTemplates.subtitle': '建立可複用的旅行打包清單', + 'admin.packingTemplates.create': '新建模板', + 'admin.packingTemplates.namePlaceholder': '模板名稱(如:海灘度假)', + 'admin.packingTemplates.empty': '尚未建立模板', + 'admin.packingTemplates.items': '物品', + 'admin.packingTemplates.categories': '分類', + 'admin.packingTemplates.itemName': '物品名稱', + 'admin.packingTemplates.itemCategory': '分類', + 'admin.packingTemplates.categoryName': '分類名稱(如:衣物)', + 'admin.packingTemplates.addCategory': '新增分類', + 'admin.packingTemplates.created': '模板已建立', + 'admin.packingTemplates.deleted': '模板已刪除', + 'admin.packingTemplates.loadError': '載入模板失敗', + 'admin.packingTemplates.createError': '建立模板失敗', + 'admin.packingTemplates.deleteError': '刪除模板失敗', + 'admin.packingTemplates.saveError': '儲存失敗', + 'admin.tabs.addons': '擴充套件', + 'admin.addons.title': '擴充套件', + 'admin.addons.subtitle': '啟用或停用功能以自定義你的 TREK 體驗。', + 'admin.addons.catalog.memories.name': '照片 (Immich)', + 'admin.addons.catalog.memories.description': '透過 Immich 例項分享旅行照片', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': '用於 AI 助手整合的模型上下文協議', + 'admin.addons.catalog.packing.name': '行李', + 'admin.addons.catalog.packing.description': '每次旅行的行李準備清單', + 'admin.addons.catalog.budget.name': '預算', + 'admin.addons.catalog.budget.description': '跟蹤支出並規劃旅行預算', + 'admin.addons.catalog.documents.name': '文件', + 'admin.addons.catalog.documents.description': '儲存和管理旅行文件', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': '帶日曆檢視的個人假期規劃器', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + '標記已訪問國家和旅行統計的世界地圖', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': '旅行規劃的即時筆記、投票和聊天', + 'admin.addons.subtitleBefore': '啟用或停用功能以自定義你的 ', + 'admin.addons.subtitleAfter': ' 體驗。', + 'admin.addons.enabled': '已啟用', + 'admin.addons.disabled': '已停用', + 'admin.addons.type.trip': '旅行', + 'admin.addons.type.global': '全域性', + 'admin.addons.type.integration': '整合', + 'admin.addons.tripHint': '在每次旅行中作為標籤頁顯示', + 'admin.addons.globalHint': '在主導航中作為獨立板塊顯示', + 'admin.addons.integrationHint': '後端服務和 API 整合,無專屬頁面', + 'admin.addons.toast.updated': '擴充套件已更新', + 'admin.addons.toast.error': '更新擴充套件失敗', + 'admin.addons.noAddons': '暫無可用擴充套件', + 'admin.weather.title': '天氣資料', + 'admin.weather.badge': '自 2026 年 3 月 24 日起', + 'admin.weather.description': + 'TREK 使用 Open-Meteo 作為天氣資料來源。Open-Meteo 是免費的開源天氣服務——無需 API 金鑰。', + 'admin.weather.forecast': '16 天天氣預報', + 'admin.weather.forecastDesc': '之前為 5 天 (OpenWeatherMap)', + 'admin.weather.climate': '歷史氣候資料', + 'admin.weather.climateDesc': '16 天預報之外的日期使用過去 85 年的平均值', + 'admin.weather.requests': '每天 10,000 次請求', + 'admin.weather.requestsDesc': '免費,無需 API 金鑰', + 'admin.weather.locationHint': + '天氣基於每天中第一個有座標的地點。如果當天沒有分配地點,則使用地點列表中的任意地點作為參考。', + 'admin.tabs.mcpTokens': 'MCP 存取', + 'admin.mcpTokens.title': 'MCP 存取', + 'admin.mcpTokens.subtitle': '管理所有使用者的 OAuth 工作階段和 API 令牌', + 'admin.mcpTokens.sectionTitle': 'API 令牌', + 'admin.mcpTokens.owner': '所有者', + 'admin.mcpTokens.tokenName': '令牌名稱', + 'admin.mcpTokens.created': '建立時間', + 'admin.mcpTokens.lastUsed': '最後使用', + 'admin.mcpTokens.never': '從未', + 'admin.mcpTokens.empty': '尚未建立任何 MCP 令牌', + 'admin.mcpTokens.deleteTitle': '刪除令牌', + 'admin.mcpTokens.deleteMessage': + '此令牌將立即被撤銷。使用者將失去透過此令牌的 MCP 訪問許可權。', + 'admin.mcpTokens.deleteSuccess': '令牌已刪除', + 'admin.mcpTokens.deleteError': '刪除令牌失敗', + 'admin.mcpTokens.loadError': '載入令牌失敗', + 'admin.oauthSessions.sectionTitle': 'OAuth 工作階段', + 'admin.oauthSessions.clientName': '客戶端', + 'admin.oauthSessions.owner': '所有者', + 'admin.oauthSessions.scopes': '權限範圍', + 'admin.oauthSessions.created': '建立時間', + 'admin.oauthSessions.empty': '目前沒有活躍的 OAuth 工作階段', + 'admin.oauthSessions.revokeTitle': '撤銷工作階段', + 'admin.oauthSessions.revokeMessage': + '此 OAuth 工作階段將立即被撤銷。客戶端將失去 MCP 存取權限。', + 'admin.oauthSessions.revokeSuccess': '工作階段已撤銷', + 'admin.oauthSessions.revokeError': '撤銷工作階段失敗', + 'admin.oauthSessions.loadError': '載入 OAuth 工作階段失敗', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': '安全與管理員操作記錄(備份、使用者、MFA、設定)。', + 'admin.audit.empty': '暫無審計記錄。', + 'admin.audit.refresh': '重新整理', + 'admin.audit.loadMore': '載入更多', + 'admin.audit.showing': '已載入 {count} 條 · 共 {total} 條', + 'admin.audit.col.time': '時間', + 'admin.audit.col.user': '使用者', + 'admin.audit.col.action': '操作', + 'admin.audit.col.resource': '資源', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': '詳情', + 'admin.github.title': '版本歷史', + 'admin.github.subtitle': '{repo} 的最新更新', + 'admin.github.latest': '最新', + 'admin.github.prerelease': '預釋出', + 'admin.github.showDetails': '顯示詳情', + 'admin.github.hideDetails': '隱藏詳情', + 'admin.github.loadMore': '載入更多', + 'admin.github.loading': '載入中...', + 'admin.github.support': '幫助我繼續開發 TREK', + 'admin.github.error': '載入版本失敗', + 'admin.github.by': '作者', + 'admin.update.available': '有可用更新', + 'admin.update.text': 'TREK {version} 已釋出。你當前使用的是 {current}。', + 'admin.update.button': '在 GitHub 檢視', + 'admin.update.install': '安裝更新', + 'admin.update.confirmTitle': '確定安裝更新?', + 'admin.update.confirmText': + 'TREK 將從 {current} 更新到 {version}。伺服器將自動重啟。', + 'admin.update.dataInfo': + '你的所有資料(旅行、使用者、API 金鑰、上傳檔案、Vacay、Atlas、預算)將被保留。', + 'admin.update.warning': '重啟期間應用將短暫不可用。', + 'admin.update.confirm': '立即更新', + 'admin.update.installing': '更新中…', + 'admin.update.success': '更新已安裝!伺服器正在重啟…', + 'admin.update.failed': '更新失敗', + 'admin.update.backupHint': '建議在更新前建立備份。', + 'admin.update.backupLink': '前往備份', + 'admin.update.howTo': '如何更新', + 'admin.update.dockerText': + '你的 TREK 例項執行在 Docker 中。要更新到 {version},請在伺服器上執行以下命令:', + 'admin.update.reloadHint': '請在幾秒後重新整理頁面。', + 'admin.tabs.permissions': '許可權', + 'admin.addons.catalog.journey.name': '旅程', + 'admin.addons.catalog.journey.description': + '旅行追蹤與旅行日誌,包含打卡、照片和每日故事', +}; +export default admin; diff --git a/shared/src/i18n/zh-TW/airport.ts b/shared/src/i18n/zh-TW/airport.ts new file mode 100644 index 00000000..bc8e33d0 --- /dev/null +++ b/shared/src/i18n/zh-TW/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': '機場代碼或城市(例如 FRA)', +}; +export default airport; diff --git a/shared/src/i18n/zh-TW/atlas.ts b/shared/src/i18n/zh-TW/atlas.ts new file mode 100644 index 00000000..f141c3cd --- /dev/null +++ b/shared/src/i18n/zh-TW/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': '你的全球旅行足跡', + 'atlas.countries': '國家', + 'atlas.trips': '旅行', + 'atlas.places': '地點', + 'atlas.days': '天', + 'atlas.visitedCountries': '已訪問國家', + 'atlas.cities': '城市', + 'atlas.noData': '暫無旅行資料', + 'atlas.noDataHint': '建立旅行並新增地點以檢視世界地圖', + 'atlas.lastTrip': '上次旅行', + 'atlas.nextTrip': '下次旅行', + 'atlas.daysLeft': '天后出發', + 'atlas.streak': '連續', + 'atlas.year': '年', + 'atlas.years': '年', + 'atlas.yearInRow': '年連續', + 'atlas.yearsInRow': '年連續', + 'atlas.tripIn': '次旅行在', + 'atlas.tripsIn': '次旅行在', + 'atlas.since': '自', + 'atlas.europe': '歐洲', + 'atlas.asia': '亞洲', + 'atlas.northAmerica': '北美洲', + 'atlas.southAmerica': '南美洲', + 'atlas.africa': '非洲', + 'atlas.oceania': '大洋洲', + 'atlas.other': '其他', + 'atlas.firstVisit': '首次旅行', + 'atlas.lastVisitLabel': '最近旅行', + 'atlas.tripSingular': '次旅行', + 'atlas.tripPlural': '次旅行', + 'atlas.placeVisited': '個地點已訪問', + 'atlas.placesVisited': '個地點已訪問', + 'atlas.statsTab': '統計', + 'atlas.bucketTab': '心願單', + 'atlas.addBucket': '新增到心願單', + 'atlas.bucketNamePlaceholder': '地點或目的地...', + 'atlas.bucketNotesPlaceholder': '備註(可選)', + 'atlas.bucketEmpty': '你的心願單是空的', + 'atlas.bucketEmptyHint': '新增你夢想去的地方', + 'atlas.unmark': '移除', + 'atlas.confirmMark': '將此國家標記為已訪問?', + 'atlas.confirmUnmark': '從已訪問列表中移除此國家?', + 'atlas.confirmUnmarkRegion': '從已訪問列表中移除此地區?', + 'atlas.markVisited': '標記為已訪問', + 'atlas.markVisitedHint': '將此國家新增到已訪問列表', + 'atlas.markRegionVisitedHint': '將此地區新增到已訪問列表', + 'atlas.addToBucket': '新增到心願單', + 'atlas.addPoi': '新增地點', + 'atlas.searchCountry': '搜尋國家...', + 'atlas.month': '月份', + 'atlas.addToBucketHint': '儲存為想去的地方', + 'atlas.bucketWhen': '你計劃什麼時候去?', +}; +export default atlas; diff --git a/shared/src/i18n/zh-TW/backup.ts b/shared/src/i18n/zh-TW/backup.ts new file mode 100644 index 00000000..1d4b5cc8 --- /dev/null +++ b/shared/src/i18n/zh-TW/backup.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': '資料備份', + 'backup.subtitle': '資料庫和所有上傳檔案', + 'backup.refresh': '重新整理', + 'backup.upload': '上傳備份', + 'backup.uploading': '上傳中…', + 'backup.create': '建立備份', + 'backup.creating': '建立中…', + 'backup.empty': '暫無備份', + 'backup.createFirst': '建立第一個備份', + 'backup.download': '下載', + 'backup.restore': '恢復', + 'backup.confirm.restore': + '恢復備份「{name}」?\n\n所有當前資料將被備份資料替換。', + 'backup.confirm.uploadRestore': + '上傳並恢復備份檔案「{name}」?\n\n所有當前資料將被覆蓋。', + 'backup.confirm.delete': '刪除備份「{name}」?', + 'backup.toast.loadError': '載入備份失敗', + 'backup.toast.created': '備份建立成功', + 'backup.toast.createError': '建立備份失敗', + 'backup.toast.restored': '備份已恢復。頁面即將重新整理…', + 'backup.toast.restoreError': '恢復失敗', + 'backup.toast.uploadError': '上傳失敗', + 'backup.toast.deleted': '備份已刪除', + 'backup.toast.deleteError': '刪除失敗', + 'backup.toast.downloadError': '下載失敗', + 'backup.toast.settingsSaved': '自動備份設定已儲存', + 'backup.toast.settingsError': '儲存設定失敗', + 'backup.auto.title': '自動備份', + 'backup.auto.subtitle': '按計劃自動備份', + 'backup.auto.enable': '啟用自動備份', + 'backup.auto.enableHint': '將按所選計劃自動建立備份', + 'backup.auto.interval': '間隔', + 'backup.auto.hour': '執行時間', + 'backup.auto.hourHint': '伺服器本地時間({format} 格式)', + 'backup.auto.dayOfWeek': '星期幾', + 'backup.auto.dayOfMonth': '每月幾號', + 'backup.auto.dayOfMonthHint': '限 1–28 以相容所有月份', + 'backup.auto.scheduleSummary': '計劃', + 'backup.auto.summaryDaily': '每天 {hour}:00', + 'backup.auto.summaryWeekly': '每{day} {hour}:00', + 'backup.auto.summaryMonthly': '每月 {day} 號 {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + '自動備份透過 Docker 環境變數配置。要更改設定,請更新 docker-compose.yml 並重啟容器。', + 'backup.auto.copyEnv': '複製 Docker 環境變數', + 'backup.auto.envCopied': 'Docker 環境變數已複製到剪貼簿', + 'backup.auto.keepLabel': '自動刪除舊備份', + 'backup.dow.sunday': '週日', + 'backup.dow.monday': '週一', + 'backup.dow.tuesday': '週二', + 'backup.dow.wednesday': '週三', + 'backup.dow.thursday': '週四', + 'backup.dow.friday': '週五', + 'backup.dow.saturday': '週六', + 'backup.interval.hourly': '每小時', + 'backup.interval.daily': '每天', + 'backup.interval.weekly': '每週', + 'backup.interval.monthly': '每月', + 'backup.keep.1day': '1 天', + 'backup.keep.3days': '3 天', + 'backup.keep.7days': '7 天', + 'backup.keep.14days': '14 天', + 'backup.keep.30days': '30 天', + 'backup.keep.forever': '永久保留', + 'backup.restoreConfirmTitle': '恢復備份?', + 'backup.restoreWarning': + '所有當前資料(旅行、地點、使用者、上傳檔案)將被備份資料永久替換。此操作無法撤銷。', + 'backup.restoreTip': '提示:恢復前建議先備份當前狀態。', + 'backup.restoreConfirm': '確認恢復', +}; +export default backup; diff --git a/shared/src/i18n/zh-TW/budget.ts b/shared/src/i18n/zh-TW/budget.ts new file mode 100644 index 00000000..8a4d0956 --- /dev/null +++ b/shared/src/i18n/zh-TW/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': '預算', + 'budget.exportCsv': '匯出 CSV', + 'budget.emptyTitle': '尚未建立預算', + 'budget.emptyText': '建立分類和條目來規劃旅行預算', + 'budget.emptyPlaceholder': '輸入分類名稱...', + 'budget.createCategory': '建立分類', + 'budget.category': '分類', + 'budget.categoryName': '分類名稱', + 'budget.table.name': '名稱', + 'budget.table.total': '合計', + 'budget.table.persons': '人數', + 'budget.table.days': '天數', + 'budget.table.perPerson': '人均', + 'budget.table.perDay': '日均', + 'budget.table.perPersonDay': '人日均', + 'budget.table.note': '備註', + 'budget.table.date': '日期', + 'budget.newEntry': '新建條目', + 'budget.defaultEntry': '新建條目', + 'budget.defaultCategory': '新分類', + 'budget.total': '合計', + 'budget.totalBudget': '總預算', + 'budget.byCategory': '按分類', + 'budget.editTooltip': '點選編輯', + 'budget.linkedToReservation': '已連結至預訂——請在那裡編輯名稱', + 'budget.confirm.deleteCategory': + '確定刪除分類「{name}」及其 {count} 個條目?', + 'budget.deleteCategory': '刪除分類', + 'budget.perPerson': '人均', + 'budget.paid': '已支付', + 'budget.open': '未支付', + 'budget.noMembers': '未分配成員', + 'budget.settlement': '結算', + 'budget.settlementInfo': + '點選預算專案上的成員頭像將其標記為綠色——表示該成員已付款。結算會顯示誰欠誰多少。', + 'budget.netBalances': '淨餘額', + 'budget.categoriesLabel': '類別', +}; +export default budget; diff --git a/shared/src/i18n/zh-TW/categories.ts b/shared/src/i18n/zh-TW/categories.ts new file mode 100644 index 00000000..3cd57ecd --- /dev/null +++ b/shared/src/i18n/zh-TW/categories.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': '分類', + 'categories.subtitle': '管理地點分類', + 'categories.new': '新建分類', + 'categories.empty': '暫無分類', + 'categories.namePlaceholder': '分類名稱', + 'categories.icon': '圖示', + 'categories.color': '顏色', + 'categories.customColor': '選擇自定義顏色', + 'categories.preview': '預覽', + 'categories.defaultName': '分類', + 'categories.update': '更新', + 'categories.create': '建立', + 'categories.confirm.delete': '刪除分類?該分類下的地點不會被刪除。', + 'categories.toast.loadError': '載入分類失敗', + 'categories.toast.nameRequired': '請輸入名稱', + 'categories.toast.updated': '分類已更新', + 'categories.toast.created': '分類已建立', + 'categories.toast.saveError': '儲存失敗', + 'categories.toast.deleted': '分類已刪除', + 'categories.toast.deleteError': '刪除失敗', +}; +export default categories; diff --git a/shared/src/i18n/zh-TW/collab.ts b/shared/src/i18n/zh-TW/collab.ts new file mode 100644 index 00000000..6c782810 --- /dev/null +++ b/shared/src/i18n/zh-TW/collab.ts @@ -0,0 +1,73 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': '聊天', + 'collab.tabs.notes': '筆記', + 'collab.tabs.polls': '投票', + 'collab.whatsNext.title': '接下來', + 'collab.whatsNext.today': '今天', + 'collab.whatsNext.tomorrow': '明天', + 'collab.whatsNext.empty': '暫無活動', + 'collab.whatsNext.until': '至', + 'collab.whatsNext.emptyHint': '有時間安排的活動將顯示在此', + 'collab.chat.send': '傳送', + 'collab.chat.placeholder': '輸入訊息...', + 'collab.chat.empty': '開始對話', + 'collab.chat.emptyHint': '訊息對所有旅行成員可見', + 'collab.chat.emptyDesc': '與旅伴分享想法、計劃和動態', + 'collab.chat.today': '今天', + 'collab.chat.yesterday': '昨天', + 'collab.chat.deletedMessage': '刪除了一條訊息', + 'collab.chat.reply': '回覆', + 'collab.chat.loadMore': '載入更早的訊息', + 'collab.chat.justNow': '剛剛', + 'collab.chat.minutesAgo': '{n} 分鐘前', + 'collab.chat.hoursAgo': '{n} 小時前', + 'collab.notes.title': '筆記', + 'collab.notes.new': '新建筆記', + 'collab.notes.empty': '暫無筆記', + 'collab.notes.emptyHint': '開始記錄想法和計劃', + 'collab.notes.all': '全部', + 'collab.notes.titlePlaceholder': '筆記標題', + 'collab.notes.contentPlaceholder': '寫點什麼...', + 'collab.notes.categoryPlaceholder': '分類', + 'collab.notes.newCategory': '新建分類...', + 'collab.notes.category': '分類', + 'collab.notes.noCategory': '無分類', + 'collab.notes.color': '顏色', + 'collab.notes.save': '儲存', + 'collab.notes.cancel': '取消', + 'collab.notes.edit': '編輯', + 'collab.notes.delete': '刪除', + 'collab.notes.pin': '置頂', + 'collab.notes.unpin': '取消置頂', + 'collab.notes.daysAgo': '{n} 天前', + 'collab.notes.categorySettings': '管理分類', + 'collab.notes.create': '建立', + 'collab.notes.website': '網站', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': '附加檔案', + 'collab.notes.noCategoriesYet': '暫無分類', + 'collab.notes.emptyDesc': '建立一個筆記開始吧', + 'collab.polls.title': '投票', + 'collab.polls.new': '新建投票', + 'collab.polls.empty': '暫無投票', + 'collab.polls.emptyHint': '向團隊提問並一起投票', + 'collab.polls.question': '問題', + 'collab.polls.questionPlaceholder': '我們應該做什麼?', + 'collab.polls.addOption': '+ 新增選項', + 'collab.polls.optionPlaceholder': '選項 {n}', + 'collab.polls.create': '建立投票', + 'collab.polls.close': '關閉', + 'collab.polls.closed': '已關閉', + 'collab.polls.votes': '{n} 票', + 'collab.polls.vote': '{n} 票', + 'collab.polls.multipleChoice': '多選', + 'collab.polls.multiChoice': '多選', + 'collab.polls.deadline': '截止時間', + 'collab.polls.option': '選項', + 'collab.polls.options': '選項', + 'collab.polls.delete': '刪除', + 'collab.polls.closedSection': '已關閉', +}; +export default collab; diff --git a/shared/src/i18n/zh-TW/common.ts b/shared/src/i18n/zh-TW/common.ts new file mode 100644 index 00000000..da543769 --- /dev/null +++ b/shared/src/i18n/zh-TW/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': '儲存', + 'common.showMore': '顯示更多', + 'common.showLess': '收起', + 'common.cancel': '取消', + 'common.clear': '清除', + 'common.delete': '刪除', + 'common.edit': '編輯', + 'common.add': '新增', + 'common.loading': '載入中...', + 'common.import': '匯入', + 'common.select': '選擇', + 'common.selectAll': '全選', + 'common.deselectAll': '取消全選', + 'common.error': '錯誤', + 'common.unknownError': '未知錯誤', + 'common.tooManyAttempts': '嘗試次數過多,請稍後再試。', + 'common.back': '返回', + 'common.all': '全部', + 'common.close': '關閉', + 'common.open': '開啟', + 'common.upload': '上傳', + 'common.search': '搜尋', + 'common.confirm': '確認', + 'common.ok': '確定', + 'common.yes': '是', + 'common.no': '否', + 'common.or': '或', + 'common.none': '無', + 'common.date': '日期', + 'common.rename': '重新命名', + 'common.discardChanges': '捨棄變更', + 'common.discard': '捨棄', + 'common.name': '名稱', + 'common.email': '郵箱', + 'common.password': '密碼', + 'common.saving': '儲存中...', + 'common.saved': '已儲存', + 'common.expand': '展開', + 'common.collapse': '折疊', + 'common.update': '更新', + 'common.change': '修改', + 'common.uploading': '上傳中…', + 'common.backToPlanning': '返回規劃', + 'common.reset': '重置', + 'common.copy': '複製', + 'common.copied': '已複製', + 'common.justNow': '剛剛', + 'common.hoursAgo': '{count}小時前', + 'common.daysAgo': '{count}天前', +}; +export default common; diff --git a/shared/src/i18n/zh-TW/dashboard.ts b/shared/src/i18n/zh-TW/dashboard.ts new file mode 100644 index 00000000..b0ca802b --- /dev/null +++ b/shared/src/i18n/zh-TW/dashboard.ts @@ -0,0 +1,105 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': '我的旅行', + 'dashboard.subtitle.loading': '載入旅行中...', + 'dashboard.subtitle.trips': '{count} 次旅行({archived} 已歸檔)', + 'dashboard.subtitle.empty': '開始你的第一次旅行', + 'dashboard.subtitle.activeOne': '{count} 個進行中的旅行', + 'dashboard.subtitle.activeMany': '{count} 個進行中的旅行', + 'dashboard.subtitle.archivedSuffix': ' · {count} 已歸檔', + 'dashboard.newTrip': '新建旅行', + 'dashboard.gridView': '網格檢視', + 'dashboard.listView': '列表檢視', + 'dashboard.currency': '貨幣', + 'dashboard.timezone': '時區', + 'dashboard.localTime': '本地', + 'dashboard.timezoneCustomTitle': '自定義時區', + 'dashboard.timezoneCustomLabelPlaceholder': '標籤(可選)', + 'dashboard.timezoneCustomTzPlaceholder': '如 America/New_York', + 'dashboard.timezoneCustomAdd': '新增', + 'dashboard.timezoneCustomErrorEmpty': '請輸入時區識別符號', + 'dashboard.timezoneCustomErrorInvalid': + '無效的時區。請使用 Europe/Berlin 這樣的格式', + 'dashboard.timezoneCustomErrorDuplicate': '已新增', + 'dashboard.emptyTitle': '暫無旅行', + 'dashboard.emptyText': '建立你的第一次旅行,開始規劃吧!', + 'dashboard.emptyButton': '建立第一次旅行', + 'dashboard.nextTrip': '下次旅行', + 'dashboard.shared': '共享', + 'dashboard.sharedBy': '由 {name} 分享', + 'dashboard.days': '天', + 'dashboard.places': '地點', + 'dashboard.members': '旅伴', + 'dashboard.archive': '歸檔', + 'dashboard.copyTrip': '複製', + 'dashboard.copySuffix': '副本', + 'dashboard.restore': '恢復', + 'dashboard.archived': '已歸檔', + 'dashboard.status.ongoing': '進行中', + 'dashboard.status.today': '今天', + 'dashboard.status.tomorrow': '明天', + 'dashboard.status.past': '已結束', + 'dashboard.status.daysLeft': '還剩 {count} 天', + 'dashboard.toast.loadError': '載入旅行失敗', + 'dashboard.toast.created': '旅行建立成功!', + 'dashboard.toast.createError': '建立旅行失敗', + 'dashboard.toast.updated': '旅行已更新!', + 'dashboard.toast.updateError': '更新旅行失敗', + 'dashboard.toast.deleted': '旅行已刪除', + 'dashboard.toast.deleteError': '刪除旅行失敗', + 'dashboard.toast.archived': '旅行已歸檔', + 'dashboard.toast.archiveError': '歸檔旅行失敗', + 'dashboard.toast.restored': '旅行已恢復', + 'dashboard.toast.restoreError': '恢復旅行失敗', + 'dashboard.toast.copied': '旅行已複製!', + 'dashboard.toast.copyError': '複製旅行失敗', + 'dashboard.confirm.delete': + '刪除旅行「{title}」?所有地點和計劃將被永久刪除。', + 'dashboard.editTrip': '編輯旅行', + 'dashboard.createTrip': '建立新旅行', + 'dashboard.tripTitle': '標題', + 'dashboard.tripTitlePlaceholder': '如:日本夏日之旅', + 'dashboard.tripDescription': '描述', + 'dashboard.tripDescriptionPlaceholder': '這次旅行是關於什麼的?', + 'dashboard.startDate': '開始日期', + 'dashboard.endDate': '結束日期', + 'dashboard.dayCount': '天數', + 'dashboard.dayCountHint': '未設定旅行日期時,要規劃的天數。', + 'dashboard.noDateHint': '未設定日期——將預設建立 7 天。你可以隨時修改。', + 'dashboard.coverImage': '封面圖片', + 'dashboard.addCoverImage': '新增封面圖片', + 'dashboard.addMembers': '旅伴', + 'dashboard.addMember': '新增成員', + 'dashboard.coverSaved': '封面圖片已儲存', + 'dashboard.coverUploadError': '上傳失敗', + 'dashboard.coverRemoveError': '移除失敗', + 'dashboard.titleRequired': '標題為必填項', + 'dashboard.endDateError': '結束日期必須晚於開始日期', + 'dashboard.greeting.morning': '早安,', + 'dashboard.greeting.afternoon': '午安,', + 'dashboard.greeting.evening': '晚安,', + 'dashboard.mobile.liveNow': '進行中', + 'dashboard.mobile.tripProgress': '旅行進度', + 'dashboard.mobile.daysLeft': '還剩 {count} 天', + 'dashboard.mobile.places': '地點', + 'dashboard.mobile.buddies': '旅伴', + 'dashboard.mobile.newTrip': '新建旅行', + 'dashboard.mobile.currency': '貨幣', + 'dashboard.mobile.timezone': '時區', + 'dashboard.mobile.upcomingTrips': '即將到來的旅行', + 'dashboard.mobile.yourTrips': '我的旅行', + 'dashboard.mobile.trips': '個旅行', + 'dashboard.mobile.starts': '出發', + 'dashboard.mobile.duration': '時長', + 'dashboard.mobile.day': '天', + 'dashboard.mobile.days': '天', + 'dashboard.mobile.ongoing': '進行中', + 'dashboard.mobile.startsToday': '今天出發', + 'dashboard.mobile.tomorrow': '明天', + 'dashboard.mobile.inDays': '{count} 天後', + 'dashboard.mobile.inMonths': '{count} 個月後', + 'dashboard.mobile.completed': '已完成', + 'dashboard.mobile.currencyConverter': '匯率轉換', +}; +export default dashboard; diff --git a/shared/src/i18n/zh-TW/day.ts b/shared/src/i18n/zh-TW/day.ts new file mode 100644 index 00000000..59f24189 --- /dev/null +++ b/shared/src/i18n/zh-TW/day.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': '降水機率', + 'day.precipitation': '降水量', + 'day.wind': '風速', + 'day.sunrise': '日出', + 'day.sunset': '日落', + 'day.hourlyForecast': '逐小時預報', + 'day.climateHint': '歷史平均值——實際預報在該日期前 16 天內可用。', + 'day.noWeather': '無天氣資料。請新增有座標的地點。', + 'day.overview': '每日概覽', + 'day.accommodation': '住宿', + 'day.addAccommodation': '新增住宿', + 'day.hotelDayRange': '應用到天數', + 'day.noPlacesForHotel': '請先在旅行中新增地點', + 'day.allDays': '全部', + 'day.checkIn': '入住', + 'day.checkInUntil': '截止', + 'day.checkOut': '退房', + 'day.confirmation': '確認號', + 'day.editAccommodation': '編輯住宿', + 'day.reservations': '預訂', +}; +export default day; diff --git a/shared/src/i18n/zh-TW/dayplan.ts b/shared/src/i18n/zh-TW/dayplan.ts new file mode 100644 index 00000000..dbc99146 --- /dev/null +++ b/shared/src/i18n/zh-TW/dayplan.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': '匯出日曆 (ICS)', + 'dayplan.emptyDay': '當天暫無計劃', + 'dayplan.addNote': '新增備註', + 'dayplan.editNote': '編輯備註', + 'dayplan.noteAdd': '新增備註', + 'dayplan.noteEdit': '編輯備註', + 'dayplan.noteTitle': '備註', + 'dayplan.noteSubtitle': '每日備註', + 'dayplan.totalCost': '總費用', + 'dayplan.days': '天', + 'dayplan.dayN': '第 {n} 天', + 'dayplan.calculating': '計算中...', + 'dayplan.route': '路線', + 'dayplan.optimize': '最佳化', + 'dayplan.optimized': '路線已最佳化', + 'dayplan.routeError': '路線計算失敗', + 'dayplan.toast.needTwoPlaces': '路線最佳化至少需要兩個地點', + 'dayplan.toast.routeOptimized': '路線已最佳化', + 'dayplan.toast.noGeoPlaces': '未找到有座標的地點用於路線計算', + 'dayplan.confirmed': '已確認', + 'dayplan.pendingRes': '待確認', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': '匯出當天計劃為 PDF', + 'dayplan.pdfError': 'PDF 匯出失敗', + 'dayplan.cannotReorderTransport': '有固定時間的預訂無法重新排序', + 'dayplan.confirmRemoveTimeTitle': '移除時間?', + 'dayplan.confirmRemoveTimeBody': + '此地點有固定時間({time})。移動後將移除時間並允許自由排序。', + 'dayplan.confirmRemoveTimeAction': '移除時間並移動', + 'dayplan.cannotDropOnTimed': '無法將專案放置在有固定時間的條目之間', + 'dayplan.cannotBreakChronology': '這將打亂已計劃專案和預訂的時間順序', + 'dayplan.mobile.addPlace': '新增地點', + 'dayplan.mobile.searchPlaces': '搜尋地點...', + 'dayplan.mobile.allAssigned': '所有地點已分配', + 'dayplan.mobile.noMatch': '無匹配', + 'dayplan.mobile.createNew': '建立新地點', +}; +export default dayplan; diff --git a/shared/src/i18n/zh-TW/externalNotifications.ts b/shared/src/i18n/zh-TW/externalNotifications.ts new file mode 100644 index 00000000..2f5d28e2 --- /dev/null +++ b/shared/src/i18n/zh-TW/externalNotifications.ts @@ -0,0 +1,62 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const zhTW: NotificationLocale = { + email: { + footer: '您收到這封郵件是因為您在 TREK 中啟用了通知。', + manage: '管理偏好設定', + madeWith: 'Made with', + openTrek: '開啟 TREK', + }, + events: { + trip_invite: (p) => ({ + title: `邀請加入「${p.trip}」`, + body: `${p.actor} 邀請了 ${p.invitee || '成員'} 加入行程「${p.trip}」。`, + }), + booking_change: (p) => ({ + title: `新預訂:${p.booking}`, + body: `${p.actor} 在「${p.trip}」中新增了預訂「${p.booking}」(${p.type})。`, + }), + trip_reminder: (p) => ({ + title: `行程提醒:${p.trip}`, + body: `您的行程「${p.trip}」即將開始!`, + }), + todo_due: (p) => ({ + title: `待辦事項即將到期:${p.todo}`, + body: `「${p.trip}」中的「${p.todo}」將於 ${p.due} 到期。`, + }), + vacay_invite: (p) => ({ + title: 'Vacay 融合邀請', + body: `${p.actor} 邀請您合併假期計畫。開啟 TREK 以接受或拒絕。`, + }), + photos_shared: (p) => ({ + title: `已分享 ${p.count} 張照片`, + body: `${p.actor} 在「${p.trip}」中分享了 ${p.count} 張照片。`, + }), + collab_message: (p) => ({ + title: `「${p.trip}」中的新訊息`, + body: `${p.actor}:${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `打包清單:${p.category}`, + body: `${p.actor} 已將您指派到「${p.trip}」中的「${p.category}」分類。`, + }), + version_available: (p) => ({ + title: '新版 TREK 可用', + body: `TREK ${p.version} 現已可用。請前往管理面板進行更新。`, + }), + synology_session_cleared: () => ({ + title: 'Synology 工作階段已清除', + body: '您的 Synology 帳戶或 URL 已變更,您已登出 Synology Photos。', + }), + }, + passwordReset: { + subject: '重設您的密碼', + greeting: '您好', + body: '我們收到了重設您 TREK 帳號密碼的請求。點擊下方按鈕以設定新密碼。', + ctaIntro: '重設密碼', + expiry: '此連結將於 60 分鐘後失效。', + ignore: '若非您本人發起的請求,請忽略此郵件 — 您的密碼不會變更。', + }, +}; + +export default zhTW; diff --git a/shared/src/i18n/zh-TW/files.ts b/shared/src/i18n/zh-TW/files.ts new file mode 100644 index 00000000..ecd2e2e1 --- /dev/null +++ b/shared/src/i18n/zh-TW/files.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': '檔案', + 'files.pageTitle': '檔案與文件', + 'files.subtitle': '{trip} 的 {count} 個檔案', + 'files.download': '下載', + 'files.openError': '無法開啟檔案', + 'files.downloadPdf': '下載 PDF', + 'files.count': '{count} 個檔案', + 'files.countSingular': '1 個檔案', + 'files.uploaded': '已上傳 {count} 個', + 'files.uploadError': '上傳失敗', + 'files.dropzone': '將檔案拖放到此處', + 'files.dropzoneHint': '或點選瀏覽', + 'files.allowedTypes': + '圖片、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大 50 MB', + 'files.uploading': '上傳中...', + 'files.filterAll': '全部', + 'files.filterPdf': 'PDF', + 'files.filterImages': '圖片', + 'files.filterDocs': '文件', + 'files.filterCollab': '協作筆記', + 'files.sourceCollab': '來自協作筆記', + 'files.empty': '暫無檔案', + 'files.emptyHint': '上傳檔案以附加到旅行中', + 'files.openTab': '在新標籤頁中開啟', + 'files.confirm.delete': '確定要刪除此檔案嗎?', + 'files.toast.deleted': '檔案已刪除', + 'files.toast.deleteError': '刪除檔案失敗', + 'files.sourcePlan': '日程計劃', + 'files.sourceBooking': '預訂', + 'files.sourceTransport': '交通', + 'files.attach': '附加', + 'files.pasteHint': '也可以從剪貼簿貼上圖片 (Ctrl+V)', + 'files.trash': '回收站', + 'files.trashEmpty': '回收站為空', + 'files.emptyTrash': '清空回收站', + 'files.restore': '恢復', + 'files.star': '收藏', + 'files.unstar': '取消收藏', + 'files.assign': '分配', + 'files.assignTitle': '分配檔案', + 'files.assignPlace': '地點', + 'files.assignBooking': '預訂', + 'files.assignTransport': '交通', + 'files.unassigned': '未分配', + 'files.unlink': '移除關聯', + 'files.toast.trashed': '已移至回收站', + 'files.toast.restored': '檔案已恢復', + 'files.toast.trashEmptied': '回收站已清空', + 'files.toast.assigned': '檔案已分配', + 'files.toast.assignError': '分配失敗', + 'files.toast.restoreError': '恢復失敗', + 'files.confirm.permanentDelete': '永久刪除此檔案?此操作無法撤銷。', + 'files.confirm.emptyTrash': '永久刪除回收站中的所有檔案?此操作無法撤銷。', + 'files.noteLabel': '備註', + 'files.notePlaceholder': '新增備註...', +}; +export default files; diff --git a/shared/src/i18n/zh-TW/index.ts b/shared/src/i18n/zh-TW/index.ts new file mode 100644 index 00000000..b67d1baa --- /dev/null +++ b/shared/src/i18n/zh-TW/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...todo, + ...notifications, + ...journey, + ...notif, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/zh-TW/inspector.ts b/shared/src/i18n/zh-TW/inspector.ts new file mode 100644 index 00000000..65f9b2cd --- /dev/null +++ b/shared/src/i18n/zh-TW/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': '營業中', + 'inspector.closed': '已關閉', + 'inspector.openingHours': '營業時間', + 'inspector.showHours': '顯示營業時間', + 'inspector.files': '檔案', + 'inspector.filesCount': '{count} 個檔案', + 'inspector.removeFromDay': '從當天移除', + 'inspector.remove': '刪除', + 'inspector.addToDay': '新增到當天', + 'inspector.confirmedRes': '已確認預訂', + 'inspector.pendingRes': '待確認預訂', + 'inspector.google': '在 Google Maps 中開啟', + 'inspector.website': '開啟網站', + 'inspector.addRes': '預訂', + 'inspector.editRes': '編輯預訂', + 'inspector.participants': '參與者', + 'inspector.trackStats': '軌跡資料', +}; +export default inspector; diff --git a/shared/src/i18n/zh-TW/journey.ts b/shared/src/i18n/zh-TW/journey.ts new file mode 100644 index 00000000..40063142 --- /dev/null +++ b/shared/src/i18n/zh-TW/journey.ts @@ -0,0 +1,229 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': '搜尋旅程…', + 'journey.search.noResults': '沒有符合「{query}」的旅程', + 'journey.title': '旅程', + 'journey.subtitle': '即時記錄你的旅行', + 'journey.new': '新建旅程', + 'journey.create': '建立', + 'journey.titlePlaceholder': '你要去哪裡?', + 'journey.empty': '還沒有旅程', + 'journey.emptyHint': '開始記錄你的下一次旅行', + 'journey.deleted': '旅程已刪除', + 'journey.createError': '無法建立旅程', + 'journey.deleteError': '無法刪除旅程', + 'journey.deleteConfirmTitle': '刪除', + 'journey.deleteConfirmMessage': '刪除「{title}」?此操作無法復原。', + 'journey.deleteConfirmGeneric': '確定要刪除嗎?', + 'journey.notFound': '未找到旅程', + 'journey.photos': '照片', + 'journey.timelineEmpty': '還沒有行程', + 'journey.timelineEmptyHint': '新增一個打卡或寫一篇日誌開始記錄', + 'journey.status.draft': '草稿', + 'journey.status.active': '進行中', + 'journey.status.completed': '已完成', + 'journey.status.upcoming': '即將開始', + 'journey.status.archived': '已封存', + 'journey.checkin.add': '打卡', + 'journey.checkin.namePlaceholder': '地點名稱', + 'journey.checkin.notesPlaceholder': '備註(可選)', + 'journey.checkin.save': '儲存', + 'journey.checkin.error': '無法儲存打卡', + 'journey.entry.add': '日誌', + 'journey.entry.edit': '編輯條目', + 'journey.entry.titlePlaceholder': '標題(可選)', + 'journey.entry.bodyPlaceholder': '今天發生了什麼?', + 'journey.entry.save': '儲存', + 'journey.entry.error': '無法儲存條目', + 'journey.photo.add': '照片', + 'journey.photo.uploadError': '上傳失敗', + 'journey.share.share': '分享', + 'journey.share.public': '公開', + 'journey.share.linkCopied': '公開連結已複製', + 'journey.share.disabled': '已關閉公開分享', + 'journey.editor.titlePlaceholder': '給這個瞬間起個名字...', + 'journey.editor.bodyPlaceholder': '講述這一天的故事...', + 'journey.editor.placePlaceholder': '地點(可選)', + 'journey.editor.tagsPlaceholder': '標籤:隱藏寶藏、最佳美食、值得再訪...', + 'journey.visibility.private': '私密', + 'journey.visibility.shared': '共享', + 'journey.visibility.public': '公開', + 'journey.emptyState.title': '你的故事從這裡開始', + 'journey.emptyState.subtitle': '在某個地方打卡或寫下你的第一篇日誌', + 'journey.frontpage.subtitle': '將旅行變成永遠不會忘記的故事', + 'journey.frontpage.createJourney': '建立旅程', + 'journey.frontpage.activeJourney': '進行中的旅程', + 'journey.frontpage.allJourneys': '所有旅程', + 'journey.frontpage.journeys': '個旅程', + 'journey.frontpage.createNew': '建立新旅程', + 'journey.frontpage.createNewSub': '選擇旅行、寫故事、分享你的冒險', + 'journey.frontpage.live': '即時', + 'journey.frontpage.synced': '已同步', + 'journey.frontpage.continueWriting': '繼續撰寫', + 'journey.frontpage.updated': '更新於 {time}', + 'journey.frontpage.suggestionLabel': '旅行剛結束', + 'journey.frontpage.suggestionText': + '將 {title} 變成一段旅程', + 'journey.frontpage.dismiss': '忽略', + 'journey.frontpage.journeyName': '旅程名稱', + 'journey.frontpage.namePlaceholder': '例如 東南亞 2026', + 'journey.frontpage.selectTrips': '選擇旅行', + 'journey.frontpage.tripsSelected': '個旅行已選擇', + 'journey.frontpage.trips': '個旅行', + 'journey.frontpage.placesImported': '個地點將被匯入', + 'journey.frontpage.places': '個地點', + 'journey.detail.backToJourney': '返回旅程', + 'journey.detail.syncedWithTrips': '已與旅行同步', + 'journey.detail.addEntry': '新增條目', + 'journey.detail.newEntry': '新建條目', + 'journey.detail.editEntry': '編輯條目', + 'journey.detail.noEntries': '還沒有條目', + 'journey.detail.noEntriesHint': '新增一個旅行以產生骨架條目', + 'journey.detail.noPhotos': '還沒有照片', + 'journey.detail.noPhotosHint': + '上傳照片到條目或瀏覽你的 Immich/Synology 相簿', + 'journey.detail.journeyStats': '旅程統計', + 'journey.detail.syncedTrips': '已同步的旅行', + 'journey.detail.noTripsLinked': '尚未關聯旅行', + 'journey.detail.contributors': '貢獻者', + 'journey.detail.readMore': '閱讀更多', + 'journey.detail.prosCons': '優缺點', + 'journey.detail.photos': '照片', + 'journey.detail.day': '第{number}天', + 'journey.detail.places': '個地點', + 'journey.stats.days': '天', + 'journey.stats.cities': '城市', + 'journey.stats.entries': '條目', + 'journey.stats.photos': '照片', + 'journey.stats.places': '地點', + 'journey.skeletons.show': '顯示建議', + 'journey.skeletons.hide': '隱藏建議', + 'journey.verdict.lovedIt': '非常喜歡', + 'journey.verdict.couldBeBetter': '有待改進', + 'journey.synced.places': '個地點', + 'journey.synced.synced': '已同步', + 'journey.editor.discardChangesConfirm': '您有未儲存的變更。要放棄嗎?', + 'journey.editor.uploadFailed': '照片上傳失敗', + 'journey.editor.uploadPhotos': '上傳照片', + 'journey.editor.uploading': '上傳中...', + 'journey.editor.uploadingProgress': '上傳中 {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{total} 張中有 {failed} 張上傳失敗 — 再次儲存以重試', + 'journey.editor.fromGallery': '從相簿', + 'journey.editor.allPhotosAdded': '所有照片已新增', + 'journey.editor.writeStory': '寫下你的故事...', + 'journey.editor.prosCons': '優缺點', + 'journey.editor.pros': '優點', + 'journey.editor.cons': '缺點', + 'journey.editor.proPlaceholder': '好的方面...', + 'journey.editor.conPlaceholder': '不好的方面...', + 'journey.editor.addAnother': '再新增一個', + 'journey.editor.date': '日期', + 'journey.editor.location': '地點', + 'journey.editor.searchLocation': '搜尋地點...', + 'journey.editor.mood': '心情', + 'journey.editor.weather': '天氣', + 'journey.editor.photoFirst': '第1張', + 'journey.editor.makeFirst': '設為第1張', + 'journey.editor.searching': '搜尋中...', + 'journey.mood.amazing': '太棒了', + 'journey.mood.good': '不錯', + 'journey.mood.neutral': '一般', + 'journey.mood.rough': '糟糕', + 'journey.weather.sunny': '晴天', + 'journey.weather.partly': '多雲', + 'journey.weather.cloudy': '陰天', + 'journey.weather.rainy': '雨天', + 'journey.weather.stormy': '暴風雨', + 'journey.weather.cold': '雪天', + 'journey.trips.linkTrip': '關聯旅行', + 'journey.trips.searchTrip': '搜尋旅行', + 'journey.trips.searchPlaceholder': '旅行名稱或目的地...', + 'journey.trips.noTripsAvailable': '沒有可用的旅行', + 'journey.trips.link': '關聯', + 'journey.trips.tripLinked': '旅行已關聯', + 'journey.trips.linkFailed': '關聯旅行失敗', + 'journey.trips.addTrip': '新增旅行', + 'journey.trips.unlinkTrip': '取消關聯旅行', + 'journey.trips.unlinkMessage': + '取消關聯「{title}」?此旅行中所有已同步的條目和照片將被永久刪除。此操作無法復原。', + 'journey.trips.unlink': '取消關聯', + 'journey.trips.tripUnlinked': '旅行已取消關聯', + 'journey.trips.unlinkFailed': '取消關聯失敗', + 'journey.trips.noTripsLinkedSettings': '未關聯旅行', + 'journey.contributors.invite': '邀請貢獻者', + 'journey.contributors.searchUser': '搜尋使用者', + 'journey.contributors.searchPlaceholder': '使用者名稱或郵箱...', + 'journey.contributors.noUsers': '未找到使用者', + 'journey.contributors.role': '角色', + 'journey.contributors.added': '貢獻者已新增', + 'journey.contributors.addFailed': '新增貢獻者失敗', + 'journey.share.publicShare': '公開分享', + 'journey.share.createLink': '建立分享連結', + 'journey.share.linkCreated': '分享連結已建立', + 'journey.share.createFailed': '建立連結失敗', + 'journey.share.copy': '複製', + 'journey.share.copied': '已複製!', + 'journey.share.timeline': '時間線', + 'journey.share.gallery': '圖庫', + 'journey.share.map': '地圖', + 'journey.share.removeLink': '移除分享連結', + 'journey.share.linkDeleted': '分享連結已刪除', + 'journey.share.deleteFailed': '刪除失敗', + 'journey.share.updateFailed': '更新失敗', + 'journey.invite.role': '角色', + 'journey.invite.viewer': '檢視者', + 'journey.invite.editor': '編輯者', + 'journey.invite.invite': '邀請', + 'journey.invite.inviting': '邀請中...', + 'journey.settings.title': '旅程設定', + 'journey.settings.coverImage': '封面圖片', + 'journey.settings.changeCover': '更換封面', + 'journey.settings.addCover': '新增封面圖片', + 'journey.settings.name': '名稱', + 'journey.settings.subtitle': '副標題', + 'journey.settings.subtitlePlaceholder': '例如 泰國、越南和柬埔寨', + 'journey.settings.endJourney': '封存旅程', + 'journey.settings.reopenJourney': '還原旅程', + 'journey.settings.archived': '旅程已封存', + 'journey.settings.reopened': '旅程已重新開啟', + 'journey.settings.endDescription': '隱藏直播標記。您可以隨時重新開啟。', + 'journey.settings.delete': '刪除', + 'journey.settings.deleteJourney': '刪除旅程', + 'journey.settings.deleteMessage': '刪除「{title}」?所有條目和照片將遺失。', + 'journey.settings.saved': '設定已儲存', + 'journey.settings.saveFailed': '儲存失敗', + 'journey.settings.coverUpdated': '封面已更新', + 'journey.settings.coverFailed': '上傳失敗', + 'journey.settings.failedToDelete': '刪除失敗', + 'journey.entries.deleteTitle': '刪除條目', + 'journey.photosUploaded': '{count} 張照片已上傳', + 'journey.photosUploadFailed': '部分照片上傳失敗', + 'journey.photosAdded': '{count} 張照片已新增', + 'journey.public.notFound': '未找到', + 'journey.public.notFoundMessage': '此旅程不存在或連結已過期。', + 'journey.public.readOnly': '唯讀 · 公開旅程', + 'journey.public.tagline': '旅行資源與探索工具包', + 'journey.public.sharedVia': '分享自', + 'journey.public.madeWith': '由', + 'journey.pdf.journeyBook': '旅程手冊', + 'journey.pdf.madeWith': '由 TREK 製作', + 'journey.pdf.day': '第', + 'journey.pdf.theEnd': '終', + 'journey.pdf.saveAsPdf': '儲存為 PDF', + 'journey.pdf.pages': '頁', + 'journey.picker.tripPeriod': '旅行期間', + 'journey.picker.dateRange': '日期範圍', + 'journey.picker.allPhotos': '所有照片', + 'journey.picker.albums': '相簿', + 'journey.picker.selected': '已選擇', + 'journey.picker.addTo': '新增至', + 'journey.picker.newGallery': '新相簿', + 'journey.picker.selectAll': '全選', + 'journey.picker.deselectAll': '取消全選', + 'journey.picker.noAlbums': '未找到相簿', + 'journey.picker.selectDate': '選擇日期', + 'journey.picker.search': '搜尋', +}; +export default journey; diff --git a/shared/src/i18n/zh-TW/login.ts b/shared/src/i18n/zh-TW/login.ts new file mode 100644 index 00000000..78ddd040 --- /dev/null +++ b/shared/src/i18n/zh-TW/login.ts @@ -0,0 +1,88 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': '登入失敗,請檢查你的憑據。', + 'login.tagline': '你的旅行。\n你的計劃。', + 'login.description': '透過互動地圖、預算管理和即時同步,協同規劃旅行。', + 'login.features.maps': '互動地圖', + 'login.features.mapsDesc': 'Google Places、路線和聚類', + 'login.features.realtime': '即時同步', + 'login.features.realtimeDesc': '透過 WebSocket 協同規劃', + 'login.features.budget': '預算跟蹤', + 'login.features.budgetDesc': '分類、圖表和人均費用', + 'login.features.collab': '協作', + 'login.features.collabDesc': '多使用者共享旅行', + 'login.features.packing': '行李清單', + 'login.features.packingDesc': '分類、進度和建議', + 'login.features.bookings': '預訂', + 'login.features.bookingsDesc': '航班、酒店、餐廳等', + 'login.features.files': '文件', + 'login.features.filesDesc': '上傳和管理文件', + 'login.features.routes': '智慧路線', + 'login.features.routesDesc': '自動最佳化和匯出到 Google Maps', + 'login.selfHosted': '自託管 · 開源 · 資料由你掌控', + 'login.title': '登入', + 'login.subtitle': '歡迎回來', + 'login.signingIn': '登入中…', + 'login.signIn': '登入', + 'login.createAdmin': '建立管理員賬戶', + 'login.createAdminHint': '為 TREK 設定第一個管理員賬戶。', + 'login.setNewPassword': '設定新密碼', + 'login.setNewPasswordHint': '您必須更改密碼才能繼續。', + 'login.createAccount': '建立賬戶', + 'login.createAccountHint': '註冊新賬戶。', + 'login.creating': '建立中…', + 'login.noAccount': '還沒有賬戶?', + 'login.hasAccount': '已有賬戶?', + 'login.register': '註冊', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': '使用者名稱', + 'login.oidc.registrationDisabled': '註冊已關閉。請聯絡管理員。', + 'login.oidc.noEmail': '未從提供商獲取到郵箱。', + 'login.mfaTitle': '雙因素認證', + 'login.mfaSubtitle': '請輸入身份驗證器應用中的 6 位驗證碼。', + 'login.mfaCodeLabel': '驗證碼', + 'login.mfaCodeRequired': '請輸入身份驗證器應用中的驗證碼。', + 'login.mfaHint': '開啟 Google Authenticator、Authy 或其他 TOTP 應用。', + 'login.mfaBack': '← 返回登入', + 'login.mfaVerify': '驗證', + 'login.invalidInviteLink': '邀請連結無效或已過期', + 'login.oidcFailed': 'OIDC 登入失敗', + 'login.usernameRequired': '使用者名稱為必填', + 'login.passwordMinLength': '密碼至少需要8個字元', + 'login.forgotPassword': '忘記密碼?', + 'login.forgotPasswordTitle': '重設密碼', + 'login.forgotPasswordBody': + '請輸入您註冊時使用的電子郵件。若帳號存在,我們將傳送重設連結。', + 'login.forgotPasswordSubmit': '傳送重設連結', + 'login.forgotPasswordSentTitle': '請查看您的電子郵件', + 'login.forgotPasswordSentBody': + '若此電子郵件存在帳號,重設連結正在傳送中。連結將於 60 分鐘後失效。', + 'login.forgotPasswordSmtpHintOff': + '提醒:管理員尚未設定 SMTP,重設連結將寫入伺服器控制台,而非透過電子郵件寄送。', + 'login.backToLogin': '返回登入', + 'login.newPassword': '新密碼', + 'login.confirmPassword': '確認新密碼', + 'login.passwordsDontMatch': '兩次輸入的密碼不一致', + 'login.mfaCode': '2FA 驗證碼', + 'login.resetPasswordTitle': '設定新密碼', + 'login.resetPasswordBody': + '請選擇您在此處尚未使用過的強密碼。至少 8 個字元。', + 'login.resetPasswordMfaBody': '請輸入您的 2FA 驗證碼或備用代碼以完成重設。', + 'login.resetPasswordSubmit': '重設密碼', + 'login.resetPasswordVerify': '驗證並重設', + 'login.resetPasswordSuccessTitle': '密碼已更新', + 'login.resetPasswordSuccessBody': '您現在可以使用新密碼登入。', + 'login.resetPasswordInvalidLink': '無效的重設連結', + 'login.resetPasswordInvalidLinkBody': + '此連結遺失或已損壞。請重新申請以繼續。', + 'login.resetPasswordFailed': '重設失敗。連結可能已過期。', + 'login.oidc.tokenFailed': '認證失敗。', + 'login.oidc.invalidState': '會話無效,請重試。', + 'login.demoFailed': '演示登入失敗', + 'login.oidcSignIn': '透過 {name} 登入', + 'login.oidcOnly': '密碼登入已關閉。請透過 SSO 提供商登入。', + 'login.oidcLoggedOut': '您已登出。請重新透過 SSO 提供商登入。', + 'login.demoHint': '試用演示——無需註冊', +}; +export default login; diff --git a/shared/src/i18n/zh-TW/map.ts b/shared/src/i18n/zh-TW/map.ts new file mode 100644 index 00000000..09343d02 --- /dev/null +++ b/shared/src/i18n/zh-TW/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': '連接', + 'map.showConnections': '顯示預訂路線', + 'map.hideConnections': '隱藏預訂路線', +}; +export default map; diff --git a/shared/src/i18n/zh-TW/members.ts b/shared/src/i18n/zh-TW/members.ts new file mode 100644 index 00000000..17049fe3 --- /dev/null +++ b/shared/src/i18n/zh-TW/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': '分享旅行', + 'members.inviteUser': '邀請使用者', + 'members.selectUser': '選擇使用者…', + 'members.invite': '邀請', + 'members.allHaveAccess': '所有使用者均已擁有訪問許可權。', + 'members.access': '訪問許可權', + 'members.person': '人', + 'members.persons': '人', + 'members.you': '你', + 'members.owner': '所有者', + 'members.leaveTrip': '退出旅行', + 'members.removeAccess': '移除訪問許可權', + 'members.confirmLeave': '退出旅行?你將失去訪問許可權。', + 'members.confirmRemove': '移除該使用者的訪問許可權?', + 'members.loadError': '載入成員失敗', + 'members.added': '已新增', + 'members.addError': '新增失敗', + 'members.removed': '成員已移除', + 'members.removeError': '移除失敗', +}; +export default members; diff --git a/shared/src/i18n/zh-TW/memories.ts b/shared/src/i18n/zh-TW/memories.ts new file mode 100644 index 00000000..0d76375f --- /dev/null +++ b/shared/src/i18n/zh-TW/memories.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': '照片', + 'memories.notConnected': '{provider_name} 未連線', + 'memories.notConnectedHint': + '在設定中連線您的 {provider_name} 例項以在此旅行中新增照片。', + 'memories.notConnectedMultipleHint': + '在設定中連線以下任一照片提供商:{provider_names} 以在此旅行中新增照片。', + 'memories.noDates': '為旅行新增日期以載入照片。', + 'memories.noPhotos': '未找到照片', + 'memories.noPhotosHint': '{provider_name} 中未找到此旅行日期範圍內的照片。', + 'memories.photosFound': '張照片', + 'memories.fromOthers': '來自他人', + 'memories.sharePhotos': '分享照片', + 'memories.sharing': '分享中', + 'memories.reviewTitle': '審查您的照片', + 'memories.reviewHint': '點選照片以將其從分享中排除。', + 'memories.shareCount': '分享 {count} 張照片', + 'memories.providerUrl': '伺服器 URL', + 'memories.providerApiKey': 'API 金鑰', + 'memories.providerUsername': '使用者名稱', + 'memories.providerPassword': '密碼', + 'memories.providerOTP': 'MFA 驗證碼(如已啟用)', + 'memories.skipSSLVerification': '跳過 SSL 憑證驗證', + 'memories.immichAutoUpload': '上傳 Journey 照片時同步到 Immich', + 'memories.providerUrlHintSynology': + '在網址中包含照片應用程式路徑,例如 https://nas:5001/photo', + 'memories.testConnection': '測試連線', + 'memories.testShort': '測試', + 'memories.testFirst': '請先測試連線', + 'memories.connected': '已連線', + 'memories.disconnected': '未連線', + 'memories.connectionSuccess': '已連線到 {provider_name}', + 'memories.connectionError': '無法連線到 {provider_name}', + 'memories.saved': '{provider_name} 設定已儲存', + 'memories.providerDisconnectedBanner': + '您與 {provider_name} 的連線已中斷。請在設定中重新連線以查看照片。', + 'memories.saveError': '無法儲存 {provider_name} 設定', + 'memories.oldest': '最早優先', + 'memories.newest': '最新優先', + 'memories.allLocations': '所有地點', + 'memories.addPhotos': '新增照片', + 'memories.linkAlbum': '關聯相簿', + 'memories.selectAlbum': '選擇 {provider_name} 相簿', + 'memories.selectAlbumMultiple': '選擇相簿', + 'memories.noAlbums': '未找到相簿', + 'memories.syncAlbum': '同步相簿', + 'memories.unlinkAlbum': '取消關聯', + 'memories.photos': '張照片', + 'memories.selectPhotos': '從 {provider_name} 選擇照片', + 'memories.selectPhotosMultiple': '選擇照片', + 'memories.selectHint': '點選照片以選擇。', + 'memories.selected': '已選擇', + 'memories.addSelected': '新增 {count} 張照片', + 'memories.alreadyAdded': '已新增', + 'memories.private': '私密', + 'memories.stopSharing': '停止分享', + 'memories.tripDates': '旅行日期', + 'memories.allPhotos': '所有照片', + 'memories.confirmShareTitle': '與旅行成員分享?', + 'memories.confirmShareHint': + '{count} 張照片將對本次旅行的所有成員可見。你可以稍後將單張照片設為私密。', + 'memories.confirmShareButton': '分享照片', + 'memories.error.loadAlbums': '載入相簿失敗', + 'memories.error.linkAlbum': '關聯相簿失敗', + 'memories.error.unlinkAlbum': '取消關聯相簿失敗', + 'memories.error.syncAlbum': '同步相簿失敗', + 'memories.error.loadPhotos': '載入照片失敗', + 'memories.error.addPhotos': '新增照片失敗', + 'memories.error.removePhoto': '刪除照片失敗', + 'memories.error.toggleSharing': '更新共享設定失敗', + 'memories.saveRouteNotConfigured': '此提供商未設定儲存路由', + 'memories.testRouteNotConfigured': '此提供商未設定測試路由', + 'memories.fillRequiredFields': '請填寫所有必填欄位', +}; +export default memories; diff --git a/shared/src/i18n/zh-TW/nav.ts b/shared/src/i18n/zh-TW/nav.ts new file mode 100644 index 00000000..35f4ec90 --- /dev/null +++ b/shared/src/i18n/zh-TW/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': '旅行', + 'nav.share': '分享', + 'nav.settings': '設定', + 'nav.admin': '管理', + 'nav.logout': '退出登入', + 'nav.lightMode': '淺色模式', + 'nav.darkMode': '深色模式', + 'nav.autoMode': '自動模式', + 'nav.administrator': '管理員', + 'nav.myTrips': '我的旅行', + 'nav.profile': '個人資料', + 'nav.bottomSettings': '設定', + 'nav.bottomAdmin': '管理設定', + 'nav.bottomLogout': '退出登入', + 'nav.bottomAdminBadge': '管理員', +}; +export default nav; diff --git a/shared/src/i18n/zh-TW/notif.ts b/shared/src/i18n/zh-TW/notif.ts new file mode 100644 index 00000000..f6abef7a --- /dev/null +++ b/shared/src/i18n/zh-TW/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[測試] 通知', + 'notif.test.simple.text': '這是一則簡單的測試通知。', + 'notif.test.boolean.text': '你是否接受這則測試通知?', + 'notif.test.navigate.text': '點擊下方前往儀表板。', + 'notif.trip_invite.title': '旅行邀請', + 'notif.trip_invite.text': '{actor} 邀請你加入 {trip}', + 'notif.booking_change.title': '預訂已更新', + 'notif.booking_change.text': '{actor} 更新了 {trip} 中的預訂', + 'notif.trip_reminder.title': '旅行提醒', + 'notif.trip_reminder.text': '你的旅行 {trip} 即將開始!', + 'notif.todo_due.title': '待辦事項即將到期', + 'notif.todo_due.text': '{trip} 中的 {todo} 將於 {due} 到期', + 'notif.vacay_invite.title': 'Vacay Fusion 邀請', + 'notif.vacay_invite.text': '{actor} 邀請你合併假期計畫', + 'notif.photos_shared.title': '照片已分享', + 'notif.photos_shared.text': '{actor} 在 {trip} 中分享了 {count} 張照片', + 'notif.collab_message.title': '新訊息', + 'notif.collab_message.text': '{actor} 在 {trip} 中傳送了一則訊息', + 'notif.packing_tagged.title': '打包指派', + 'notif.packing_tagged.text': '{actor} 在 {trip} 中將 {category} 指派給你', + 'notif.version_available.title': '有新版本可用', + 'notif.version_available.text': 'TREK {version} 現已推出', + 'notif.action.view_trip': '查看旅行', + 'notif.action.view_collab': '查看訊息', + 'notif.action.view_packing': '查看打包', + 'notif.action.view_photos': '查看照片', + 'notif.action.view_vacay': '查看 Vacay', + 'notif.action.view_admin': '前往管理', + 'notif.action.view': '查看', + 'notif.action.accept': '接受', + 'notif.action.decline': '拒絕', + 'notif.generic.title': '通知', + 'notif.generic.text': '你有一則新通知', + 'notif.dev.unknown_event.title': '[DEV] 未知事件', + 'notif.dev.unknown_event.text': + '事件類型「{event}」未在 EVENT_NOTIFICATION_CONFIG 中註冊', +}; +export default notif; diff --git a/shared/src/i18n/zh-TW/notifications.ts b/shared/src/i18n/zh-TW/notifications.ts new file mode 100644 index 00000000..afd9fc37 --- /dev/null +++ b/shared/src/i18n/zh-TW/notifications.ts @@ -0,0 +1,36 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': '通知', + 'notifications.markAllRead': '全部標為已讀', + 'notifications.deleteAll': '全部刪除', + 'notifications.showAll': '檢視所有通知', + 'notifications.empty': '暫無通知', + 'notifications.emptyDescription': '您已全部查閱!', + 'notifications.all': '全部', + 'notifications.unreadOnly': '未讀', + 'notifications.markRead': '標為已讀', + 'notifications.markUnread': '標為未讀', + 'notifications.delete': '刪除', + 'notifications.system': '系統', + 'notifications.synologySessionCleared.title': 'Synology Photos 已斷線', + 'notifications.synologySessionCleared.text': + '您的伺服器或帳號已更改 — 請前往設定重新測試連線。', + 'notifications.test.title': '來自 {actor} 的測試通知', + 'notifications.test.text': '這是一條簡單的測試通知。', + 'notifications.test.booleanTitle': '{actor} 請求您的審批', + 'notifications.test.booleanText': '測試布林通知。', + 'notifications.test.accept': '批准', + 'notifications.test.decline': '拒絕', + 'notifications.test.navigateTitle': '檢視詳情', + 'notifications.test.navigateText': '測試跳轉通知。', + 'notifications.test.goThere': '前往', + 'notifications.test.adminTitle': '管理員廣播', + 'notifications.test.adminText': '{actor} 向所有管理員傳送了測試通知。', + 'notifications.test.tripTitle': '{actor} 在您的行程中發帖', + 'notifications.test.tripText': '行程"{trip}"的測試通知。', + 'notifications.versionAvailable.title': '有可用更新', + 'notifications.versionAvailable.text': 'TREK {version} 現已推出。', + 'notifications.versionAvailable.button': '查看詳情', +}; +export default notifications; diff --git a/shared/src/i18n/zh-TW/oauth.ts b/shared/src/i18n/zh-TW/oauth.ts new file mode 100644 index 00000000..f96c4cd7 --- /dev/null +++ b/shared/src/i18n/zh-TW/oauth.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': '行程', + 'oauth.scope.group.places': '地點', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': '行李', + 'oauth.scope.group.todos': '待辦事項', + 'oauth.scope.group.budget': '預算', + 'oauth.scope.group.reservations': '預訂', + 'oauth.scope.group.collab': '協作', + 'oauth.scope.group.notifications': '通知', + 'oauth.scope.group.vacay': '假期', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': '天氣', + 'oauth.scope.group.journey': '旅程', + 'oauth.scope.trips:read.label': '檢視行程與旅遊計畫', + 'oauth.scope.trips:read.description': '讀取行程、天數、每日筆記及成員', + 'oauth.scope.trips:write.label': '編輯行程與旅遊計畫', + 'oauth.scope.trips:write.description': '建立及更新行程、天數、筆記並管理成員', + 'oauth.scope.trips:delete.label': '刪除行程', + 'oauth.scope.trips:delete.description': '永久刪除整個行程——此操作無法復原', + 'oauth.scope.trips:share.label': '管理分享連結', + 'oauth.scope.trips:share.description': '建立、更新及撤銷行程的公開分享連結', + 'oauth.scope.places:read.label': '檢視地點與地圖資料', + 'oauth.scope.places:read.description': '讀取地點、每日指派、標籤及類別', + 'oauth.scope.places:write.label': '管理地點', + 'oauth.scope.places:write.description': '建立、更新及刪除地點、指派及標籤', + 'oauth.scope.atlas:read.label': '檢視 Atlas', + 'oauth.scope.atlas:read.description': '讀取已造訪的國家、地區及願望清單', + 'oauth.scope.atlas:write.label': '管理 Atlas', + 'oauth.scope.atlas:write.description': '標記已造訪的國家及地區,管理願望清單', + 'oauth.scope.packing:read.label': '檢視行李清單', + 'oauth.scope.packing:read.description': '讀取行李物品、行李袋及類別負責人', + 'oauth.scope.packing:write.label': '管理行李清單', + 'oauth.scope.packing:write.description': + '新增、更新、刪除、勾選及重新排序行李物品和行李袋', + 'oauth.scope.todos:read.label': '檢視待辦清單', + 'oauth.scope.todos:read.description': '讀取行程待辦事項及類別負責人', + 'oauth.scope.todos:write.label': '管理待辦清單', + 'oauth.scope.todos:write.description': + '建立、更新、勾選、刪除及重新排序待辦事項', + 'oauth.scope.budget:read.label': '檢視預算', + 'oauth.scope.budget:read.description': '讀取預算項目及費用明細', + 'oauth.scope.budget:write.label': '管理預算', + 'oauth.scope.budget:write.description': '建立、更新及刪除預算項目', + 'oauth.scope.reservations:read.label': '檢視預訂', + 'oauth.scope.reservations:read.description': '讀取預訂及住宿詳情', + 'oauth.scope.reservations:write.label': '管理預訂', + 'oauth.scope.reservations:write.description': + '建立、更新、刪除及重新排序預訂', + 'oauth.scope.collab:read.label': '檢視協作', + 'oauth.scope.collab:read.description': '讀取協作筆記、投票及訊息', + 'oauth.scope.collab:write.label': '管理協作', + 'oauth.scope.collab:write.description': + '建立、更新及刪除協作筆記、投票及訊息', + 'oauth.scope.notifications:read.label': '檢視通知', + 'oauth.scope.notifications:read.description': '讀取應用程式通知及未讀數量', + 'oauth.scope.notifications:write.label': '管理通知', + 'oauth.scope.notifications:write.description': '將通知標為已讀並回覆', + 'oauth.scope.vacay:read.label': '檢視假期計畫', + 'oauth.scope.vacay:read.description': '讀取假期計畫資料、項目及統計', + 'oauth.scope.vacay:write.label': '管理假期計畫', + 'oauth.scope.vacay:write.description': '建立及管理假期項目、節假日及團隊計畫', + 'oauth.scope.geo:read.label': '地圖與地理編碼', + 'oauth.scope.geo:read.description': + '搜尋地點、解析地圖 URL 及反向地理編碼坐標', + 'oauth.scope.weather:read.label': '天氣預報', + 'oauth.scope.weather:read.description': '取得行程地點及日期的天氣預報', + 'oauth.scope.journey:read.label': '檢視旅程', + 'oauth.scope.journey:read.description': '讀取旅程、條目及貢獻者清單', + 'oauth.scope.journey:write.label': '管理旅程', + 'oauth.scope.journey:write.description': '建立、更新及刪除旅程及其條目', + 'oauth.scope.journey:share.label': '管理旅程連結', + 'oauth.scope.journey:share.description': '建立、更新及撤銷旅程的公開分享連結', +}; +export default oauth; diff --git a/shared/src/i18n/zh-TW/packing.ts b/shared/src/i18n/zh-TW/packing.ts new file mode 100644 index 00000000..01de6880 --- /dev/null +++ b/shared/src/i18n/zh-TW/packing.ts @@ -0,0 +1,183 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': '行李清單', + 'packing.empty': '行李清單為空', + 'packing.import': '匯入', + 'packing.importTitle': '匯入裝箱清單', + 'packing.importHint': + '每行一個物品。可選用逗號、分號或製表符分隔類別和數量:名稱, 類別, 數量', + 'packing.importPlaceholder': '牙刷\n防曬霜, 衛生\nT恤, 衣物, 5\n護照, 證件', + 'packing.importCsv': '載入 CSV/TXT', + 'packing.importAction': '匯入 {count}', + 'packing.importSuccess': '已匯入 {count} 項', + 'packing.importError': '匯入失敗', + 'packing.importEmpty': '沒有可匯入的專案', + 'packing.progress': '已打包 {packed}/{total}({percent}%)', + 'packing.clearChecked': '移除 {count} 個已勾選', + 'packing.clearCheckedShort': '移除 {count} 個', + 'packing.suggestions': '建議', + 'packing.suggestionsTitle': '新增建議', + 'packing.allSuggested': '所有建議已新增', + 'packing.allPacked': '全部打包完成!', + 'packing.addPlaceholder': '新增新物品...', + 'packing.categoryPlaceholder': '分類...', + 'packing.filterAll': '全部', + 'packing.filterOpen': '未完成', + 'packing.filterDone': '已完成', + 'packing.emptyTitle': '行李清單為空', + 'packing.emptyHint': '新增物品或使用建議', + 'packing.emptyFiltered': '沒有匹配的物品', + 'packing.menuRename': '重新命名', + 'packing.menuCheckAll': '全部勾選', + 'packing.menuUncheckAll': '取消全部勾選', + 'packing.menuDeleteCat': '刪除分類', + 'packing.addItem': '新增物品', + 'packing.addItemPlaceholder': '物品名稱...', + 'packing.addCategory': '新增分類', + 'packing.newCategoryPlaceholder': '分類名稱(如:衣物)', + 'packing.applyTemplate': '應用模板', + 'packing.template': '模板', + 'packing.templateApplied': '已從模板新增 {count} 個物品', + 'packing.templateError': '應用模板失敗', + 'packing.saveAsTemplate': '儲存為範本', + 'packing.templateName': '範本名稱', + 'packing.templateSaved': '行李清單已儲存為範本', + 'packing.noMembers': '無成員', + 'packing.bags': '行李', + 'packing.noBag': '未分配', + 'packing.totalWeight': '總重量', + 'packing.bagName': '名稱...', + 'packing.addBag': '新增行李', + 'packing.changeCategory': '更改分類', + 'packing.confirm.clearChecked': '確定移除 {count} 個已勾選的物品?', + 'packing.confirm.deleteCat': '確定刪除分類「{name}」及其 {count} 個物品?', + 'packing.defaultCategory': '其他', + 'packing.toast.saveError': '儲存失敗', + 'packing.toast.deleteError': '刪除失敗', + 'packing.toast.renameError': '重新命名失敗', + 'packing.toast.addError': '新增失敗', + 'packing.suggestions.items': [ + { + name: '護照', + category: '證件', + }, + { + name: '身份證', + category: '證件', + }, + { + name: '旅行保險', + category: '證件', + }, + { + name: '機票', + category: '證件', + }, + { + name: '信用卡', + category: '財務', + }, + { + name: '現金', + category: '財務', + }, + { + name: '簽證', + category: '證件', + }, + { + name: 'T恤', + category: '衣物', + }, + { + name: '褲子', + category: '衣物', + }, + { + name: '內衣', + category: '衣物', + }, + { + name: '襪子', + category: '衣物', + }, + { + name: '外套', + category: '衣物', + }, + { + name: '睡衣', + category: '衣物', + }, + { + name: '泳衣', + category: '衣物', + }, + { + name: '雨衣', + category: '衣物', + }, + { + name: '舒適的鞋子', + category: '衣物', + }, + { + name: '牙刷', + category: '洗漱用品', + }, + { + name: '牙膏', + category: '洗漱用品', + }, + { + name: '洗髮水', + category: '洗漱用品', + }, + { + name: '除臭劑', + category: '洗漱用品', + }, + { + name: '防曬霜', + category: '洗漱用品', + }, + { + name: '剃鬚刀', + category: '洗漱用品', + }, + { + name: '充電器', + category: '電子產品', + }, + { + name: '充電寶', + category: '電子產品', + }, + { + name: '耳機', + category: '電子產品', + }, + { + name: '旅行轉換插頭', + category: '電子產品', + }, + { + name: '相機', + category: '電子產品', + }, + { + name: '止痛藥', + category: '健康', + }, + { + name: '創可貼', + category: '健康', + }, + { + name: '消毒液', + category: '健康', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/zh-TW/pdf.ts b/shared/src/i18n/zh-TW/pdf.ts new file mode 100644 index 00000000..9636e197 --- /dev/null +++ b/shared/src/i18n/zh-TW/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': '旅行計劃', + 'pdf.planned': '已規劃', + 'pdf.costLabel': '費用 EUR', + 'pdf.preview': 'PDF 預覽', + 'pdf.saveAsPdf': '儲存為 PDF', +}; +export default pdf; diff --git a/shared/src/i18n/zh-TW/perm.ts b/shared/src/i18n/zh-TW/perm.ts new file mode 100644 index 00000000..e37e6ebe --- /dev/null +++ b/shared/src/i18n/zh-TW/perm.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': '許可權設定', + 'perm.subtitle': '控制誰可以在應用中執行操作', + 'perm.saved': '許可權設定已儲存', + 'perm.resetDefaults': '恢復預設', + 'perm.customized': '已自定義', + 'perm.level.admin': '僅管理員', + 'perm.level.tripOwner': '旅行所有者', + 'perm.level.tripMember': '旅行成員', + 'perm.level.everybody': '所有人', + 'perm.cat.trip': '旅行管理', + 'perm.cat.members': '成員管理', + 'perm.cat.files': '檔案', + 'perm.cat.content': '內容與日程', + 'perm.cat.extras': '預算、行李與協作', + 'perm.action.trip_create': '建立旅行', + 'perm.action.trip_edit': '編輯旅行詳情', + 'perm.action.trip_delete': '刪除旅行', + 'perm.action.trip_archive': '歸檔 / 取消歸檔旅行', + 'perm.action.trip_cover_upload': '上傳封面圖片', + 'perm.action.member_manage': '新增 / 移除成員', + 'perm.action.file_upload': '上傳檔案', + 'perm.action.file_edit': '編輯檔案後設資料', + 'perm.action.file_delete': '刪除檔案', + 'perm.action.place_edit': '新增 / 編輯 / 刪除地點', + 'perm.action.day_edit': '編輯日程、備註與分配', + 'perm.action.reservation_edit': '管理預訂', + 'perm.action.budget_edit': '管理預算', + 'perm.action.packing_edit': '管理行李清單', + 'perm.action.collab_edit': '協作(筆記、投票、聊天)', + 'perm.action.share_manage': '管理分享連結', + 'perm.actionHint.trip_create': '誰可以建立新旅行', + 'perm.actionHint.trip_edit': '誰可以更改旅行名稱、日期、描述和貨幣', + 'perm.actionHint.trip_delete': '誰可以永久刪除旅行', + 'perm.actionHint.trip_archive': '誰可以歸檔或取消歸檔旅行', + 'perm.actionHint.trip_cover_upload': '誰可以上傳或更改封面圖片', + 'perm.actionHint.member_manage': '誰可以邀請或移除旅行成員', + 'perm.actionHint.file_upload': '誰可以向旅行上傳檔案', + 'perm.actionHint.file_edit': '誰可以編輯檔案描述和連結', + 'perm.actionHint.file_delete': '誰可以將檔案移至回收站或永久刪除', + 'perm.actionHint.place_edit': '誰可以新增、編輯或刪除地點', + 'perm.actionHint.day_edit': '誰可以編輯日程、日程備註和地點分配', + 'perm.actionHint.reservation_edit': '誰可以建立、編輯或刪除預訂', + 'perm.actionHint.budget_edit': '誰可以建立、編輯或刪除預算專案', + 'perm.actionHint.packing_edit': '誰可以管理行李物品和包袋', + 'perm.actionHint.collab_edit': '誰可以建立筆記、投票和傳送訊息', + 'perm.actionHint.share_manage': '誰可以建立或刪除公開分享連結', +}; +export default perm; diff --git a/shared/src/i18n/zh-TW/photos.ts b/shared/src/i18n/zh-TW/photos.ts new file mode 100644 index 00000000..2e8bcf43 --- /dev/null +++ b/shared/src/i18n/zh-TW/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': '照片', + 'photos.subtitle': '{trip} 的 {count} 張照片', + 'photos.dropHere': '將照片拖放至此...', + 'photos.dropHereActive': '將照片拖放至此', + 'photos.captionForAll': '標題(所有)', + 'photos.captionPlaceholder': '可選標題...', + 'photos.addCaption': '新增標題...', + 'photos.allDays': '所有天', + 'photos.noPhotos': '暫無照片', + 'photos.uploadHint': '上傳你的旅行照片', + 'photos.clickToSelect': '或點選選擇', + 'photos.linkPlace': '關聯地點', + 'photos.noPlace': '無地點', + 'photos.uploadN': '上傳 {n} 張照片', + 'photos.linkDay': '關聯天數', + 'photos.noDay': '無天數', + 'photos.dayLabel': '第 {number} 天', + 'photos.photoSelected': '張照片已選擇', + 'photos.photosSelected': '張照片已選擇', + 'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 張照片', +}; +export default photos; diff --git a/shared/src/i18n/zh-TW/places.ts b/shared/src/i18n/zh-TW/places.ts new file mode 100644 index 00000000..f52c7aea --- /dev/null +++ b/shared/src/i18n/zh-TW/places.ts @@ -0,0 +1,87 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': '新增地點/活動', + 'places.importFile': '匯入檔案', + 'places.sidebarDrop': '拖放以匯入', + 'places.importFileHint': + '從 Google My Maps、Google Earth 或 GPS 追蹤器等工具匯入 .gpx、.kml 或 .kmz 檔案。', + 'places.importFileDropHere': '點選以選取檔案或拖放至此處', + 'places.importFileDropActive': '放開檔案以選取', + 'places.importFileUnsupported': + '不支援的檔案類型,請使用 .gpx、.kml 或 .kmz。', + 'places.importFileTooLarge': '檔案過大。最大上傳大小為 {maxMb} MB。', + 'places.importFileError': '匯入失敗', + 'places.importAllSkipped': '所有地點已在行程中。', + 'places.gpxImported': '已從 GPX 匯入 {count} 個地點', + 'places.gpxImportTypes': '要匯入什麼?', + 'places.gpxImportWaypoints': '路點', + 'places.gpxImportRoutes': '路線', + 'places.gpxImportTracks': '軌跡(含路徑幾何)', + 'places.gpxImportNoneSelected': '請至少選擇一種匯入類型。', + 'places.kmlImportTypes': '要匯入什麼?', + 'places.kmlImportPoints': '點(Placemarks)', + 'places.kmlImportPaths': '路徑(LineStrings)', + 'places.kmlImportNoneSelected': '請至少選擇一種類型。', + 'places.selectionCount': '已選 {count} 項', + 'places.deleteSelected': '刪除所選', + 'places.kmlKmzImported': '已從 KMZ/KML 匯入 {count} 個地點', + 'places.urlResolved': '已從 URL 匯入地點', + 'places.importList': '列表匯入', + 'places.kmlKmzSummaryValues': + 'Placemarks:{total} • 已匯入:{created} • 已略過:{skipped}', + 'places.importGoogleList': 'Google 列表', + 'places.importNaverList': 'Naver 列表', + 'places.googleListHint': '貼上共享的 Google Maps 列表連結以匯入所有地點。', + 'places.googleListImported': '已從"{list}"匯入 {count} 個地點', + 'places.googleListError': 'Google Maps 列表匯入失敗', + 'places.naverListHint': '貼上共享的 Naver Maps 列表連結以匯入所有地點。', + 'places.naverListImported': '已從"{list}"匯入 {count} 個地點', + 'places.naverListError': 'Naver Maps 列表匯入失敗', + 'places.viewDetails': '檢視詳情', + 'places.assignToDay': '新增到哪一天?', + 'places.all': '全部', + 'places.unplanned': '未規劃', + 'places.filterTracks': '路線', + 'places.search': '搜尋地點...', + 'places.allCategories': '所有分類', + 'places.categoriesSelected': '個分類', + 'places.clearFilter': '清除篩選', + 'places.count': '{count} 個地點', + 'places.countSingular': '1 個地點', + 'places.allPlanned': '所有地點已規劃', + 'places.noneFound': '未找到地點', + 'places.editPlace': '編輯地點', + 'places.formName': '名稱', + 'places.formNamePlaceholder': '如:埃菲爾鐵塔', + 'places.formDescription': '描述', + 'places.formDescriptionPlaceholder': '簡短描述...', + 'places.formAddress': '地址', + 'places.formAddressPlaceholder': '街道、城市、國家', + 'places.formLat': '緯度(如 48.8566)', + 'places.formLng': '經度(如 2.3522)', + 'places.formCategory': '分類', + 'places.noCategory': '無分類', + 'places.categoryNamePlaceholder': '分類名稱', + 'places.formTime': '時間', + 'places.startTime': '開始', + 'places.endTime': '結束', + 'places.endTimeBeforeStart': '結束時間早於開始時間', + 'places.timeCollision': '時間衝突:', + 'places.formWebsite': '網站', + 'places.formNotes': '備註', + 'places.formNotesPlaceholder': '個人備註...', + 'places.formReservation': '預訂', + 'places.reservationNotesPlaceholder': '預訂備註、確認號...', + 'places.mapsSearchPlaceholder': '搜尋地點...', + 'places.mapsSearchError': '地點搜尋失敗。', + 'places.loadingDetails': '正在載入地點詳情…', + 'places.osmHint': + '使用 OpenStreetMap 搜尋(無照片、營業時間或評分)。在設定中新增 Google API 金鑰以獲取完整資訊。', + 'places.osmActive': + '透過 OpenStreetMap 搜尋(無照片、評分或營業時間)。在設定中新增 Google API 金鑰以獲取增強資料。', + 'places.categoryCreateError': '建立分類失敗', + 'places.nameRequired': '請輸入名稱', + 'places.saveError': '儲存失敗', +}; +export default places; diff --git a/shared/src/i18n/zh-TW/planner.ts b/shared/src/i18n/zh-TW/planner.ts new file mode 100644 index 00000000..ad0c561c --- /dev/null +++ b/shared/src/i18n/zh-TW/planner.ts @@ -0,0 +1,67 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': '地點', + 'planner.bookings': '預訂', + 'planner.packingList': '行李清單', + 'planner.documents': '文件', + 'planner.dayPlan': '日程計劃', + 'planner.reservations': '預訂', + 'planner.minTwoPlaces': '至少需要 2 個有座標的地點', + 'planner.noGeoPlaces': '沒有有座標的地點', + 'planner.routeCalculated': '路線已計算', + 'planner.routeCalcFailed': '無法計算路線', + 'planner.routeError': '路線計算錯誤', + 'planner.icsExportFailed': 'ICS 匯出失敗', + 'planner.routeOptimized': '路線已最佳化', + 'planner.reservationUpdated': '預訂已更新', + 'planner.reservationAdded': '預訂已新增', + 'planner.confirmDeleteReservation': '刪除預訂?', + 'planner.reservationDeleted': '預訂已刪除', + 'planner.days': '天', + 'planner.allPlaces': '所有地點', + 'planner.totalPlaces': '共 {n} 個地點', + 'planner.noDaysPlanned': '尚未規劃天數', + 'planner.editTrip': '編輯旅行 →', + 'planner.placeOne': '1 個地點', + 'planner.placeN': '{n} 個地點', + 'planner.addNote': '新增備註', + 'planner.noEntries': '當天無條目', + 'planner.addPlace': '新增地點/活動', + 'planner.addPlaceShort': '+ 新增地點/活動', + 'planner.resPending': '預訂待確認 · ', + 'planner.resConfirmed': '預訂已確認 · ', + 'planner.notePlaceholder': '備註…', + 'planner.noteTimePlaceholder': '時間(可選)', + 'planner.noteExamplePlaceholder': + '如:14:30 從中央車站乘 S3,7 號碼頭渡輪,午餐休息…', + 'planner.totalCost': '總費用', + 'planner.searchPlaces': '搜尋地點…', + 'planner.allCategories': '所有分類', + 'planner.noPlacesFound': '未找到地點', + 'planner.addFirstPlace': '新增第一個地點', + 'planner.noReservations': '暫無預訂', + 'planner.addFirstReservation': '新增第一個預訂', + 'planner.new': '新建', + 'planner.addToDay': '+ 天', + 'planner.calculating': '計算中…', + 'planner.route': '路線', + 'planner.optimize': '最佳化', + 'planner.openGoogleMaps': '在 Google Maps 中開啟', + 'planner.selectDayHint': '從左側列表選擇一天以檢視日程計劃', + 'planner.noPlacesForDay': '當天暫無地點', + 'planner.addPlacesLink': '新增地點 →', + 'planner.minTotal': '分鐘 合計', + 'planner.noReservation': '無預訂', + 'planner.removeFromDay': '從當天移除', + 'planner.addToThisDay': '新增到當天', + 'planner.overview': '概覽', + 'planner.noDays': '暫無天數', + 'planner.editTripToAddDays': '編輯旅行以新增天數', + 'planner.dayCount': '{n} 天', + 'planner.clickToUnlock': '點選解鎖', + 'planner.keepPosition': '路線最佳化時保持位置', + 'planner.dayDetails': '日程詳情', + 'planner.dayN': '第 {n} 天', +}; +export default planner; diff --git a/shared/src/i18n/zh-TW/register.ts b/shared/src/i18n/zh-TW/register.ts new file mode 100644 index 00000000..dd22ae77 --- /dev/null +++ b/shared/src/i18n/zh-TW/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': '兩次輸入的密碼不一致', + 'register.passwordTooShort': '密碼至少需要 8 個字元', + 'register.failed': '註冊失敗', + 'register.getStarted': '開始使用', + 'register.subtitle': '建立賬戶,開始規劃你的夢想旅行。', + 'register.feature1': '無限旅行計劃', + 'register.feature2': '互動地圖檢視', + 'register.feature3': '管理地點和分類', + 'register.feature4': '跟蹤預訂', + 'register.feature5': '建立行李清單', + 'register.feature6': '儲存照片和檔案', + 'register.createAccount': '建立賬戶', + 'register.startPlanning': '開始規劃你的旅行', + 'register.minChars': '至少 6 個字元', + 'register.confirmPassword': '確認密碼', + 'register.repeatPassword': '重複密碼', + 'register.registering': '註冊中...', + 'register.register': '註冊', + 'register.hasAccount': '已有賬戶?', + 'register.signIn': '登入', +}; +export default register; diff --git a/shared/src/i18n/zh-TW/reservations.ts b/shared/src/i18n/zh-TW/reservations.ts new file mode 100644 index 00000000..2a2ea440 --- /dev/null +++ b/shared/src/i18n/zh-TW/reservations.ts @@ -0,0 +1,115 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': '預訂', + 'reservations.empty': '暫無預訂', + 'reservations.emptyHint': '新增航班、酒店等預訂資訊', + 'reservations.add': '新增預訂', + 'reservations.addManual': '手動新增', + 'reservations.placeHint': + '提示:建議從地點直接建立預訂,以便與日程計劃關聯。', + 'reservations.confirmed': '已確認', + 'reservations.pending': '待確認', + 'reservations.summary': '{confirmed} 已確認,{pending} 待確認', + 'reservations.fromPlan': '來自計劃', + 'reservations.showFiles': '檢視檔案', + 'reservations.editTitle': '編輯預訂', + 'reservations.status': '狀態', + 'reservations.datetime': '日期和時間', + 'reservations.startTime': '開始時間', + 'reservations.endTime': '結束時間', + 'reservations.date': '日期', + 'reservations.time': '時間', + 'reservations.timeAlt': '時間(備選,如 19:30)', + 'reservations.notes': '備註', + 'reservations.notesPlaceholder': '其他備註...', + 'reservations.meta.airline': '航空公司', + 'reservations.meta.flightNumber': '航班號', + 'reservations.meta.from': '出發', + 'reservations.meta.to': '到達', + 'reservations.needsReview': '待確認', + 'reservations.needsReviewHint': '無法自動匹配機場 — 請確認位置。', + 'reservations.searchLocation': '搜尋車站、港口、地址...', + 'reservations.meta.trainNumber': '車次', + 'reservations.meta.platform': '站臺', + 'reservations.meta.seat': '座位', + 'reservations.meta.checkIn': '入住', + 'reservations.meta.checkInUntil': '入住截止', + 'reservations.meta.checkOut': '退房', + 'reservations.meta.linkAccommodation': '住宿', + 'reservations.meta.pickAccommodation': '關聯住宿', + 'reservations.meta.noAccommodation': '無', + 'reservations.meta.hotelPlace': '住宿', + 'reservations.meta.pickHotel': '選擇住宿', + 'reservations.meta.fromDay': '從', + 'reservations.meta.toDay': '到', + 'reservations.meta.selectDay': '選擇日期', + 'reservations.type.flight': '航班', + 'reservations.type.hotel': '住宿', + 'reservations.type.restaurant': '餐廳', + 'reservations.type.train': '火車', + 'reservations.type.car': '汽車', + 'reservations.type.cruise': '郵輪', + 'reservations.type.event': '活動', + 'reservations.type.tour': '旅遊團', + 'reservations.type.other': '其他', + 'reservations.confirm.delete': '確定要刪除預訂「{name}」嗎?', + 'reservations.confirm.deleteTitle': '刪除預訂?', + 'reservations.confirm.deleteBody': '"{name}" 將被永久刪除。', + 'reservations.toast.updated': '預訂已更新', + 'reservations.toast.removed': '預訂已刪除', + 'reservations.toast.fileUploaded': '檔案已上傳', + 'reservations.toast.uploadError': '上傳失敗', + 'reservations.newTitle': '新建預訂', + 'reservations.bookingType': '預訂型別', + 'reservations.titleLabel': '標題', + 'reservations.titlePlaceholder': '如:漢莎 LH123、阿德隆酒店...', + 'reservations.locationAddress': '地點 / 地址', + 'reservations.locationPlaceholder': '地址、機場、酒店...', + 'reservations.confirmationCode': '預訂碼', + 'reservations.confirmationPlaceholder': '如:ABC12345', + 'reservations.day': '日期', + 'reservations.noDay': '無日期', + 'reservations.place': '地點', + 'reservations.noPlace': '無地點', + 'reservations.pendingSave': '將被儲存…', + 'reservations.uploading': '上傳中...', + 'reservations.attachFile': '附加檔案', + 'reservations.linkExisting': '關聯已有檔案', + 'reservations.toast.saveError': '儲存失敗', + 'reservations.toast.updateError': '更新失敗', + 'reservations.toast.deleteError': '刪除失敗', + 'reservations.confirm.remove': '移除「{name}」的預訂?', + 'reservations.linkAssignment': '關聯日程分配', + 'reservations.pickAssignment': '從計劃中選擇一個分配...', + 'reservations.noAssignment': '無關聯(獨立)', + 'reservations.price': '價格', + 'reservations.budgetCategory': '預算分類', + 'reservations.budgetCategoryPlaceholder': '如:交通、住宿', + 'reservations.budgetCategoryAuto': '自動(依預訂類型)', + 'reservations.budgetHint': '儲存時將自動建立預算條目。', + 'reservations.departureDate': '出發日期', + 'reservations.arrivalDate': '到達日期', + 'reservations.departureTime': '出發時間', + 'reservations.arrivalTime': '到達時間', + 'reservations.pickupDate': '取車日期', + 'reservations.returnDate': '還車日期', + 'reservations.pickupTime': '取車時間', + 'reservations.returnTime': '還車時間', + 'reservations.endDate': '結束日期', + 'reservations.meta.departureTimezone': '出發時區', + 'reservations.meta.arrivalTimezone': '到達時區', + 'reservations.span.departure': '出發', + 'reservations.span.arrival': '到達', + 'reservations.span.inTransit': '途中', + 'reservations.span.pickup': '取車', + 'reservations.span.return': '還車', + 'reservations.span.active': '進行中', + 'reservations.span.start': '開始', + 'reservations.span.end': '結束', + 'reservations.span.ongoing': '進行中', + 'reservations.validation.endBeforeStart': + '結束日期/時間必須晚於開始日期/時間', + 'reservations.addBooking': '新增預訂', +}; +export default reservations; diff --git a/shared/src/i18n/zh-TW/settings.ts b/shared/src/i18n/zh-TW/settings.ts new file mode 100644 index 00000000..2cbad306 --- /dev/null +++ b/shared/src/i18n/zh-TW/settings.ts @@ -0,0 +1,286 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': '設定', + 'settings.subtitle': '配置你的個人設定', + 'settings.tabs.display': '顯示', + 'settings.tabs.map': '地圖', + 'settings.tabs.notifications': '通知', + 'settings.tabs.integrations': '整合', + 'settings.tabs.account': '帳戶', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': '關於', + 'settings.map': '地圖', + 'settings.mapTemplate': '地圖模板', + 'settings.mapTemplatePlaceholder.select': '選擇模板...', + 'settings.mapDefaultHint': '留空則使用 OpenStreetMap(預設)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': '地圖瓦片 URL 模板', + 'settings.mapProvider': '地圖提供商', + 'settings.mapProviderHint': + '影響行程規劃和旅程地圖。Atlas 始終使用 Leaflet。', + 'settings.mapLeafletSubtitle': '經典 2D,任何柵格瓦片', + 'settings.mapMapboxSubtitle': '向量瓦片、3D 建築和地形', + 'settings.mapExperimental': '實驗性', + 'settings.mapMapboxToken': 'Mapbox 存取權杖', + 'settings.mapMapboxTokenHint': '公開權杖 (pk.*) 來自', + 'settings.mapMapboxTokenLink': 'mapbox.com → 存取權杖', + 'settings.mapStyle': '地圖樣式', + 'settings.mapStylePlaceholder': '選擇 Mapbox 樣式', + 'settings.mapStyleHint': '預設或您自己的 mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D 建築和地形', + 'settings.map3dHint': '傾斜 + 真實 3D 建築拉伸 — 適用於所有樣式,包括衛星。', + 'settings.mapHighQuality': '高畫質模式', + 'settings.mapHighQualityHint': + '抗鋸齒 + 地球投影,帶來更清晰的邊緣和更真實的世界視圖。', + 'settings.mapHighQualityWarning': '可能影響低階裝置的效能。', + 'settings.mapTipLabel': '提示:', + 'settings.mapTip': + '右鍵點擊並拖曳以旋轉/傾斜地圖。中鍵點擊新增地點(右鍵用於旋轉)。', + 'settings.latitude': '緯度', + 'settings.longitude': '經度', + 'settings.saveMap': '儲存地圖', + 'settings.apiKeys': 'API 金鑰', + 'settings.mapsKey': 'Google Maps API 金鑰', + 'settings.mapsKeyHint': + '用於地點搜尋。需要 Places API (New)。在 console.cloud.google.com 獲取', + 'settings.weatherKey': 'OpenWeatherMap API 金鑰', + 'settings.weatherKeyHint': '用於天氣資料。在 openweathermap.org/api 免費獲取', + 'settings.keyPlaceholder': '輸入金鑰...', + 'settings.configured': '已配置', + 'settings.saveKeys': '儲存金鑰', + 'settings.display': '顯示', + 'settings.colorMode': '顏色模式', + 'settings.light': '淺色', + 'settings.dark': '深色', + 'settings.auto': '自動', + 'settings.language': '語言', + 'settings.temperature': '溫度單位', + 'settings.timeFormat': '時間格式', + 'settings.blurBookingCodes': '模糊預訂程式碼', + 'settings.notifications': '通知', + 'settings.notifyTripInvite': '旅行邀請', + 'settings.notifyBookingChange': '預訂變更', + 'settings.notifyTripReminder': '旅行提醒', + 'settings.notifyTodoDue': '待辦事項即將到期', + 'settings.notifyVacayInvite': 'Vacay 融合邀請', + 'settings.notifyPhotosShared': '共享照片 (Immich)', + 'settings.notifyCollabMessage': '聊天訊息 (Collab)', + 'settings.notifyPackingTagged': '行李清單:分配', + 'settings.notifyWebhook': 'Webhook 通知', + 'settings.notifyVersionAvailable': '有新版本可用', + 'settings.notificationPreferences.email': '電子郵件', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.inapp': '應用程式內', + 'settings.notificationPreferences.ntfy': 'Ntfy', + 'settings.notificationPreferences.noChannels': + '未配置通知渠道。請聯絡管理員設定電子郵件或 Webhook 通知。', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + '輸入您的 Discord、Slack 或自訂 Webhook URL 以接收通知。', + 'settings.webhookUrl.saved': 'Webhook URL 已儲存', + 'settings.webhookUrl.test': '測試', + 'settings.webhookUrl.testSuccess': '測試 Webhook 傳送成功', + 'settings.webhookUrl.testFailed': '測試 Webhook 傳送失敗', + 'settings.ntfyUrl.topicLabel': 'Ntfy 主題', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy 伺服器 URL(選填)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + '輸入您的 Ntfy 主題以接收推播通知。將伺服器留空以使用管理員設定的預設值。', + 'settings.ntfyUrl.tokenLabel': '存取權杖(選填)', + 'settings.ntfyUrl.tokenHint': '受密碼保護的主題需要此項目。', + 'settings.ntfyUrl.saved': 'Ntfy 設定已儲存', + 'settings.ntfyUrl.test': '測試', + 'settings.ntfyUrl.testSuccess': '測試 Ntfy 通知傳送成功', + 'settings.ntfyUrl.testFailed': '測試 Ntfy 通知失敗', + 'settings.ntfyUrl.tokenCleared': '存取權杖已清除', + 'settings.notificationsDisabled': + '通知尚未配置。請聯絡管理員啟用電子郵件或 Webhook 通知。', + 'settings.notificationsActive': '活躍頻道', + 'settings.notificationsManagedByAdmin': '通知事件由管理員配置。', + 'settings.on': '開', + 'settings.off': '關', + 'settings.mcp.title': 'MCP 配置', + 'settings.mcp.endpoint': 'MCP 端點', + 'settings.mcp.clientConfig': '客戶端配置', + 'settings.mcp.clientConfigHint': + '將 替換為下方列表中的 API 令牌。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。', + 'settings.mcp.clientConfigHintOAuth': + '將 替換為上方建立的 OAuth 2.1 客戶端所顯示的憑據。首次連線時,mcp-remote 將開啟瀏覽器完成授權。npx 的路徑可能需要根據您的系統進行調整(例如 Windows 上為 C:\\PROGRA~1\\nodejs\\npx.cmd)。', + 'settings.mcp.copy': '複製', + 'settings.mcp.copied': '已複製!', + 'settings.mcp.apiTokens': 'API 令牌', + 'settings.mcp.createToken': '建立新令牌', + 'settings.mcp.noTokens': '暫無令牌,請建立一個以連線 MCP 客戶端。', + 'settings.mcp.tokenCreatedAt': '創建於', + 'settings.mcp.tokenUsedAt': '使用於', + 'settings.mcp.deleteTokenTitle': '刪除令牌', + 'settings.mcp.deleteTokenMessage': + '此令牌將立即失效,使用它的所有 MCP 客戶端將失去訪問許可權。', + 'settings.mcp.modal.createTitle': '建立 API 令牌', + 'settings.mcp.modal.tokenName': '令牌名稱', + 'settings.mcp.modal.tokenNamePlaceholder': '例如:Claude Desktop、工作電腦', + 'settings.mcp.modal.creating': '建立中…', + 'settings.mcp.modal.create': '建立令牌', + 'settings.mcp.modal.createdTitle': '令牌已建立', + 'settings.mcp.modal.createdWarning': + '此令牌只會顯示一次,請立即複製並妥善儲存——無法找回。', + 'settings.mcp.modal.done': '完成', + 'settings.mcp.toast.created': '令牌已建立', + 'settings.mcp.toast.createError': '建立令牌失敗', + 'settings.mcp.toast.deleted': '令牌已刪除', + 'settings.mcp.toast.deleteError': '刪除令牌失敗', + 'settings.mcp.apiTokensDeprecated': + 'API 金鑰已棄用,將於未來版本中移除。請改用 OAuth 2.1 客戶端。', + 'settings.oauth.clients': 'OAuth 2.1 客戶端', + 'settings.oauth.clientsHint': + '註冊 OAuth 2.1 客戶端,讓第三方 MCP 應用程式(Claude Web、Cursor 等)無需靜態金鑰即可連線。', + 'settings.oauth.createClient': '新增客戶端', + 'settings.oauth.noClients': '尚無已註冊的 OAuth 客戶端。', + 'settings.oauth.clientId': '客戶端 ID', + 'settings.oauth.clientSecret': '客戶端密鑰', + 'settings.oauth.deleteClient': '刪除客戶端', + 'settings.oauth.deleteClientMessage': + '此客戶端及所有活躍工作階段將被永久刪除。任何使用此客戶端的應用程式將立即失去存取權限。', + 'settings.oauth.rotateSecret': '輪換密鑰', + 'settings.oauth.rotateSecretMessage': + '將產生新的客戶端密鑰,所有現有工作階段將立即失效。請在關閉此對話框前更新您的應用程式。', + 'settings.oauth.rotateSecretConfirm': '輪換', + 'settings.oauth.rotateSecretConfirming': '輪換中…', + 'settings.oauth.rotateSecretDoneTitle': '已產生新密鑰', + 'settings.oauth.rotateSecretDoneWarning': + '此密鑰僅顯示一次。請立即複製並更新您的應用程式——所有先前的工作階段已失效。', + 'settings.oauth.activeSessions': '活躍的 OAuth 工作階段', + 'settings.oauth.sessionScopes': '授權範圍', + 'settings.oauth.sessionExpires': '到期時間', + 'settings.oauth.revoke': '撤銷', + 'settings.oauth.revokeSession': '撤銷工作階段', + 'settings.oauth.revokeSessionMessage': + '這將立即撤銷此 OAuth 工作階段的存取權限。', + 'settings.oauth.modal.createTitle': '註冊 OAuth 客戶端', + 'settings.oauth.modal.presets': '快速預設', + 'settings.oauth.modal.clientName': '應用程式名稱', + 'settings.oauth.modal.clientNamePlaceholder': + '例如 Claude Web、我的 MCP 應用程式', + 'settings.oauth.modal.redirectUris': '重新導向 URI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + '每行一個 URI。需要 HTTPS(localhost 除外)。需要完全符合。', + 'settings.oauth.modal.scopes': '允許的授權範圍', + 'settings.oauth.modal.scopesHint': + 'list_trips 和 get_trip_summary 始終可用——不需要授權範圍。它們可幫助 AI 找到所需的行程 ID。', + 'settings.oauth.modal.selectAll': '全選', + 'settings.oauth.modal.deselectAll': '取消全選', + 'settings.oauth.modal.creating': '註冊中…', + 'settings.oauth.modal.create': '註冊客戶端', + 'settings.oauth.modal.createdTitle': '客戶端已註冊', + 'settings.oauth.modal.createdWarning': + '客戶端密鑰僅顯示一次。請立即複製——無法恢復。', + 'settings.oauth.toast.createError': '註冊 OAuth 客戶端失敗', + 'settings.oauth.toast.deleted': 'OAuth 客戶端已刪除', + 'settings.oauth.toast.deleteError': '刪除 OAuth 客戶端失敗', + 'settings.oauth.toast.revoked': '工作階段已撤銷', + 'settings.oauth.toast.revokeError': '撤銷工作階段失敗', + 'settings.oauth.toast.rotateError': '輪換客戶端密鑰失敗', + 'settings.oauth.modal.machineClient': '機器客戶端(無需瀏覽器登入)', + 'settings.oauth.modal.machineClientHint': + '使用 client_credentials 授權——無需重新導向 URI。令牌透過 client_id + client_secret 直接簽發,並在所選範圍內以您的身份運行。', + 'settings.oauth.modal.machineClientUsage': + '取得令牌:向 /oauth/token 發送 POST 請求,攜帶 grant_type=client_credentials、client_id 和 client_secret。無需瀏覽器,無重整令牌。', + 'settings.oauth.badge.machine': '機器', + 'settings.account': '賬戶', + 'settings.about': '關於', + 'settings.about.reportBug': '回報錯誤', + 'settings.about.reportBugHint': '發現問題?告訴我們', + 'settings.about.featureRequest': '功能建議', + 'settings.about.featureRequestHint': '建議新功能', + 'settings.about.wikiHint': '文件與指南', + 'settings.about.supporters.badge': '月度支持者', + 'settings.about.supporters.title': '與 TREK 同行的夥伴', + 'settings.about.supporters.subtitle': + '當你規劃下一段路線時,這些人也在一起規劃 TREK 的未來。他們每月的支持直接用於開發與實際投入的時間——讓 TREK 保持開源。', + 'settings.about.supporters.since': '自 {date} 起的支持者', + 'settings.about.supporters.tierEmpty': '成為第一個', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK 是一款自架旅遊規劃器,幫助您從最初構想到最後回憶,整理每次旅行。日程規劃、預算、行李清單、照片及更多功能——全部集中在您自己的伺服器上。', + 'settings.about.madeWith': '以', + 'settings.about.madeBy': '由 Maurice 及不斷成長的開源社群製作。', + 'settings.username': '使用者名稱', + 'settings.email': '郵箱', + 'settings.role': '角色', + 'settings.roleAdmin': '管理員', + 'settings.oidcLinked': '已關聯', + 'settings.changePassword': '修改密碼', + 'settings.mustChangePassword': '您必須更改密碼才能繼續。請在下方設定新密碼。', + 'settings.currentPassword': '當前密碼', + 'settings.currentPasswordRequired': '請輸入當前密碼', + 'settings.newPassword': '新密碼', + 'settings.confirmPassword': '確認新密碼', + 'settings.updatePassword': '更新密碼', + 'settings.passwordRequired': '請輸入當前密碼和新密碼', + 'settings.passwordTooShort': '密碼至少需要 8 個字元', + 'settings.passwordMismatch': '兩次輸入的密碼不一致', + 'settings.passwordWeak': '密碼必須包含大寫字母、小寫字母、數字和特殊字元', + 'settings.passwordChanged': '密碼修改成功', + 'settings.deleteAccount': '刪除賬戶', + 'settings.deleteAccountTitle': '確定刪除賬戶?', + 'settings.deleteAccountWarning': + '你的賬戶以及所有旅行、地點和檔案將被永久刪除。此操作無法撤銷。', + 'settings.deleteAccountConfirm': '永久刪除', + 'settings.deleteBlockedTitle': '無法刪除', + 'settings.deleteBlockedMessage': + '你是唯一的管理員。請先將其他使用者提升為管理員,然後再刪除賬戶。', + 'settings.roleUser': '使用者', + 'settings.saveProfile': '儲存資料', + 'settings.mfa.title': '雙因素認證 (2FA)', + 'settings.mfa.description': + '登入時新增第二步驗證。使用身份驗證器應用(Google Authenticator、Authy 等)。', + 'settings.mfa.requiredByPolicy': + '管理員要求雙因素身份驗證。請先完成下方的身份驗證器設定後再繼續。', + 'settings.mfa.backupTitle': '備用程式碼', + 'settings.mfa.backupDescription': + '如果你無法使用身份驗證器應用,可使用這些一次性備用程式碼登入。', + 'settings.mfa.backupWarning': + '請立即儲存這些程式碼。每個程式碼只能使用一次。', + 'settings.mfa.backupCopy': '複製程式碼', + 'settings.mfa.backupDownload': '下載 TXT', + 'settings.mfa.backupPrint': '列印 / PDF', + 'settings.mfa.backupCopied': '備用程式碼已複製', + 'settings.mfa.enabled': '您的賬戶已啟用 2FA。', + 'settings.mfa.disabled': '2FA 未啟用。', + 'settings.mfa.setup': '設定身份驗證器', + 'settings.mfa.scanQr': '使用應用掃描此二維碼,或手動輸入金鑰。', + 'settings.mfa.secretLabel': '金鑰(手動輸入)', + 'settings.mfa.codePlaceholder': '6 位驗證碼', + 'settings.mfa.enable': '啟用 2FA', + 'settings.mfa.cancelSetup': '取消', + 'settings.mfa.disableTitle': '停用 2FA', + 'settings.mfa.disableHint': '輸入您的賬戶密碼和身份驗證器中的當前驗證碼。', + 'settings.mfa.disable': '停用 2FA', + 'settings.mfa.toastEnabled': '雙因素認證已啟用', + 'settings.mfa.toastDisabled': '雙因素認證已停用', + 'settings.mfa.demoBlocked': '演示模式下不可用', + 'settings.toast.mapSaved': '地圖設定已儲存', + 'settings.toast.keysSaved': 'API 金鑰已儲存', + 'settings.toast.displaySaved': '顯示設定已儲存', + 'settings.toast.profileSaved': '資料已儲存', + 'settings.uploadAvatar': '上傳頭像', + 'settings.removeAvatar': '移除頭像', + 'settings.avatarUploaded': '頭像已更新', + 'settings.avatarRemoved': '頭像已移除', + 'settings.avatarError': '上傳失敗', + 'settings.bookingLabels': '預訂路線標籤', + 'settings.bookingLabelsHint': + '在地圖上顯示車站 / 機場名稱。關閉時僅顯示圖示。', +}; +export default settings; diff --git a/shared/src/i18n/zh-TW/share.ts b/shared/src/i18n/zh-TW/share.ts new file mode 100644 index 00000000..7c31f8cc --- /dev/null +++ b/shared/src/i18n/zh-TW/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': '公開連結', + 'share.linkHint': + '建立一個連結,任何人無需登入即可檢視此旅行。僅可檢視,無法編輯。', + 'share.createLink': '建立連結', + 'share.deleteLink': '刪除連結', + 'share.createError': '無法建立連結', + 'share.permMap': '地圖與計劃', + 'share.permBookings': '預訂', + 'share.permPacking': '行李', + 'share.permBudget': '預算', + 'share.permCollab': '聊天', +}; +export default share; diff --git a/shared/src/i18n/zh-TW/shared.ts b/shared/src/i18n/zh-TW/shared.ts new file mode 100644 index 00000000..b4162a63 --- /dev/null +++ b/shared/src/i18n/zh-TW/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': '連結已過期或無效', + 'shared.expiredHint': '此共享旅行連結已失效。', + 'shared.readOnly': '只讀共享檢視', + 'shared.tabPlan': '計劃', + 'shared.tabBookings': '預訂', + 'shared.tabPacking': '行李', + 'shared.tabBudget': '預算', + 'shared.tabChat': '聊天', + 'shared.days': '天', + 'shared.places': '個地點', + 'shared.other': '其他', + 'shared.totalBudget': '總預算', + 'shared.messages': '條訊息', + 'shared.sharedVia': '透過以下分享', + 'shared.confirmed': '已確認', + 'shared.pending': '待確認', +}; +export default shared; diff --git a/shared/src/i18n/zh-TW/stats.ts b/shared/src/i18n/zh-TW/stats.ts new file mode 100644 index 00000000..7e75a0b5 --- /dev/null +++ b/shared/src/i18n/zh-TW/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': '國家', + 'stats.cities': '城市', + 'stats.trips': '旅行', + 'stats.places': '地點', + 'stats.worldProgress': '全球進度', + 'stats.visited': '已訪問', + 'stats.remaining': '未訪問', + 'stats.visitedCountries': '已訪問國家', +}; +export default stats; diff --git a/shared/src/i18n/zh-TW/system_notice.ts b/shared/src/i18n/zh-TW/system_notice.ts new file mode 100644 index 00000000..b13b9384 --- /dev/null +++ b/shared/src/i18n/zh-TW/system_notice.ts @@ -0,0 +1,50 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': '歡迎使用 TREK', + 'system_notice.welcome_v1.body': + '您的全方位旅遊規劃器。建立行程、與朋友分享旅遊,隨時保持條理分明——無論線上或離線皆可。', + 'system_notice.welcome_v1.cta_label': '規劃行程', + 'system_notice.welcome_v1.hero_alt': '風景優美的旅遊目的地與 TREK 介面', + 'system_notice.welcome_v1.highlight_plan': '逐日行程規劃', + 'system_notice.welcome_v1.highlight_share': '與旅伴協作規劃', + 'system_notice.welcome_v1.highlight_offline': '行動裝置支援離線使用', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': '上一則通知', + 'system_notice.pager.next': '下一則通知', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': '前往通知 {n}', + 'system_notice.pager.position': '通知 {current}/{total}', + 'system_notice.v3_photos.title': '3.0 版相片已移至', + 'system_notice.v3_photos.body': + '行程規劃器中的​**相片**標籤已被移除。您的相片安全— TREK 從未修改您的 Immich 或 Synology 相簿。\n\n相片現在位於 **Journey** 附加元件中。Journey 為選用 — 若尚未啟用,請聯絡管理員於 Admin → 附加元件 中開啟。', + 'system_notice.v3_journey.title': '認識 Journey — 旅行日記', + 'system_notice.v3_journey.body': + '將您的旅程記錄為具有時間軸、相片畫庫與互動地圖的豐富旅行故事。', + 'system_notice.v3_journey.cta_label': '開啟 Journey', + 'system_notice.v3_journey.highlight_timeline': '每日時間軸與畫庫', + 'system_notice.v3_journey.highlight_photos': '從 Immich 或 Synology 匯入', + 'system_notice.v3_journey.highlight_share': '公開分享 — 無需登入', + 'system_notice.v3_journey.highlight_export': '匯出為 PDF 相簿书', + 'system_notice.v3_features.title': '3.0 版更多亮點', + 'system_notice.v3_features.body': '這個版本還有一些其他專項值得了解。', + 'system_notice.v3_features.highlight_dashboard': '行動先行儀表板重設計', + 'system_notice.v3_features.highlight_offline': '作為 PWA 的完整離線模式', + 'system_notice.v3_features.highlight_search': '地點搜尋即時自動補全', + 'system_notice.v3_features.highlight_import': '從 KMZ/KML 檔案匯入地點', + 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1 升級', + 'system_notice.v3_mcp.body': + 'MCP 整合已全面重構。OAuth 2.1 現為建議的身份驗證方式。靜態令牌(trek_…)已棄用,將於未來版本移除。', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 建議(mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 個細粒度權限範圍', + 'system_notice.v3_mcp.highlight_deprecated': '靜態 trek_ 令牌已棄用', + 'system_notice.v3_mcp.highlight_tools': '擴展工具集與提示詞', + 'system_notice.v3_thankyou.title': '來自我的一封私人信', + 'system_notice.v3_thankyou.body': + '在你繼續之前——我想停下來說幾句。\n\nTREK 最初只是我為自己的旅行而做的一個業餘專案。我從未想過它會成長為 4,000 人信賴的冒險規劃工具。每一顆星標、每一個 issue、每一個功能請求——我都會讀,它們在全職工作和大學學業之間的深夜裡支撐著我繼續前行。\n\n我想讓你們知道:TREK 將永遠開源,永遠可自託管,永遠屬於你們。沒有追蹤,沒有訂閱,沒有任何附加條件。只是一個熱愛旅行的人為同樣熱愛旅行的你們打造的工具。\n\n特別感謝 [jubnl](https://github.com/jubnl)——你已經成為一位不可思議的合作者。3.0 版本中許多精彩之處都留下了你的印記。感謝你在這個專案還很粗糙的時候就選擇了相信它。\n\n也感謝你們每一位——回報了 bug、翻譯了文字、向朋友分享了 TREK,或者只是用它規劃了一次旅行——**謝謝你們**。你們是這一切存在的原因。\n\n願我們一起踏上更多的冒險旅程。\n\n— Maurice\n\n---\n\n[加入 Discord 社群](https://discord.gg/7Q6M6jDwzf)\n\n如果 TREK 讓你的旅行更美好,一杯[小小的咖啡](https://ko-fi.com/mauriceboe)能讓這盞燈一直亮著。', + 'system_notice.v3014_whitespace_collision.title': '需要操作:使用者帳戶衝突', + 'system_notice.v3014_whitespace_collision.body': + '3.0.14 版本升級偵測到一個或多個由儲存帳戶中前後空白字元引發的使用者名稱或電子郵件衝突。受影響的帳戶已自動重新命名。請檢查伺服器日誌中以 **[migration] WHITESPACE COLLISION** 開頭的行,以確認哪些帳戶需要審查。', +}; +export default system_notice; diff --git a/shared/src/i18n/zh-TW/todo.ts b/shared/src/i18n/zh-TW/todo.ts new file mode 100644 index 00000000..e2765369 --- /dev/null +++ b/shared/src/i18n/zh-TW/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': '行李清單', + 'todo.subtab.todo': '待辦事項', + 'todo.completed': '已完成', + 'todo.filter.all': '全部', + 'todo.filter.open': '未完成', + 'todo.filter.done': '已完成', + 'todo.uncategorized': '未分類', + 'todo.namePlaceholder': '任務名稱', + 'todo.descriptionPlaceholder': '說明(可選)', + 'todo.unassigned': '未指派', + 'todo.noCategory': '無分類', + 'todo.hasDescription': '有說明', + 'todo.addItem': '新增任務', + 'todo.sidebar.sortBy': '排序方式', + 'todo.priority': '優先順序', + 'todo.newCategoryLabel': '新增', + 'todo.newCategory': '分類名稱', + 'todo.addCategory': '新增分類', + 'todo.newItem': '新任務', + 'todo.empty': '尚無任務。新增任務以開始!', + 'todo.filter.my': '我的任務', + 'todo.filter.overdue': '已逾期', + 'todo.sidebar.tasks': '任務', + 'todo.sidebar.categories': '分類', + 'todo.detail.title': '任務', + 'todo.detail.description': '說明', + 'todo.detail.category': '分類', + 'todo.detail.dueDate': '到期日', + 'todo.detail.assignedTo': '指派給', + 'todo.detail.delete': '刪除', + 'todo.detail.save': '儲存變更', + 'todo.sortByPrio': '優先順序', + 'todo.detail.priority': '優先順序', + 'todo.detail.noPriority': '無', + 'todo.detail.create': '建立任務', +}; +export default todo; diff --git a/shared/src/i18n/zh-TW/transport.ts b/shared/src/i18n/zh-TW/transport.ts new file mode 100644 index 00000000..607f4d83 --- /dev/null +++ b/shared/src/i18n/zh-TW/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': '新增交通', + 'transport.modalTitle.create': '新增交通', + 'transport.modalTitle.edit': '編輯交通', + 'transport.title': '交通', + 'transport.addManual': '手動新增交通', +}; +export default transport; diff --git a/shared/src/i18n/zh-TW/trip.ts b/shared/src/i18n/zh-TW/trip.ts new file mode 100644 index 00000000..5a3cb76f --- /dev/null +++ b/shared/src/i18n/zh-TW/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': '計劃', + 'trip.tabs.transports': '交通', + 'trip.tabs.reservations': '預訂', + 'trip.tabs.reservationsShort': '預訂', + 'trip.tabs.packing': '行李清單', + 'trip.tabs.packingShort': '行李', + 'trip.tabs.lists': '清單', + 'trip.tabs.listsShort': '清單', + 'trip.tabs.budget': '預算', + 'trip.tabs.files': '檔案', + 'trip.loading': '載入旅行中...', + 'trip.loadingPhotos': '正在載入地點照片...', + 'trip.mobilePlan': '計劃', + 'trip.mobilePlaces': '地點', + 'trip.toast.placeUpdated': '地點已更新', + 'trip.toast.placeAdded': '地點已新增', + 'trip.toast.placeDeleted': '地點已刪除', + 'trip.toast.selectDay': '請先選擇一天', + 'trip.toast.assignedToDay': '地點已分配到當天', + 'trip.toast.reorderError': '排序失敗', + 'trip.toast.reservationUpdated': '預訂已更新', + 'trip.toast.reservationAdded': '預訂已新增', + 'trip.toast.deleted': '已刪除', + 'trip.confirm.deletePlace': '確定要刪除這個地點嗎?', + 'trip.confirm.deletePlaces': '刪除 {count} 個地點?', + 'trip.toast.placesDeleted': '已刪除 {count} 個地點', +}; +export default trip; diff --git a/shared/src/i18n/zh-TW/trips.ts b/shared/src/i18n/zh-TW/trips.ts new file mode 100644 index 00000000..a2c1dbba --- /dev/null +++ b/shared/src/i18n/zh-TW/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} 已移除', + 'trips.memberRemoveError': '移除失敗', + 'trips.memberAdded': '{username} 已新增', + 'trips.memberAddError': '新增失敗', + 'trips.reminder': '提醒', + 'trips.reminderNone': '無', + 'trips.reminderDay': '天', + 'trips.reminderDays': '天', + 'trips.reminderCustom': '自定義', + 'trips.reminderDaysBefore': '天前提醒', + 'trips.reminderDisabledHint': + '旅行提醒已停用。請在管理 > 設定 > 通知中啟用。', +}; +export default trips; diff --git a/shared/src/i18n/zh-TW/undo.ts b/shared/src/i18n/zh-TW/undo.ts new file mode 100644 index 00000000..8be5ed12 --- /dev/null +++ b/shared/src/i18n/zh-TW/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': '撤銷', + 'undo.tooltip': '撤銷:{action}', + 'undo.assignPlace': '地點已分配至某天', + 'undo.removeAssignment': '地點已從某天移除', + 'undo.reorder': '地點已重新排序', + 'undo.optimize': '路線已最佳化', + 'undo.deletePlace': '地點已刪除', + 'undo.deletePlaces': '地點已刪除', + 'undo.moveDay': '地點已移至另一天', + 'undo.lock': '地點鎖定已切換', + 'undo.importGpx': 'GPX 匯入', + 'undo.importKeyholeMarkup': 'KMZ/KML 匯入', + 'undo.importGoogleList': 'Google 地圖匯入', + 'undo.importNaverList': 'Naver 地圖匯入', + 'undo.addPlace': '地點已新增', + 'undo.done': '已撤銷:{action}', +}; +export default undo; diff --git a/shared/src/i18n/zh-TW/vacay.ts b/shared/src/i18n/zh-TW/vacay.ts new file mode 100644 index 00000000..c2eda2ba --- /dev/null +++ b/shared/src/i18n/zh-TW/vacay.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': '規劃和管理假期', + 'vacay.settings': '設定', + 'vacay.year': '年份', + 'vacay.addYear': '新增下一年', + 'vacay.addPrevYear': '新增上一年', + 'vacay.removeYear': '移除年份', + 'vacay.removeYearConfirm': '移除 {year}?', + 'vacay.removeYearHint': '該年度所有假期記錄和公司假日將被永久刪除。', + 'vacay.remove': '移除', + 'vacay.persons': '成員', + 'vacay.noPersons': '暫無成員', + 'vacay.addPerson': '新增成員', + 'vacay.editPerson': '編輯成員', + 'vacay.removePerson': '移除成員', + 'vacay.removePersonConfirm': '移除 {name}?', + 'vacay.removePersonHint': '該成員的所有假期記錄將被永久刪除。', + 'vacay.personName': '姓名', + 'vacay.personNamePlaceholder': '輸入姓名', + 'vacay.color': '顏色', + 'vacay.add': '新增', + 'vacay.legend': '圖例', + 'vacay.publicHoliday': '公共假日', + 'vacay.companyHoliday': '公司假日', + 'vacay.weekend': '週末', + 'vacay.modeVacation': '休假', + 'vacay.modeCompany': '公司假日', + 'vacay.entitlement': '年假額度', + 'vacay.entitlementDays': '天', + 'vacay.used': '已用', + 'vacay.remaining': '剩餘', + 'vacay.carriedOver': '從 {year} 結轉', + 'vacay.blockWeekends': '鎖定週末', + 'vacay.blockWeekendsHint': '禁止在週六和週日安排假期', + 'vacay.weekendDays': '週末', + 'vacay.mon': '週一', + 'vacay.tue': '週二', + 'vacay.wed': '週三', + 'vacay.thu': '週四', + 'vacay.fri': '週五', + 'vacay.sat': '週六', + 'vacay.sun': '週日', + 'vacay.publicHolidays': '公共假日', + 'vacay.publicHolidaysHint': '在日曆中標記公共假日', + 'vacay.selectCountry': '選擇國家', + 'vacay.selectRegion': '選擇地區(可選)', + 'vacay.companyHolidays': '公司假日', + 'vacay.companyHolidaysHint': '允許標記公司統一休假日', + 'vacay.companyHolidaysNoDeduct': '公司假日不計入年假天數。', + 'vacay.weekStart': '每週開始於', + 'vacay.weekStartHint': '選擇日曆週從週一還是週日開始', + 'vacay.carryOver': '結轉', + 'vacay.carryOverHint': '自動將剩餘年假天數結轉到下一年', + 'vacay.sharing': '共享', + 'vacay.sharingHint': '與其他 TREK 使用者共享你的假期計劃', + 'vacay.owner': '所有者', + 'vacay.shareEmailPlaceholder': 'TREK 使用者郵箱', + 'vacay.shareSuccess': '計劃共享成功', + 'vacay.shareError': '無法共享計劃', + 'vacay.dissolve': '解除合併', + 'vacay.dissolveHint': '重新分離日曆。你的記錄將被保留。', + 'vacay.dissolveAction': '解除', + 'vacay.dissolved': '日曆已分離', + 'vacay.fusedWith': '已合併', + 'vacay.you': '你', + 'vacay.noData': '暫無資料', + 'vacay.changeColor': '更改顏色', + 'vacay.inviteUser': '邀請使用者', + 'vacay.inviteHint': '邀請其他 TREK 使用者共享合併的假期日曆。', + 'vacay.selectUser': '選擇使用者', + 'vacay.sendInvite': '傳送邀請', + 'vacay.inviteSent': '邀請已傳送', + 'vacay.inviteError': '無法傳送邀請', + 'vacay.pending': '待處理', + 'vacay.noUsersAvailable': '沒有可用使用者', + 'vacay.accept': '接受', + 'vacay.decline': '拒絕', + 'vacay.acceptFusion': '接受併合並', + 'vacay.inviteTitle': '合併請求', + 'vacay.inviteWantsToFuse': '想要與你共享假期日曆。', + 'vacay.fuseInfo1': '你們雙方將在一個共享日曆中看到所有假期記錄。', + 'vacay.fuseInfo2': '雙方都可以為對方建立和編輯記錄。', + 'vacay.fuseInfo3': '雙方都可以刪除記錄和修改年假額度。', + 'vacay.fuseInfo4': '公共假日和公司假日等設定將共享。', + 'vacay.fuseInfo5': '任何一方都可以隨時解除合併。你的記錄將被保留。', + 'vacay.addCalendar': '新增日曆', + 'vacay.calendarColor': '顏色', + 'vacay.calendarLabel': '標籤', + 'vacay.noCalendars': '無日曆', +}; +export default vacay; diff --git a/shared/src/i18n/zh/admin.ts b/shared/src/i18n/zh/admin.ts new file mode 100644 index 00000000..0e5191b3 --- /dev/null +++ b/shared/src/i18n/zh/admin.ts @@ -0,0 +1,330 @@ +import type { TranslationStrings } from '../types'; + +const admin: TranslationStrings = { + 'admin.notifications.title': '通知', + 'admin.notifications.hint': '选择一个通知渠道。一次只能激活一个。', + 'admin.notifications.none': '已禁用', + 'admin.notifications.email': '电子邮件 (SMTP)', + 'admin.notifications.webhook': 'Webhook', + 'admin.notifications.save': '保存通知设置', + 'admin.notifications.saved': '通知设置已保存', + 'admin.notifications.testWebhook': '发送测试 Webhook', + 'admin.notifications.testWebhookSuccess': '测试 Webhook 发送成功', + 'admin.notifications.testWebhookFailed': '测试 Webhook 发送失败', + 'admin.smtp.title': '邮件与通知', + 'admin.smtp.hint': '用于发送电子邮件通知的 SMTP 配置。', + 'admin.smtp.testButton': '发送测试邮件', + 'admin.webhook.hint': '向外部 Webhook 发送通知(Discord、Slack 等)。', + 'admin.smtp.testSuccess': '测试邮件发送成功', + 'admin.smtp.testFailed': '测试邮件发送失败', + 'admin.title': '管理后台', + 'admin.subtitle': '用户管理和系统设置', + 'admin.tabs.users': '用户', + 'admin.tabs.categories': '分类', + 'admin.tabs.backup': '备份', + 'admin.tabs.audit': '审计', + 'admin.stats.users': '用户', + 'admin.stats.trips': '旅行', + 'admin.stats.places': '地点', + 'admin.stats.photos': '照片', + 'admin.stats.files': '文件', + 'admin.table.user': '用户', + 'admin.table.email': '邮箱', + 'admin.table.role': '角色', + 'admin.table.created': '创建时间', + 'admin.table.lastLogin': '最后登录', + 'admin.table.actions': '操作', + 'admin.you': '(你)', + 'admin.editUser': '编辑用户', + 'admin.newPassword': '新密码', + 'admin.newPasswordHint': '留空则保持当前密码', + 'admin.deleteUser': '删除用户「{name}」?所有旅行将被永久删除。', + 'admin.deleteUserTitle': '删除用户', + 'admin.newPasswordPlaceholder': '输入新密码…', + 'admin.toast.loadError': '加载管理数据失败', + 'admin.toast.userUpdated': '用户已更新', + 'admin.toast.updateError': '更新失败', + 'admin.toast.userDeleted': '用户已删除', + 'admin.toast.deleteError': '删除失败', + 'admin.toast.cannotDeleteSelf': '不能删除自己的账户', + 'admin.toast.userCreated': '用户已创建', + 'admin.toast.createError': '创建用户失败', + 'admin.toast.fieldsRequired': '用户名、邮箱和密码为必填项', + 'admin.createUser': '创建用户', + 'admin.invite.title': '邀请链接', + 'admin.invite.subtitle': '创建一次性注册链接', + 'admin.invite.create': '创建链接', + 'admin.invite.createAndCopy': '创建并复制', + 'admin.invite.empty': '尚未创建邀请链接', + 'admin.invite.maxUses': '最大使用次数', + 'admin.invite.expiry': '有效期', + 'admin.invite.uses': '已使用', + 'admin.invite.expiresAt': '过期时间', + 'admin.invite.createdBy': '由', + 'admin.invite.active': '有效', + 'admin.invite.expired': '已过期', + 'admin.invite.usedUp': '已用完', + 'admin.invite.copied': '邀请链接已复制', + 'admin.invite.copyLink': '复制链接', + 'admin.invite.deleted': '邀请链接已删除', + 'admin.invite.createError': '创建链接失败', + 'admin.invite.deleteError': '删除链接失败', + 'admin.tabs.settings': '设置', + 'admin.allowRegistration': '允许注册', + 'admin.allowRegistrationHint': '新用户可以自行注册', + 'admin.authMethods': 'Authentication Methods', + 'admin.passwordLogin': 'Password Login', + 'admin.passwordLoginHint': 'Allow users to sign in with email and password', + 'admin.passwordRegistration': 'Password Registration', + 'admin.passwordRegistrationHint': + 'Allow new users to register with email and password', + 'admin.oidcLogin': 'SSO Login', + 'admin.oidcLoginHint': 'Allow users to sign in with SSO', + 'admin.oidcRegistration': 'SSO Auto-Provisioning', + 'admin.oidcRegistrationHint': + 'Automatically create accounts for new SSO users', + 'admin.envOverrideHint': + 'Password login settings are controlled by the OIDC_ONLY environment variable and cannot be changed here.', + 'admin.lockoutWarning': 'At least one login method must remain enabled', + 'admin.requireMfa': '要求双因素身份验证(2FA)', + 'admin.requireMfaHint': + '未启用 2FA 的用户必须先完成设置中的配置才能使用应用。', + 'admin.apiKeys': 'API 密钥', + 'admin.apiKeysHint': '可选。启用地点的扩展数据,如照片和天气。', + 'admin.mapsKey': 'Google Maps API 密钥', + 'admin.mapsKeyHint': '用于地点搜索。在 console.cloud.google.com 获取', + 'admin.mapsKeyHintLong': + '没有 API 密钥时,使用 OpenStreetMap 搜索地点。有了 Google API 密钥,还可以加载照片、评分和营业时间。在 console.cloud.google.com 获取。', + 'admin.recommended': '推荐', + 'admin.weatherKey': 'OpenWeatherMap API 密钥', + 'admin.weatherKeyHint': '用于天气数据。在 openweathermap.org 免费获取', + 'admin.validateKey': '测试', + 'admin.keyValid': '已连接', + 'admin.keyInvalid': '无效', + 'admin.keySaved': 'API 密钥已保存', + 'admin.oidcTitle': '单点登录 (OIDC)', + 'admin.oidcSubtitle': + '允许通过 Google、Apple、Authentik 或 Keycloak 等外部提供商登录。', + 'admin.oidcDisplayName': '显示名称', + 'admin.oidcIssuer': '颁发者 URL', + 'admin.oidcIssuerHint': + '提供商的 OpenID Connect 颁发者 URL。如 https://accounts.google.com', + 'admin.oidcSaved': 'OIDC 配置已保存', + 'admin.oidcOnlyMode': '禁用密码登录', + 'admin.oidcOnlyModeHint': '启用后,仅允许 SSO 登录。密码登录和注册将被禁用。', + 'admin.fileTypes': '允许的文件类型', + 'admin.fileTypesHint': '配置用户可以上传的文件类型。', + 'admin.fileTypesFormat': + '以逗号分隔的扩展名(如 jpg,png,pdf,doc)。使用 * 允许所有类型。', + 'admin.fileTypesSaved': '文件类型设置已保存', + 'admin.placesPhotos.title': '地点照片', + 'admin.placesPhotos.subtitle': + '从 Google Places API 获取照片。禁用可节省 API 配额。Wikimedia 照片不受影响。', + 'admin.placesAutocomplete.title': '地点自动补全', + 'admin.placesAutocomplete.subtitle': + '使用 Google Places API 提供搜索建议。禁用可节省 API 配额。', + 'admin.placesDetails.title': '地点详情', + 'admin.placesDetails.subtitle': + '从 Google Places API 获取地点详细信息(营业时间、评分、网站)。禁用可节省 API 配额。', + 'admin.bagTracking.title': '行李追踪', + 'admin.bagTracking.subtitle': '为打包物品启用重量和行李分配', + 'admin.collab.chat.title': '聊天', + 'admin.collab.chat.subtitle': '实时消息协作', + 'admin.collab.notes.title': '笔记', + 'admin.collab.notes.subtitle': '共享笔记和文档', + 'admin.collab.polls.title': '投票', + 'admin.collab.polls.subtitle': '群组投票和表决', + 'admin.collab.whatsnext.title': '下一步', + 'admin.collab.whatsnext.subtitle': '活动建议和后续步骤', + 'admin.tabs.config': '个性化', + 'admin.tabs.defaults': '用户默认设置', + 'admin.defaultSettings.title': '用户默认设置', + 'admin.defaultSettings.description': + '设置实例范围的默认值。未更改设置的用户将看到这些值。用户自己的更改始终优先。', + 'admin.defaultSettings.saved': '默认值已保存', + 'admin.defaultSettings.reset': '重置为内置默认值', + 'admin.defaultSettings.resetToBuiltIn': '重置', + 'admin.tabs.templates': '打包模板', + 'admin.packingTemplates.title': '打包模板', + 'admin.packingTemplates.subtitle': '创建可复用的旅行打包清单', + 'admin.packingTemplates.create': '新建模板', + 'admin.packingTemplates.namePlaceholder': '模板名称(如:海滩度假)', + 'admin.packingTemplates.empty': '尚未创建模板', + 'admin.packingTemplates.items': '物品', + 'admin.packingTemplates.categories': '分类', + 'admin.packingTemplates.itemName': '物品名称', + 'admin.packingTemplates.itemCategory': '分类', + 'admin.packingTemplates.categoryName': '分类名称(如:衣物)', + 'admin.packingTemplates.addCategory': '添加分类', + 'admin.packingTemplates.created': '模板已创建', + 'admin.packingTemplates.deleted': '模板已删除', + 'admin.packingTemplates.loadError': '加载模板失败', + 'admin.packingTemplates.createError': '创建模板失败', + 'admin.packingTemplates.deleteError': '删除模板失败', + 'admin.packingTemplates.saveError': '保存失败', + 'admin.tabs.addons': '扩展', + 'admin.addons.title': '扩展', + 'admin.addons.subtitle': '启用或禁用功能以自定义你的 TREK 体验。', + 'admin.addons.catalog.memories.name': '照片 (Immich)', + 'admin.addons.catalog.memories.description': '通过 Immich 实例分享旅行照片', + 'admin.addons.catalog.mcp.name': 'MCP', + 'admin.addons.catalog.mcp.description': '用于 AI 助手集成的模型上下文协议', + 'admin.addons.catalog.packing.name': '列表', + 'admin.addons.catalog.packing.description': '行程打包清单与待办任务', + 'admin.addons.catalog.budget.name': '预算', + 'admin.addons.catalog.budget.description': '跟踪支出并规划旅行预算', + 'admin.addons.catalog.documents.name': '文档', + 'admin.addons.catalog.documents.description': '存储和管理旅行文档', + 'admin.addons.catalog.vacay.name': 'Vacay', + 'admin.addons.catalog.vacay.description': '带日历视图的个人假期规划器', + 'admin.addons.catalog.atlas.name': 'Atlas', + 'admin.addons.catalog.atlas.description': + '标记已访问国家和旅行统计的世界地图', + 'admin.addons.catalog.collab.name': 'Collab', + 'admin.addons.catalog.collab.description': '旅行规划的实时笔记、投票和聊天', + 'admin.addons.subtitleBefore': '启用或禁用功能以自定义你的 ', + 'admin.addons.subtitleAfter': ' 体验。', + 'admin.addons.enabled': '已启用', + 'admin.addons.disabled': '已禁用', + 'admin.addons.type.trip': '旅行', + 'admin.addons.type.global': '全局', + 'admin.addons.type.integration': '集成', + 'admin.addons.tripHint': '在每次旅行中作为标签页显示', + 'admin.addons.globalHint': '在主导航中作为独立板块显示', + 'admin.addons.integrationHint': '后端服务和 API 集成,无专属页面', + 'admin.addons.toast.updated': '扩展已更新', + 'admin.addons.toast.error': '更新扩展失败', + 'admin.addons.noAddons': '暂无可用扩展', + 'admin.weather.title': '天气数据', + 'admin.weather.badge': '自 2026 年 3 月 24 日起', + 'admin.weather.description': + 'TREK 使用 Open-Meteo 作为天气数据源。Open-Meteo 是免费的开源天气服务——无需 API 密钥。', + 'admin.weather.forecast': '16 天天气预报', + 'admin.weather.forecastDesc': '之前为 5 天 (OpenWeatherMap)', + 'admin.weather.climate': '历史气候数据', + 'admin.weather.climateDesc': '16 天预报之外的日期使用过去 85 年的平均值', + 'admin.weather.requests': '每天 10,000 次请求', + 'admin.weather.requestsDesc': '免费,无需 API 密钥', + 'admin.weather.locationHint': + '天气基于每天中第一个有坐标的地点。如果当天没有分配地点,则使用地点列表中的任意地点作为参考。', + 'admin.tabs.mcpTokens': 'MCP 访问', + 'admin.mcpTokens.title': 'MCP 访问', + 'admin.mcpTokens.subtitle': '管理所有用户的 OAuth 会话和 API 令牌', + 'admin.mcpTokens.sectionTitle': 'API 令牌', + 'admin.mcpTokens.owner': '所有者', + 'admin.mcpTokens.tokenName': '令牌名称', + 'admin.mcpTokens.created': '创建时间', + 'admin.mcpTokens.lastUsed': '最后使用', + 'admin.mcpTokens.never': '从未', + 'admin.mcpTokens.empty': '尚未创建任何 MCP 令牌', + 'admin.mcpTokens.deleteTitle': '删除令牌', + 'admin.mcpTokens.deleteMessage': + '此令牌将立即被撤销。用户将失去通过此令牌的 MCP 访问权限。', + 'admin.mcpTokens.deleteSuccess': '令牌已删除', + 'admin.mcpTokens.deleteError': '删除令牌失败', + 'admin.mcpTokens.loadError': '加载令牌失败', + 'admin.oauthSessions.sectionTitle': 'OAuth 会话', + 'admin.oauthSessions.clientName': '客户端', + 'admin.oauthSessions.owner': '所有者', + 'admin.oauthSessions.scopes': '权限范围', + 'admin.oauthSessions.created': '创建时间', + 'admin.oauthSessions.empty': '暂无活跃的 OAuth 会话', + 'admin.oauthSessions.revokeTitle': '撤销会话', + 'admin.oauthSessions.revokeMessage': + '此 OAuth 会话将立即被撤销。客户端将失去 MCP 访问权限。', + 'admin.oauthSessions.revokeSuccess': '会话已撤销', + 'admin.oauthSessions.revokeError': '撤销会话失败', + 'admin.oauthSessions.loadError': '加载 OAuth 会话失败', + 'admin.tabs.github': 'GitHub', + 'admin.audit.subtitle': '安全与管理员操作记录(备份、用户、MFA、设置)。', + 'admin.audit.empty': '暂无审计记录。', + 'admin.audit.refresh': '刷新', + 'admin.audit.loadMore': '加载更多', + 'admin.audit.showing': '已加载 {count} 条 · 共 {total} 条', + 'admin.audit.col.time': '时间', + 'admin.audit.col.user': '用户', + 'admin.audit.col.action': '操作', + 'admin.audit.col.resource': '资源', + 'admin.audit.col.ip': 'IP', + 'admin.audit.col.details': '详情', + 'admin.github.title': '版本历史', + 'admin.github.subtitle': '{repo} 的最新更新', + 'admin.github.latest': '最新', + 'admin.github.prerelease': '预发布', + 'admin.github.showDetails': '显示详情', + 'admin.github.hideDetails': '隐藏详情', + 'admin.github.loadMore': '加载更多', + 'admin.github.loading': '加载中...', + 'admin.github.support': '帮助我继续开发 TREK', + 'admin.github.error': '加载版本失败', + 'admin.github.by': '作者', + 'admin.update.available': '有可用更新', + 'admin.update.text': 'TREK {version} 已发布。你当前使用的是 {current}。', + 'admin.update.button': '在 GitHub 查看', + 'admin.update.install': '安装更新', + 'admin.update.confirmTitle': '确定安装更新?', + 'admin.update.confirmText': + 'TREK 将从 {current} 更新到 {version}。服务器将自动重启。', + 'admin.update.dataInfo': + '你的所有数据(旅行、用户、API 密钥、上传文件、Vacay、Atlas、预算)将被保留。', + 'admin.update.warning': '重启期间应用将短暂不可用。', + 'admin.update.confirm': '立即更新', + 'admin.update.installing': '更新中…', + 'admin.update.success': '更新已安装!服务器正在重启…', + 'admin.update.failed': '更新失败', + 'admin.update.backupHint': '建议在更新前创建备份。', + 'admin.update.backupLink': '前往备份', + 'admin.update.howTo': '如何更新', + 'admin.update.dockerText': + '你的 TREK 实例运行在 Docker 中。要更新到 {version},请在服务器上执行以下命令:', + 'admin.update.reloadHint': '请在几秒后刷新页面。', + 'admin.tabs.permissions': '权限', + 'admin.notifications.emailPanel.title': 'Email (SMTP)', + 'admin.notifications.webhookPanel.title': 'Webhook', + 'admin.notifications.inappPanel.title': 'In-App', + 'admin.notifications.inappPanel.hint': + '应用内通知始终处于活跃状态,无法全局禁用。', + 'admin.notifications.adminWebhookPanel.title': '管理员 Webhook', + 'admin.notifications.adminWebhookPanel.hint': + '此 Webhook 专用于管理员通知(如版本更新提醒)。它与用户 Webhook 相互独立,配置 URL 后自动触发。', + 'admin.notifications.adminWebhookPanel.saved': '管理员 Webhook URL 已保存', + 'admin.notifications.adminWebhookPanel.testSuccess': '测试 Webhook 发送成功', + 'admin.notifications.adminWebhookPanel.testFailed': '测试 Webhook 失败', + 'admin.notifications.adminWebhookPanel.alwaysOnHint': + '配置 URL 后管理员 Webhook 自动触发', + 'admin.notifications.ntfy': 'Ntfy', + 'admin.ntfy.hint': + '允许用户配置自己的 ntfy 主题以接收推送通知。在下方设置默认服务器以预填充用户设置。', + 'admin.notifications.testNtfy': '发送测试 Ntfy', + 'admin.notifications.testNtfySuccess': '测试 Ntfy 发送成功', + 'admin.notifications.testNtfyFailed': '测试 Ntfy 失败', + 'admin.notifications.adminNtfyPanel.title': '管理员 Ntfy', + 'admin.notifications.adminNtfyPanel.hint': + '此 Ntfy 主题专用于管理员通知(如版本更新提醒)。它与每用户主题相互独立,配置后始终触发。', + 'admin.notifications.adminNtfyPanel.serverLabel': 'Ntfy 服务器 URL', + 'admin.notifications.adminNtfyPanel.serverHint': + '同时用作用户 ntfy 通知的默认服务器。留空则默认使用 ntfy.sh。用户可在其自己的设置中覆盖此项。', + 'admin.notifications.adminNtfyPanel.serverPlaceholder': 'https://ntfy.sh', + 'admin.notifications.adminNtfyPanel.topicLabel': '管理员主题', + 'admin.notifications.adminNtfyPanel.topicPlaceholder': 'trek-admin-alerts', + 'admin.notifications.adminNtfyPanel.tokenLabel': '访问令牌(可选)', + 'admin.notifications.adminNtfyPanel.tokenCleared': '管理员访问令牌已清除', + 'admin.notifications.adminNtfyPanel.saved': '管理员 Ntfy 设置已保存', + 'admin.notifications.adminNtfyPanel.test': '发送测试 Ntfy', + 'admin.notifications.adminNtfyPanel.testSuccess': '测试 Ntfy 发送成功', + 'admin.notifications.adminNtfyPanel.testFailed': '测试 Ntfy 失败', + 'admin.notifications.adminNtfyPanel.alwaysOnHint': + '配置主题后管理员 Ntfy 始终触发', + 'admin.notifications.adminNotificationsHint': + '配置哪些渠道发送管理员通知(如版本更新提醒)。设置管理员 Webhook URL 后,Webhook 将自动触发。', + 'admin.notifications.tripReminders.title': '行程提醒', + 'admin.notifications.tripReminders.hint': + '在行程开始前发送提醒通知(需要在行程中设置提醒天数)。', + 'admin.notifications.tripReminders.enabled': '行程提醒已启用', + 'admin.notifications.tripReminders.disabled': '行程提醒已禁用', + 'admin.tabs.notifications': '通知', + 'admin.addons.catalog.journey.name': '旅程', + 'admin.addons.catalog.journey.description': + '旅行追踪与旅行日志,包含签到、照片和每日故事', +}; +export default admin; diff --git a/shared/src/i18n/zh/airport.ts b/shared/src/i18n/zh/airport.ts new file mode 100644 index 00000000..f96c89db --- /dev/null +++ b/shared/src/i18n/zh/airport.ts @@ -0,0 +1,6 @@ +import type { TranslationStrings } from '../types'; + +const airport: TranslationStrings = { + 'airport.searchPlaceholder': '机场代码或城市(如 FRA)', +}; +export default airport; diff --git a/shared/src/i18n/zh/atlas.ts b/shared/src/i18n/zh/atlas.ts new file mode 100644 index 00000000..c42cfe76 --- /dev/null +++ b/shared/src/i18n/zh/atlas.ts @@ -0,0 +1,58 @@ +import type { TranslationStrings } from '../types'; + +const atlas: TranslationStrings = { + 'atlas.subtitle': '你的全球旅行足迹', + 'atlas.countries': '国家', + 'atlas.trips': '旅行', + 'atlas.places': '地点', + 'atlas.days': '天', + 'atlas.visitedCountries': '已访问国家', + 'atlas.cities': '城市', + 'atlas.noData': '暂无旅行数据', + 'atlas.noDataHint': '创建旅行并添加地点以查看世界地图', + 'atlas.lastTrip': '上次旅行', + 'atlas.nextTrip': '下次旅行', + 'atlas.daysLeft': '天后出发', + 'atlas.streak': '连续', + 'atlas.year': '年', + 'atlas.years': '年', + 'atlas.yearInRow': '年连续', + 'atlas.yearsInRow': '年连续', + 'atlas.tripIn': '次旅行在', + 'atlas.tripsIn': '次旅行在', + 'atlas.since': '自', + 'atlas.europe': '欧洲', + 'atlas.asia': '亚洲', + 'atlas.northAmerica': '北美洲', + 'atlas.southAmerica': '南美洲', + 'atlas.africa': '非洲', + 'atlas.oceania': '大洋洲', + 'atlas.other': '其他', + 'atlas.firstVisit': '首次旅行', + 'atlas.lastVisitLabel': '最近旅行', + 'atlas.tripSingular': '次旅行', + 'atlas.tripPlural': '次旅行', + 'atlas.placeVisited': '个地点已访问', + 'atlas.placesVisited': '个地点已访问', + 'atlas.statsTab': '统计', + 'atlas.bucketTab': '心愿单', + 'atlas.addBucket': '添加到心愿单', + 'atlas.bucketNamePlaceholder': '地点或目的地...', + 'atlas.bucketNotesPlaceholder': '备注(可选)', + 'atlas.bucketEmpty': '你的心愿单是空的', + 'atlas.bucketEmptyHint': '添加你梦想去的地方', + 'atlas.unmark': '移除', + 'atlas.confirmMark': '将此国家标记为已访问?', + 'atlas.confirmUnmark': '从已访问列表中移除此国家?', + 'atlas.confirmUnmarkRegion': '从已访问列表中移除此地区?', + 'atlas.markVisited': '标记为已访问', + 'atlas.markVisitedHint': '将此国家添加到已访问列表', + 'atlas.markRegionVisitedHint': '将此地区添加到已访问列表', + 'atlas.addToBucket': '添加到心愿单', + 'atlas.addPoi': '添加地点', + 'atlas.searchCountry': '搜索国家...', + 'atlas.month': '月份', + 'atlas.addToBucketHint': '保存为想去的地方', + 'atlas.bucketWhen': '你计划什么时候去?', +}; +export default atlas; diff --git a/shared/src/i18n/zh/backup.ts b/shared/src/i18n/zh/backup.ts new file mode 100644 index 00000000..8092734a --- /dev/null +++ b/shared/src/i18n/zh/backup.ts @@ -0,0 +1,74 @@ +import type { TranslationStrings } from '../types'; + +const backup: TranslationStrings = { + 'backup.title': '数据备份', + 'backup.subtitle': '数据库和所有上传文件', + 'backup.refresh': '刷新', + 'backup.upload': '上传备份', + 'backup.uploading': '上传中…', + 'backup.create': '创建备份', + 'backup.creating': '创建中…', + 'backup.empty': '暂无备份', + 'backup.createFirst': '创建第一个备份', + 'backup.download': '下载', + 'backup.restore': '恢复', + 'backup.confirm.restore': + '恢复备份「{name}」?\n\n所有当前数据将被备份数据替换。', + 'backup.confirm.uploadRestore': + '上传并恢复备份文件「{name}」?\n\n所有当前数据将被覆盖。', + 'backup.confirm.delete': '删除备份「{name}」?', + 'backup.toast.loadError': '加载备份失败', + 'backup.toast.created': '备份创建成功', + 'backup.toast.createError': '创建备份失败', + 'backup.toast.restored': '备份已恢复。页面即将刷新…', + 'backup.toast.restoreError': '恢复失败', + 'backup.toast.uploadError': '上传失败', + 'backup.toast.deleted': '备份已删除', + 'backup.toast.deleteError': '删除失败', + 'backup.toast.downloadError': '下载失败', + 'backup.toast.settingsSaved': '自动备份设置已保存', + 'backup.toast.settingsError': '保存设置失败', + 'backup.auto.title': '自动备份', + 'backup.auto.subtitle': '按计划自动备份', + 'backup.auto.enable': '启用自动备份', + 'backup.auto.enableHint': '将按所选计划自动创建备份', + 'backup.auto.interval': '间隔', + 'backup.auto.hour': '执行时间', + 'backup.auto.hourHint': '服务器本地时间({format} 格式)', + 'backup.auto.dayOfWeek': '星期几', + 'backup.auto.dayOfMonth': '每月几号', + 'backup.auto.dayOfMonthHint': '限 1–28 以兼容所有月份', + 'backup.auto.scheduleSummary': '计划', + 'backup.auto.summaryDaily': '每天 {hour}:00', + 'backup.auto.summaryWeekly': '每{day} {hour}:00', + 'backup.auto.summaryMonthly': '每月 {day} 号 {hour}:00', + 'backup.auto.envLocked': 'Docker', + 'backup.auto.envLockedHint': + '自动备份通过 Docker 环境变量配置。要更改设置,请更新 docker-compose.yml 并重启容器。', + 'backup.auto.copyEnv': '复制 Docker 环境变量', + 'backup.auto.envCopied': 'Docker 环境变量已复制到剪贴板', + 'backup.auto.keepLabel': '自动删除旧备份', + 'backup.dow.sunday': '周日', + 'backup.dow.monday': '周一', + 'backup.dow.tuesday': '周二', + 'backup.dow.wednesday': '周三', + 'backup.dow.thursday': '周四', + 'backup.dow.friday': '周五', + 'backup.dow.saturday': '周六', + 'backup.interval.hourly': '每小时', + 'backup.interval.daily': '每天', + 'backup.interval.weekly': '每周', + 'backup.interval.monthly': '每月', + 'backup.keep.1day': '1 天', + 'backup.keep.3days': '3 天', + 'backup.keep.7days': '7 天', + 'backup.keep.14days': '14 天', + 'backup.keep.30days': '30 天', + 'backup.keep.forever': '永久保留', + 'backup.restoreConfirmTitle': '恢复备份?', + 'backup.restoreWarning': + '所有当前数据(旅行、地点、用户、上传文件)将被备份数据永久替换。此操作无法撤销。', + 'backup.restoreTip': '提示:恢复前建议先备份当前状态。', + 'backup.restoreConfirm': '确认恢复', +}; +export default backup; diff --git a/shared/src/i18n/zh/budget.ts b/shared/src/i18n/zh/budget.ts new file mode 100644 index 00000000..884c29d6 --- /dev/null +++ b/shared/src/i18n/zh/budget.ts @@ -0,0 +1,42 @@ +import type { TranslationStrings } from '../types'; + +const budget: TranslationStrings = { + 'budget.title': '预算', + 'budget.exportCsv': '导出 CSV', + 'budget.emptyTitle': '尚未创建预算', + 'budget.emptyText': '创建分类和条目来规划旅行预算', + 'budget.emptyPlaceholder': '输入分类名称...', + 'budget.createCategory': '创建分类', + 'budget.category': '分类', + 'budget.categoryName': '分类名称', + 'budget.table.name': '名称', + 'budget.table.total': '合计', + 'budget.table.persons': '人数', + 'budget.table.days': '天数', + 'budget.table.perPerson': '人均', + 'budget.table.perDay': '日均', + 'budget.table.perPersonDay': '人日均', + 'budget.table.note': '备注', + 'budget.table.date': '日期', + 'budget.newEntry': '新建条目', + 'budget.defaultEntry': '新建条目', + 'budget.defaultCategory': '新分类', + 'budget.total': '合计', + 'budget.totalBudget': '总预算', + 'budget.byCategory': '按分类', + 'budget.editTooltip': '点击编辑', + 'budget.linkedToReservation': '已关联到预订——请在那里编辑名称', + 'budget.confirm.deleteCategory': + '确定删除分类「{name}」及其 {count} 个条目?', + 'budget.deleteCategory': '删除分类', + 'budget.perPerson': '人均', + 'budget.paid': '已支付', + 'budget.open': '未支付', + 'budget.noMembers': '未分配成员', + 'budget.settlement': '结算', + 'budget.settlementInfo': + '点击预算项目上的成员头像将其标记为绿色——表示该成员已付款。结算会显示谁欠谁多少。', + 'budget.netBalances': '净余额', + 'budget.categoriesLabel': '类别', +}; +export default budget; diff --git a/shared/src/i18n/zh/categories.ts b/shared/src/i18n/zh/categories.ts new file mode 100644 index 00000000..4e48a53a --- /dev/null +++ b/shared/src/i18n/zh/categories.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const categories: TranslationStrings = { + 'categories.title': '分类', + 'categories.subtitle': '管理地点分类', + 'categories.new': '新建分类', + 'categories.empty': '暂无分类', + 'categories.namePlaceholder': '分类名称', + 'categories.icon': '图标', + 'categories.color': '颜色', + 'categories.customColor': '选择自定义颜色', + 'categories.preview': '预览', + 'categories.defaultName': '分类', + 'categories.update': '更新', + 'categories.create': '创建', + 'categories.confirm.delete': '删除分类?该分类下的地点不会被删除。', + 'categories.toast.loadError': '加载分类失败', + 'categories.toast.nameRequired': '请输入名称', + 'categories.toast.updated': '分类已更新', + 'categories.toast.created': '分类已创建', + 'categories.toast.saveError': '保存失败', + 'categories.toast.deleted': '分类已删除', + 'categories.toast.deleteError': '删除失败', +}; +export default categories; diff --git a/shared/src/i18n/zh/collab.ts b/shared/src/i18n/zh/collab.ts new file mode 100644 index 00000000..b682f4c8 --- /dev/null +++ b/shared/src/i18n/zh/collab.ts @@ -0,0 +1,73 @@ +import type { TranslationStrings } from '../types'; + +const collab: TranslationStrings = { + 'collab.tabs.chat': '聊天', + 'collab.tabs.notes': '笔记', + 'collab.tabs.polls': '投票', + 'collab.whatsNext.title': '接下来', + 'collab.whatsNext.today': '今天', + 'collab.whatsNext.tomorrow': '明天', + 'collab.whatsNext.empty': '暂无活动', + 'collab.whatsNext.until': '至', + 'collab.whatsNext.emptyHint': '有时间安排的活动将显示在此', + 'collab.chat.send': '发送', + 'collab.chat.placeholder': '输入消息...', + 'collab.chat.empty': '开始对话', + 'collab.chat.emptyHint': '消息对所有旅行成员可见', + 'collab.chat.emptyDesc': '与旅伴分享想法、计划和动态', + 'collab.chat.today': '今天', + 'collab.chat.yesterday': '昨天', + 'collab.chat.deletedMessage': '删除了一条消息', + 'collab.chat.reply': '回复', + 'collab.chat.loadMore': '加载更早的消息', + 'collab.chat.justNow': '刚刚', + 'collab.chat.minutesAgo': '{n} 分钟前', + 'collab.chat.hoursAgo': '{n} 小时前', + 'collab.notes.title': '笔记', + 'collab.notes.new': '新建笔记', + 'collab.notes.empty': '暂无笔记', + 'collab.notes.emptyHint': '开始记录想法和计划', + 'collab.notes.all': '全部', + 'collab.notes.titlePlaceholder': '笔记标题', + 'collab.notes.contentPlaceholder': '写点什么...', + 'collab.notes.categoryPlaceholder': '分类', + 'collab.notes.newCategory': '新建分类...', + 'collab.notes.category': '分类', + 'collab.notes.noCategory': '无分类', + 'collab.notes.color': '颜色', + 'collab.notes.save': '保存', + 'collab.notes.cancel': '取消', + 'collab.notes.edit': '编辑', + 'collab.notes.delete': '删除', + 'collab.notes.pin': '置顶', + 'collab.notes.unpin': '取消置顶', + 'collab.notes.daysAgo': '{n} 天前', + 'collab.notes.categorySettings': '管理分类', + 'collab.notes.create': '创建', + 'collab.notes.website': '网站', + 'collab.notes.websitePlaceholder': 'https://...', + 'collab.notes.attachFiles': '附加文件', + 'collab.notes.noCategoriesYet': '暂无分类', + 'collab.notes.emptyDesc': '创建一个笔记开始吧', + 'collab.polls.title': '投票', + 'collab.polls.new': '新建投票', + 'collab.polls.empty': '暂无投票', + 'collab.polls.emptyHint': '向团队提问并一起投票', + 'collab.polls.question': '问题', + 'collab.polls.questionPlaceholder': '我们应该做什么?', + 'collab.polls.addOption': '+ 添加选项', + 'collab.polls.optionPlaceholder': '选项 {n}', + 'collab.polls.create': '创建投票', + 'collab.polls.close': '关闭', + 'collab.polls.closed': '已关闭', + 'collab.polls.votes': '{n} 票', + 'collab.polls.vote': '{n} 票', + 'collab.polls.multipleChoice': '多选', + 'collab.polls.multiChoice': '多选', + 'collab.polls.deadline': '截止时间', + 'collab.polls.option': '选项', + 'collab.polls.options': '选项', + 'collab.polls.delete': '删除', + 'collab.polls.closedSection': '已关闭', +}; +export default collab; diff --git a/shared/src/i18n/zh/common.ts b/shared/src/i18n/zh/common.ts new file mode 100644 index 00000000..d335d6b0 --- /dev/null +++ b/shared/src/i18n/zh/common.ts @@ -0,0 +1,54 @@ +import type { TranslationStrings } from '../types'; + +const common: TranslationStrings = { + 'common.save': '保存', + 'common.showMore': '显示更多', + 'common.showLess': '收起', + 'common.cancel': '取消', + 'common.clear': '清除', + 'common.delete': '删除', + 'common.edit': '编辑', + 'common.add': '添加', + 'common.loading': '加载中...', + 'common.import': '导入', + 'common.select': '选择', + 'common.selectAll': '全选', + 'common.deselectAll': '取消全选', + 'common.error': '错误', + 'common.unknownError': '未知错误', + 'common.tooManyAttempts': '尝试次数过多,请稍后再试。', + 'common.back': '返回', + 'common.all': '全部', + 'common.close': '关闭', + 'common.open': '打开', + 'common.upload': '上传', + 'common.search': '搜索', + 'common.confirm': '确认', + 'common.ok': '确定', + 'common.yes': '是', + 'common.no': '否', + 'common.or': '或', + 'common.none': '无', + 'common.date': '日期', + 'common.rename': '重命名', + 'common.discardChanges': '放弃更改', + 'common.discard': '放弃', + 'common.name': '名称', + 'common.email': '邮箱', + 'common.password': '密码', + 'common.saving': '保存中...', + 'common.saved': '已保存', + 'common.expand': '展开', + 'common.collapse': '折叠', + 'common.update': '更新', + 'common.change': '修改', + 'common.uploading': '上传中…', + 'common.backToPlanning': '返回规划', + 'common.reset': '重置', + 'common.copy': '复制', + 'common.copied': '已复制', + 'common.justNow': '刚刚', + 'common.hoursAgo': '{count}小时前', + 'common.daysAgo': '{count}天前', +}; +export default common; diff --git a/shared/src/i18n/zh/dashboard.ts b/shared/src/i18n/zh/dashboard.ts new file mode 100644 index 00000000..6233b9be --- /dev/null +++ b/shared/src/i18n/zh/dashboard.ts @@ -0,0 +1,105 @@ +import type { TranslationStrings } from '../types'; + +const dashboard: TranslationStrings = { + 'dashboard.title': '我的旅行', + 'dashboard.subtitle.loading': '加载旅行中...', + 'dashboard.subtitle.trips': '{count} 次旅行({archived} 已归档)', + 'dashboard.subtitle.empty': '开始你的第一次旅行', + 'dashboard.subtitle.activeOne': '{count} 个进行中的旅行', + 'dashboard.subtitle.activeMany': '{count} 个进行中的旅行', + 'dashboard.subtitle.archivedSuffix': ' · {count} 已归档', + 'dashboard.newTrip': '新建旅行', + 'dashboard.gridView': '网格视图', + 'dashboard.listView': '列表视图', + 'dashboard.currency': '货币', + 'dashboard.timezone': '时区', + 'dashboard.localTime': '本地', + 'dashboard.timezoneCustomTitle': '自定义时区', + 'dashboard.timezoneCustomLabelPlaceholder': '标签(可选)', + 'dashboard.timezoneCustomTzPlaceholder': '如 America/New_York', + 'dashboard.timezoneCustomAdd': '添加', + 'dashboard.timezoneCustomErrorEmpty': '请输入时区标识符', + 'dashboard.timezoneCustomErrorInvalid': + '无效的时区。请使用 Europe/Berlin 这样的格式', + 'dashboard.timezoneCustomErrorDuplicate': '已添加', + 'dashboard.emptyTitle': '暂无旅行', + 'dashboard.emptyText': '创建你的第一次旅行,开始规划吧!', + 'dashboard.emptyButton': '创建第一次旅行', + 'dashboard.nextTrip': '下次旅行', + 'dashboard.shared': '共享', + 'dashboard.sharedBy': '由 {name} 分享', + 'dashboard.days': '天', + 'dashboard.places': '地点', + 'dashboard.members': '旅伴', + 'dashboard.archive': '归档', + 'dashboard.copyTrip': '复制', + 'dashboard.copySuffix': '副本', + 'dashboard.restore': '恢复', + 'dashboard.archived': '已归档', + 'dashboard.status.ongoing': '进行中', + 'dashboard.status.today': '今天', + 'dashboard.status.tomorrow': '明天', + 'dashboard.status.past': '已结束', + 'dashboard.status.daysLeft': '还剩 {count} 天', + 'dashboard.toast.loadError': '加载旅行失败', + 'dashboard.toast.created': '旅行创建成功!', + 'dashboard.toast.createError': '创建旅行失败', + 'dashboard.toast.updated': '旅行已更新!', + 'dashboard.toast.updateError': '更新旅行失败', + 'dashboard.toast.deleted': '旅行已删除', + 'dashboard.toast.deleteError': '删除旅行失败', + 'dashboard.toast.archived': '旅行已归档', + 'dashboard.toast.archiveError': '归档旅行失败', + 'dashboard.toast.restored': '旅行已恢复', + 'dashboard.toast.restoreError': '恢复旅行失败', + 'dashboard.toast.copied': '旅行已复制!', + 'dashboard.toast.copyError': '复制旅行失败', + 'dashboard.confirm.delete': + '删除旅行「{title}」?所有地点和计划将被永久删除。', + 'dashboard.editTrip': '编辑旅行', + 'dashboard.createTrip': '创建新旅行', + 'dashboard.tripTitle': '标题', + 'dashboard.tripTitlePlaceholder': '如:日本夏日之旅', + 'dashboard.tripDescription': '描述', + 'dashboard.tripDescriptionPlaceholder': '这次旅行是关于什么的?', + 'dashboard.startDate': '开始日期', + 'dashboard.endDate': '结束日期', + 'dashboard.dayCount': '天数', + 'dashboard.dayCountHint': '未设置旅行日期时要规划的天数。', + 'dashboard.noDateHint': '未设置日期——将默认创建 7 天。你可以随时修改。', + 'dashboard.coverImage': '封面图片', + 'dashboard.addCoverImage': '添加封面图片', + 'dashboard.addMembers': '旅伴', + 'dashboard.addMember': '添加成员', + 'dashboard.coverSaved': '封面图片已保存', + 'dashboard.coverUploadError': '上传失败', + 'dashboard.coverRemoveError': '移除失败', + 'dashboard.titleRequired': '标题为必填项', + 'dashboard.endDateError': '结束日期必须晚于开始日期', + 'dashboard.greeting.morning': '早上好,', + 'dashboard.greeting.afternoon': '下午好,', + 'dashboard.greeting.evening': '晚上好,', + 'dashboard.mobile.liveNow': '进行中', + 'dashboard.mobile.tripProgress': '旅行进度', + 'dashboard.mobile.daysLeft': '还剩 {count} 天', + 'dashboard.mobile.places': '地点', + 'dashboard.mobile.buddies': '旅伴', + 'dashboard.mobile.newTrip': '新建旅行', + 'dashboard.mobile.currency': '货币', + 'dashboard.mobile.timezone': '时区', + 'dashboard.mobile.upcomingTrips': '即将到来的旅行', + 'dashboard.mobile.yourTrips': '我的旅行', + 'dashboard.mobile.trips': '个旅行', + 'dashboard.mobile.starts': '出发', + 'dashboard.mobile.duration': '时长', + 'dashboard.mobile.day': '天', + 'dashboard.mobile.days': '天', + 'dashboard.mobile.ongoing': '进行中', + 'dashboard.mobile.startsToday': '今天出发', + 'dashboard.mobile.tomorrow': '明天', + 'dashboard.mobile.inDays': '{count} 天后', + 'dashboard.mobile.inMonths': '{count} 个月后', + 'dashboard.mobile.completed': '已完成', + 'dashboard.mobile.currencyConverter': '汇率转换', +}; +export default dashboard; diff --git a/shared/src/i18n/zh/day.ts b/shared/src/i18n/zh/day.ts new file mode 100644 index 00000000..dbd04fa4 --- /dev/null +++ b/shared/src/i18n/zh/day.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const day: TranslationStrings = { + 'day.precipProb': '降水概率', + 'day.precipitation': '降水量', + 'day.wind': '风速', + 'day.sunrise': '日出', + 'day.sunset': '日落', + 'day.hourlyForecast': '逐小时预报', + 'day.climateHint': '历史平均值——实际预报在该日期前 16 天内可用。', + 'day.noWeather': '无天气数据。请添加有坐标的地点。', + 'day.overview': '每日概览', + 'day.accommodation': '住宿', + 'day.addAccommodation': '添加住宿', + 'day.hotelDayRange': '应用到天数', + 'day.noPlacesForHotel': '请先在旅行中添加地点', + 'day.allDays': '全部', + 'day.checkIn': '入住', + 'day.checkInUntil': '截止', + 'day.checkOut': '退房', + 'day.confirmation': '确认号', + 'day.editAccommodation': '编辑住宿', + 'day.reservations': '预订', +}; +export default day; diff --git a/shared/src/i18n/zh/dayplan.ts b/shared/src/i18n/zh/dayplan.ts new file mode 100644 index 00000000..ebd90f37 --- /dev/null +++ b/shared/src/i18n/zh/dayplan.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const dayplan: TranslationStrings = { + 'dayplan.icsTooltip': '导出日历 (ICS)', + 'dayplan.emptyDay': '当天暂无计划', + 'dayplan.addNote': '添加备注', + 'dayplan.editNote': '编辑备注', + 'dayplan.noteAdd': '添加备注', + 'dayplan.noteEdit': '编辑备注', + 'dayplan.noteTitle': '备注', + 'dayplan.noteSubtitle': '每日备注', + 'dayplan.totalCost': '总费用', + 'dayplan.days': '天', + 'dayplan.dayN': '第 {n} 天', + 'dayplan.calculating': '计算中...', + 'dayplan.route': '路线', + 'dayplan.optimize': '优化', + 'dayplan.optimized': '路线已优化', + 'dayplan.routeError': '路线计算失败', + 'dayplan.toast.needTwoPlaces': '路线优化至少需要两个地点', + 'dayplan.toast.routeOptimized': '路线已优化', + 'dayplan.toast.noGeoPlaces': '未找到有坐标的地点用于路线计算', + 'dayplan.confirmed': '已确认', + 'dayplan.pendingRes': '待确认', + 'dayplan.pdf': 'PDF', + 'dayplan.pdfTooltip': '导出当天计划为 PDF', + 'dayplan.pdfError': 'PDF 导出失败', + 'dayplan.cannotReorderTransport': '有固定时间的预订无法重新排序', + 'dayplan.confirmRemoveTimeTitle': '移除时间?', + 'dayplan.confirmRemoveTimeBody': + '此地点有固定时间({time})。移动后将移除时间并允许自由排序。', + 'dayplan.confirmRemoveTimeAction': '移除时间并移动', + 'dayplan.cannotDropOnTimed': '无法将项目放置在有固定时间的条目之间', + 'dayplan.cannotBreakChronology': '这将打乱已计划项目和预订的时间顺序', + 'dayplan.mobile.addPlace': '添加地点', + 'dayplan.mobile.searchPlaces': '搜索地点...', + 'dayplan.mobile.allAssigned': '所有地点已分配', + 'dayplan.mobile.noMatch': '无匹配', + 'dayplan.mobile.createNew': '创建新地点', +}; +export default dayplan; diff --git a/shared/src/i18n/zh/externalNotifications.ts b/shared/src/i18n/zh/externalNotifications.ts new file mode 100644 index 00000000..9759e5b5 --- /dev/null +++ b/shared/src/i18n/zh/externalNotifications.ts @@ -0,0 +1,62 @@ +import type { NotificationLocale } from '../externalNotifications/types'; + +const zh: NotificationLocale = { + email: { + footer: '您收到此邮件是因为您在 TREK 中启用了通知。', + manage: '管理偏好设置', + madeWith: 'Made with', + openTrek: '打开 TREK', + }, + events: { + trip_invite: (p) => ({ + title: `邀请加入"${p.trip}"`, + body: `${p.actor} 邀请了 ${p.invitee || '成员'} 加入旅行"${p.trip}"。`, + }), + booking_change: (p) => ({ + title: `新预订:${p.booking}`, + body: `${p.actor} 在"${p.trip}"中添加了预订"${p.booking}"(${p.type})。`, + }), + trip_reminder: (p) => ({ + title: `旅行提醒:${p.trip}`, + body: `你的旅行"${p.trip}"即将开始!`, + }), + todo_due: (p) => ({ + title: `待办事项即将到期:${p.todo}`, + body: `"${p.trip}" 中的"${p.todo}"将于 ${p.due} 到期。`, + }), + vacay_invite: (p) => ({ + title: 'Vacay 融合邀请', + body: `${p.actor} 邀请你合并假期计划。打开 TREK 接受或拒绝。`, + }), + photos_shared: (p) => ({ + title: `${p.count} 张照片已分享`, + body: `${p.actor} 在"${p.trip}"中分享了 ${p.count} 张照片。`, + }), + collab_message: (p) => ({ + title: `"${p.trip}"中的新消息`, + body: `${p.actor}:${p.preview}`, + }), + packing_tagged: (p) => ({ + title: `行李清单:${p.category}`, + body: `${p.actor} 将你分配到"${p.trip}"中的"${p.category}"类别。`, + }), + version_available: (p) => ({ + title: '新版 TREK 可用', + body: `TREK ${p.version} 现已可用。请前往管理面板进行更新。`, + }), + synology_session_cleared: () => ({ + title: 'Synology 会话已清除', + body: '您的 Synology 账户或 URL 已更改,您已退出 Synology Photos。', + }), + }, + passwordReset: { + subject: '重置您的密码', + greeting: '您好', + body: '我们收到了重置您的 TREK 账户密码的请求。点击下方按钮设置新密码。', + ctaIntro: '重置密码', + expiry: '此链接将在 60 分钟后失效。', + ignore: '如果这不是您本人的请求,可以忽略本邮件 — 您的密码不会改变。', + }, +}; + +export default zh; diff --git a/shared/src/i18n/zh/files.ts b/shared/src/i18n/zh/files.ts new file mode 100644 index 00000000..da379d10 --- /dev/null +++ b/shared/src/i18n/zh/files.ts @@ -0,0 +1,60 @@ +import type { TranslationStrings } from '../types'; + +const files: TranslationStrings = { + 'files.title': '文件', + 'files.pageTitle': '文件与文档', + 'files.subtitle': '{trip} 的 {count} 个文件', + 'files.download': '下载', + 'files.openError': '无法打开文件', + 'files.downloadPdf': '下载 PDF', + 'files.count': '{count} 个文件', + 'files.countSingular': '1 个文件', + 'files.uploaded': '已上传 {count} 个', + 'files.uploadError': '上传失败', + 'files.dropzone': '将文件拖放到此处', + 'files.dropzoneHint': '或点击浏览', + 'files.allowedTypes': + '图片、PDF、DOC、DOCX、XLS、XLSX、TXT、CSV · 最大 50 MB', + 'files.uploading': '上传中...', + 'files.filterAll': '全部', + 'files.filterPdf': 'PDF', + 'files.filterImages': '图片', + 'files.filterDocs': '文档', + 'files.filterCollab': '协作笔记', + 'files.sourceCollab': '来自协作笔记', + 'files.empty': '暂无文件', + 'files.emptyHint': '上传文件以附加到旅行中', + 'files.openTab': '在新标签页中打开', + 'files.confirm.delete': '确定要删除此文件吗?', + 'files.toast.deleted': '文件已删除', + 'files.toast.deleteError': '删除文件失败', + 'files.sourcePlan': '日程计划', + 'files.sourceBooking': '预订', + 'files.sourceTransport': '交通', + 'files.attach': '附加', + 'files.pasteHint': '也可以从剪贴板粘贴图片 (Ctrl+V)', + 'files.trash': '回收站', + 'files.trashEmpty': '回收站为空', + 'files.emptyTrash': '清空回收站', + 'files.restore': '恢复', + 'files.star': '收藏', + 'files.unstar': '取消收藏', + 'files.assign': '分配', + 'files.assignTitle': '分配文件', + 'files.assignPlace': '地点', + 'files.assignBooking': '预订', + 'files.assignTransport': '交通', + 'files.unassigned': '未分配', + 'files.unlink': '移除关联', + 'files.toast.trashed': '已移至回收站', + 'files.toast.restored': '文件已恢复', + 'files.toast.trashEmptied': '回收站已清空', + 'files.toast.assigned': '文件已分配', + 'files.toast.assignError': '分配失败', + 'files.toast.restoreError': '恢复失败', + 'files.confirm.permanentDelete': '永久删除此文件?此操作无法撤销。', + 'files.confirm.emptyTrash': '永久删除回收站中的所有文件?此操作无法撤销。', + 'files.noteLabel': '备注', + 'files.notePlaceholder': '添加备注...', +}; +export default files; diff --git a/shared/src/i18n/zh/index.ts b/shared/src/i18n/zh/index.ts new file mode 100644 index 00000000..77aeb6a0 --- /dev/null +++ b/shared/src/i18n/zh/index.ts @@ -0,0 +1,86 @@ +import admin from './admin'; +import airport from './airport'; +import atlas from './atlas'; +import backup from './backup'; +import budget from './budget'; +import categories from './categories'; +import collab from './collab'; +import common from './common'; +import dashboard from './dashboard'; +import day from './day'; +import dayplan from './dayplan'; +import files from './files'; +import inspector from './inspector'; +import journey from './journey'; +import login from './login'; +import map from './map'; +import members from './members'; +import memories from './memories'; +import nav from './nav'; +import notif from './notif'; +import notifications from './notifications'; +import oauth from './oauth'; +import packing from './packing'; +import pdf from './pdf'; +import perm from './perm'; +import photos from './photos'; +import places from './places'; +import planner from './planner'; +import register from './register'; +import reservations from './reservations'; +import settings from './settings'; +import share from './share'; +import shared from './shared'; +import stats from './stats'; +import system_notice from './system_notice'; +import todo from './todo'; +import transport from './transport'; +import trip from './trip'; +import trips from './trips'; +import undo from './undo'; +import vacay from './vacay'; + +const locale = { + ...common, + ...trips, + ...nav, + ...dashboard, + ...settings, + ...admin, + ...dayplan, + ...share, + ...shared, + ...login, + ...register, + ...vacay, + ...atlas, + ...trip, + ...places, + ...inspector, + ...reservations, + ...airport, + ...map, + ...budget, + ...files, + ...packing, + ...members, + ...categories, + ...backup, + ...photos, + ...pdf, + ...planner, + ...stats, + ...day, + ...memories, + ...collab, + ...perm, + ...undo, + ...notifications, + ...todo, + ...notif, + ...journey, + ...oauth, + ...system_notice, + ...transport, +}; +export default locale; diff --git a/shared/src/i18n/zh/inspector.ts b/shared/src/i18n/zh/inspector.ts new file mode 100644 index 00000000..b2c0d908 --- /dev/null +++ b/shared/src/i18n/zh/inspector.ts @@ -0,0 +1,22 @@ +import type { TranslationStrings } from '../types'; + +const inspector: TranslationStrings = { + 'inspector.opened': '营业中', + 'inspector.closed': '已关闭', + 'inspector.openingHours': '营业时间', + 'inspector.showHours': '显示营业时间', + 'inspector.files': '文件', + 'inspector.filesCount': '{count} 个文件', + 'inspector.removeFromDay': '从当天移除', + 'inspector.remove': '删除', + 'inspector.addToDay': '添加到当天', + 'inspector.confirmedRes': '已确认预订', + 'inspector.pendingRes': '待确认预订', + 'inspector.google': '在 Google Maps 中打开', + 'inspector.website': '打开网站', + 'inspector.addRes': '预订', + 'inspector.editRes': '编辑预订', + 'inspector.participants': '参与者', + 'inspector.trackStats': '轨迹数据', +}; +export default inspector; diff --git a/shared/src/i18n/zh/journey.ts b/shared/src/i18n/zh/journey.ts new file mode 100644 index 00000000..b73defde --- /dev/null +++ b/shared/src/i18n/zh/journey.ts @@ -0,0 +1,229 @@ +import type { TranslationStrings } from '../types'; + +const journey: TranslationStrings = { + 'journey.search.placeholder': '搜索旅程…', + 'journey.search.noResults': '没有与"{query}"匹配的旅程', + 'journey.title': '旅程', + 'journey.subtitle': '实时记录你的旅行', + 'journey.new': '新建旅程', + 'journey.create': '创建', + 'journey.titlePlaceholder': '你要去哪里?', + 'journey.empty': '还没有旅程', + 'journey.emptyHint': '开始记录你的下一次旅行', + 'journey.deleted': '旅程已删除', + 'journey.createError': '无法创建旅程', + 'journey.deleteError': '无法删除旅程', + 'journey.deleteConfirmTitle': '删除', + 'journey.deleteConfirmMessage': '删除"{title}"?此操作无法撤销。', + 'journey.deleteConfirmGeneric': '确定要删除吗?', + 'journey.notFound': '未找到旅程', + 'journey.photos': '照片', + 'journey.timelineEmpty': '还没有行程', + 'journey.timelineEmptyHint': '添加一个签到或写一篇日志开始记录', + 'journey.status.draft': '草稿', + 'journey.status.active': '进行中', + 'journey.status.completed': '已完成', + 'journey.status.upcoming': '即将开始', + 'journey.status.archived': '已归档', + 'journey.checkin.add': '签到', + 'journey.checkin.namePlaceholder': '地点名称', + 'journey.checkin.notesPlaceholder': '备注(可选)', + 'journey.checkin.save': '保存', + 'journey.checkin.error': '无法保存签到', + 'journey.entry.add': '日志', + 'journey.entry.edit': '编辑条目', + 'journey.entry.titlePlaceholder': '标题(可选)', + 'journey.entry.bodyPlaceholder': '今天发生了什么?', + 'journey.entry.save': '保存', + 'journey.entry.error': '无法保存条目', + 'journey.photo.add': '照片', + 'journey.photo.uploadError': '上传失败', + 'journey.share.share': '分享', + 'journey.share.public': '公开', + 'journey.share.linkCopied': '公开链接已复制', + 'journey.share.disabled': '已关闭公开分享', + 'journey.editor.titlePlaceholder': '给这个瞬间起个名字...', + 'journey.editor.bodyPlaceholder': '讲述这一天的故事...', + 'journey.editor.placePlaceholder': '地点(可选)', + 'journey.editor.tagsPlaceholder': '标签:隐藏宝藏、最佳美食、值得再去...', + 'journey.visibility.private': '私密', + 'journey.visibility.shared': '共享', + 'journey.visibility.public': '公开', + 'journey.emptyState.title': '你的故事从这里开始', + 'journey.emptyState.subtitle': '在某个地方签到或写下你的第一篇日志', + 'journey.frontpage.subtitle': '将旅行变成永远不会忘记的故事', + 'journey.frontpage.createJourney': '创建旅程', + 'journey.frontpage.activeJourney': '进行中的旅程', + 'journey.frontpage.allJourneys': '所有旅程', + 'journey.frontpage.journeys': '个旅程', + 'journey.frontpage.createNew': '创建新旅程', + 'journey.frontpage.createNewSub': '选择旅行、写故事、分享你的冒险', + 'journey.frontpage.live': '实时', + 'journey.frontpage.synced': '已同步', + 'journey.frontpage.continueWriting': '继续写作', + 'journey.frontpage.updated': '更新于 {time}', + 'journey.frontpage.suggestionLabel': '旅行刚结束', + 'journey.frontpage.suggestionText': + '将 {title} 变成一段旅程', + 'journey.frontpage.dismiss': '忽略', + 'journey.frontpage.journeyName': '旅程名称', + 'journey.frontpage.namePlaceholder': '例如 东南亚 2026', + 'journey.frontpage.selectTrips': '选择旅行', + 'journey.frontpage.tripsSelected': '个旅行已选择', + 'journey.frontpage.trips': '个旅行', + 'journey.frontpage.placesImported': '个地点将被导入', + 'journey.frontpage.places': '个地点', + 'journey.detail.backToJourney': '返回旅程', + 'journey.detail.syncedWithTrips': '已与旅行同步', + 'journey.detail.addEntry': '添加条目', + 'journey.detail.newEntry': '新建条目', + 'journey.detail.editEntry': '编辑条目', + 'journey.detail.noEntries': '还没有条目', + 'journey.detail.noEntriesHint': '添加一个旅行以生成骨架条目', + 'journey.detail.noPhotos': '还没有照片', + 'journey.detail.noPhotosHint': + '上传照片到条目或浏览你的 Immich/Synology 相册', + 'journey.detail.journeyStats': '旅程统计', + 'journey.detail.syncedTrips': '已同步的旅行', + 'journey.detail.noTripsLinked': '尚未关联旅行', + 'journey.detail.contributors': '贡献者', + 'journey.detail.readMore': '阅读更多', + 'journey.detail.prosCons': '优缺点', + 'journey.detail.photos': '照片', + 'journey.detail.day': '第{number}天', + 'journey.detail.places': '个地点', + 'journey.stats.days': '天', + 'journey.stats.cities': '城市', + 'journey.stats.entries': '条目', + 'journey.stats.photos': '照片', + 'journey.stats.places': '地点', + 'journey.skeletons.show': '显示建议', + 'journey.skeletons.hide': '隐藏建议', + 'journey.verdict.lovedIt': '非常喜欢', + 'journey.verdict.couldBeBetter': '有待改进', + 'journey.synced.places': '个地点', + 'journey.synced.synced': '已同步', + 'journey.editor.discardChangesConfirm': '您有未保存的更改。要放弃吗?', + 'journey.editor.uploadFailed': '照片上传失败', + 'journey.editor.uploadPhotos': '上传照片', + 'journey.editor.uploading': '上传中...', + 'journey.editor.uploadingProgress': '上传中 {done}/{total}…', + 'journey.editor.uploadPartialFailed': + '{total} 张中有 {failed} 张上传失败 — 再次保存以重试', + 'journey.editor.fromGallery': '从相册', + 'journey.editor.allPhotosAdded': '所有照片已添加', + 'journey.editor.writeStory': '写下你的故事...', + 'journey.editor.prosCons': '优缺点', + 'journey.editor.pros': '优点', + 'journey.editor.cons': '缺点', + 'journey.editor.proPlaceholder': '好的方面...', + 'journey.editor.conPlaceholder': '不好的方面...', + 'journey.editor.addAnother': '再添加一个', + 'journey.editor.date': '日期', + 'journey.editor.location': '地点', + 'journey.editor.searchLocation': '搜索地点...', + 'journey.editor.mood': '心情', + 'journey.editor.weather': '天气', + 'journey.editor.photoFirst': '第1张', + 'journey.editor.makeFirst': '设为第1张', + 'journey.editor.searching': '搜索中...', + 'journey.mood.amazing': '太棒了', + 'journey.mood.good': '不错', + 'journey.mood.neutral': '一般', + 'journey.mood.rough': '糟糕', + 'journey.weather.sunny': '晴天', + 'journey.weather.partly': '多云', + 'journey.weather.cloudy': '阴天', + 'journey.weather.rainy': '雨天', + 'journey.weather.stormy': '暴风雨', + 'journey.weather.cold': '雪天', + 'journey.trips.linkTrip': '关联旅行', + 'journey.trips.searchTrip': '搜索旅行', + 'journey.trips.searchPlaceholder': '旅行名称或目的地...', + 'journey.trips.noTripsAvailable': '没有可用的旅行', + 'journey.trips.link': '关联', + 'journey.trips.tripLinked': '旅行已关联', + 'journey.trips.linkFailed': '关联旅行失败', + 'journey.trips.addTrip': '添加旅行', + 'journey.trips.unlinkTrip': '取消关联旅行', + 'journey.trips.unlinkMessage': + '取消关联"{title}"?此旅行中所有已同步的条目和照片将被永久删除。此操作无法撤销。', + 'journey.trips.unlink': '取消关联', + 'journey.trips.tripUnlinked': '旅行已取消关联', + 'journey.trips.unlinkFailed': '取消关联失败', + 'journey.trips.noTripsLinkedSettings': '未关联旅行', + 'journey.contributors.invite': '邀请贡献者', + 'journey.contributors.searchUser': '搜索用户', + 'journey.contributors.searchPlaceholder': '用户名或邮箱...', + 'journey.contributors.noUsers': '未找到用户', + 'journey.contributors.role': '角色', + 'journey.contributors.added': '贡献者已添加', + 'journey.contributors.addFailed': '添加贡献者失败', + 'journey.share.publicShare': '公开分享', + 'journey.share.createLink': '创建分享链接', + 'journey.share.linkCreated': '分享链接已创建', + 'journey.share.createFailed': '创建链接失败', + 'journey.share.copy': '复制', + 'journey.share.copied': '已复制!', + 'journey.share.timeline': '时间线', + 'journey.share.gallery': '图库', + 'journey.share.map': '地图', + 'journey.share.removeLink': '移除分享链接', + 'journey.share.linkDeleted': '分享链接已删除', + 'journey.share.deleteFailed': '删除失败', + 'journey.share.updateFailed': '更新失败', + 'journey.invite.role': '角色', + 'journey.invite.viewer': '查看者', + 'journey.invite.editor': '编辑者', + 'journey.invite.invite': '邀请', + 'journey.invite.inviting': '邀请中...', + 'journey.settings.title': '旅程设置', + 'journey.settings.coverImage': '封面图片', + 'journey.settings.changeCover': '更换封面', + 'journey.settings.addCover': '添加封面图片', + 'journey.settings.name': '名称', + 'journey.settings.subtitle': '副标题', + 'journey.settings.subtitlePlaceholder': '例如 泰国、越南和柬埔寨', + 'journey.settings.endJourney': '归档旅程', + 'journey.settings.reopenJourney': '恢复旅程', + 'journey.settings.archived': '旅程已归档', + 'journey.settings.reopened': '旅程已重新开启', + 'journey.settings.endDescription': '隐藏直播标记。您可以随时重新开启。', + 'journey.settings.delete': '删除', + 'journey.settings.deleteJourney': '删除旅程', + 'journey.settings.deleteMessage': '删除"{title}"?所有条目和照片将丢失。', + 'journey.settings.saved': '设置已保存', + 'journey.settings.saveFailed': '保存失败', + 'journey.settings.coverUpdated': '封面已更新', + 'journey.settings.coverFailed': '上传失败', + 'journey.settings.failedToDelete': '删除失败', + 'journey.entries.deleteTitle': '删除条目', + 'journey.photosUploaded': '{count} 张照片已上传', + 'journey.photosUploadFailed': '部分照片上传失败', + 'journey.photosAdded': '{count} 张照片已添加', + 'journey.public.notFound': '未找到', + 'journey.public.notFoundMessage': '此旅程不存在或链接已过期。', + 'journey.public.readOnly': '只读 · 公开旅程', + 'journey.public.tagline': '旅行资源与探索工具包', + 'journey.public.sharedVia': '分享自', + 'journey.public.madeWith': '由', + 'journey.pdf.journeyBook': '旅程手册', + 'journey.pdf.madeWith': '由 TREK 制作', + 'journey.pdf.day': '第', + 'journey.pdf.theEnd': '终', + 'journey.pdf.saveAsPdf': '保存为 PDF', + 'journey.pdf.pages': '页', + 'journey.picker.tripPeriod': '旅行时间段', + 'journey.picker.dateRange': '日期范围', + 'journey.picker.allPhotos': '所有照片', + 'journey.picker.albums': '相册', + 'journey.picker.selected': '已选择', + 'journey.picker.addTo': '添加到', + 'journey.picker.newGallery': '新相册', + 'journey.picker.selectAll': '全选', + 'journey.picker.deselectAll': '取消全选', + 'journey.picker.noAlbums': '未找到相册', + 'journey.picker.selectDate': '选择日期', + 'journey.picker.search': '搜索', +}; +export default journey; diff --git a/shared/src/i18n/zh/login.ts b/shared/src/i18n/zh/login.ts new file mode 100644 index 00000000..e22ff67b --- /dev/null +++ b/shared/src/i18n/zh/login.ts @@ -0,0 +1,87 @@ +import type { TranslationStrings } from '../types'; + +const login: TranslationStrings = { + 'login.error': '登录失败,请检查你的凭据。', + 'login.tagline': '你的旅行。\n你的计划。', + 'login.description': '通过互动地图、预算管理和实时同步,协同规划旅行。', + 'login.features.maps': '互动地图', + 'login.features.mapsDesc': 'Google Places、路线和聚类', + 'login.features.realtime': '实时同步', + 'login.features.realtimeDesc': '通过 WebSocket 协同规划', + 'login.features.budget': '预算跟踪', + 'login.features.budgetDesc': '分类、图表和人均费用', + 'login.features.collab': '协作', + 'login.features.collabDesc': '多用户共享旅行', + 'login.features.packing': '行李清单', + 'login.features.packingDesc': '分类、进度和建议', + 'login.features.bookings': '预订', + 'login.features.bookingsDesc': '航班、酒店、餐厅等', + 'login.features.files': '文档', + 'login.features.filesDesc': '上传和管理文档', + 'login.features.routes': '智能路线', + 'login.features.routesDesc': '自动优化和导出到 Google Maps', + 'login.selfHosted': '自托管 · 开源 · 数据由你掌控', + 'login.title': '登录', + 'login.subtitle': '欢迎回来', + 'login.signingIn': '登录中…', + 'login.signIn': '登录', + 'login.createAdmin': '创建管理员账户', + 'login.createAdminHint': '为 TREK 设置第一个管理员账户。', + 'login.setNewPassword': '设置新密码', + 'login.setNewPasswordHint': '您必须更改密码才能继续。', + 'login.createAccount': '创建账户', + 'login.createAccountHint': '注册新账户。', + 'login.creating': '创建中…', + 'login.noAccount': '还没有账户?', + 'login.hasAccount': '已有账户?', + 'login.register': '注册', + 'login.emailPlaceholder': 'your@email.com', + 'login.username': '用户名', + 'login.oidc.registrationDisabled': '注册已关闭。请联系管理员。', + 'login.oidc.noEmail': '未从提供商获取到邮箱。', + 'login.mfaTitle': '双因素认证', + 'login.mfaSubtitle': '请输入身份验证器应用中的 6 位验证码。', + 'login.mfaCodeLabel': '验证码', + 'login.mfaCodeRequired': '请输入身份验证器应用中的验证码。', + 'login.mfaHint': '打开 Google Authenticator、Authy 或其他 TOTP 应用。', + 'login.mfaBack': '← 返回登录', + 'login.mfaVerify': '验证', + 'login.invalidInviteLink': '邀请链接无效或已过期', + 'login.oidcFailed': 'OIDC 登录失败', + 'login.usernameRequired': '用户名为必填项', + 'login.passwordMinLength': '密码至少需要8个字符', + 'login.forgotPassword': '忘记密码?', + 'login.forgotPasswordTitle': '重置密码', + 'login.forgotPasswordBody': + '输入您注册时使用的邮箱地址。若账户存在,我们将发送重置链接。', + 'login.forgotPasswordSubmit': '发送重置链接', + 'login.forgotPasswordSentTitle': '请查看邮箱', + 'login.forgotPasswordSentBody': + '若该邮箱存在账户,重置链接正在发送中。链接将在 60 分钟后失效。', + 'login.forgotPasswordSmtpHintOff': + '提示:管理员未配置 SMTP,重置链接将被写入服务器控制台,而不是通过电子邮件发送。', + 'login.backToLogin': '返回登录', + 'login.newPassword': '新密码', + 'login.confirmPassword': '确认新密码', + 'login.passwordsDontMatch': '两次输入的密码不一致', + 'login.mfaCode': '二步验证码', + 'login.resetPasswordTitle': '设置新密码', + 'login.resetPasswordBody': '请选择您在此处未使用过的强密码。至少 8 位。', + 'login.resetPasswordMfaBody': '输入您的二步验证码或备用代码以完成重置。', + 'login.resetPasswordSubmit': '重置密码', + 'login.resetPasswordVerify': '验证并重置', + 'login.resetPasswordSuccessTitle': '密码已更新', + 'login.resetPasswordSuccessBody': '您现在可以使用新密码登录了。', + 'login.resetPasswordInvalidLink': '无效的重置链接', + 'login.resetPasswordInvalidLinkBody': + '此链接已丢失或损坏。请重新申请以继续。', + 'login.resetPasswordFailed': '重置失败。链接可能已过期。', + 'login.oidc.tokenFailed': '认证失败。', + 'login.oidc.invalidState': '会话无效,请重试。', + 'login.demoFailed': '演示登录失败', + 'login.oidcSignIn': '通过 {name} 登录', + 'login.oidcOnly': '密码登录已关闭。请通过 SSO 提供商登录。', + 'login.oidcLoggedOut': '您已退出登录。请重新通过 SSO 提供商登录。', + 'login.demoHint': '试用演示——无需注册', +}; +export default login; diff --git a/shared/src/i18n/zh/map.ts b/shared/src/i18n/zh/map.ts new file mode 100644 index 00000000..8589a216 --- /dev/null +++ b/shared/src/i18n/zh/map.ts @@ -0,0 +1,8 @@ +import type { TranslationStrings } from '../types'; + +const map: TranslationStrings = { + 'map.connections': '连接', + 'map.showConnections': '显示预订路线', + 'map.hideConnections': '隐藏预订路线', +}; +export default map; diff --git a/shared/src/i18n/zh/members.ts b/shared/src/i18n/zh/members.ts new file mode 100644 index 00000000..d2a62003 --- /dev/null +++ b/shared/src/i18n/zh/members.ts @@ -0,0 +1,24 @@ +import type { TranslationStrings } from '../types'; + +const members: TranslationStrings = { + 'members.shareTrip': '分享旅行', + 'members.inviteUser': '邀请用户', + 'members.selectUser': '选择用户…', + 'members.invite': '邀请', + 'members.allHaveAccess': '所有用户均已拥有访问权限。', + 'members.access': '访问权限', + 'members.person': '人', + 'members.persons': '人', + 'members.you': '你', + 'members.owner': '所有者', + 'members.leaveTrip': '退出旅行', + 'members.removeAccess': '移除访问权限', + 'members.confirmLeave': '退出旅行?你将失去访问权限。', + 'members.confirmRemove': '移除该用户的访问权限?', + 'members.loadError': '加载成员失败', + 'members.added': '已添加', + 'members.addError': '添加失败', + 'members.removed': '成员已移除', + 'members.removeError': '移除失败', +}; +export default members; diff --git a/shared/src/i18n/zh/memories.ts b/shared/src/i18n/zh/memories.ts new file mode 100644 index 00000000..10638946 --- /dev/null +++ b/shared/src/i18n/zh/memories.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const memories: TranslationStrings = { + 'memories.title': '照片', + 'memories.notConnected': 'Immich 未连接', + 'memories.notConnectedHint': + '在设置中连接您的 Immich 实例以在此查看旅行照片。', + 'memories.notConnectedMultipleHint': + '请在设置中连接以下任一照片提供商:{provider_names},以便向此行程添加照片。', + 'memories.noDates': '为旅行添加日期以加载照片。', + 'memories.noPhotos': '未找到照片', + 'memories.noPhotosHint': 'Immich 中未找到此旅行日期范围内的照片。', + 'memories.photosFound': '张照片', + 'memories.fromOthers': '来自他人', + 'memories.sharePhotos': '分享照片', + 'memories.sharing': '分享中', + 'memories.reviewTitle': '审查您的照片', + 'memories.reviewHint': '点击照片以将其从分享中排除。', + 'memories.shareCount': '分享 {count} 张照片', + 'memories.providerUrl': '服务器 URL', + 'memories.providerApiKey': 'API 密钥', + 'memories.providerUsername': '用户名', + 'memories.providerPassword': '密码', + 'memories.providerOTP': 'MFA 验证码(如已启用)', + 'memories.skipSSLVerification': '跳过 SSL 证书验证', + 'memories.immichAutoUpload': '上传 Journey 照片时同步到 Immich', + 'memories.providerUrlHintSynology': + '在 URL 中包含照片应用路径,例如 https://nas:5001/photo', + 'memories.testConnection': '测试连接', + 'memories.testShort': '测试', + 'memories.testFirst': '请先测试连接', + 'memories.connected': '已连接', + 'memories.disconnected': '未连接', + 'memories.connectionSuccess': '已连接到 Immich', + 'memories.connectionError': '无法连接到 Immich', + 'memories.saved': '{provider_name} 设置已保存', + 'memories.providerDisconnectedBanner': + '您与 {provider_name} 的连接已断开。请在设置中重新连接以查看照片。', + 'memories.saveError': '无法保存 {provider_name} 设置', + 'memories.oldest': '最早优先', + 'memories.newest': '最新优先', + 'memories.allLocations': '所有地点', + 'memories.addPhotos': '添加照片', + 'memories.linkAlbum': '关联相册', + 'memories.selectAlbum': '选择 Immich 相册', + 'memories.selectAlbumMultiple': '选择相册', + 'memories.noAlbums': '未找到相册', + 'memories.syncAlbum': '同步相册', + 'memories.unlinkAlbum': '取消关联', + 'memories.photos': '张照片', + 'memories.selectPhotos': '从 Immich 选择照片', + 'memories.selectPhotosMultiple': '选择照片', + 'memories.selectHint': '点击照片以选择。', + 'memories.selected': '已选择', + 'memories.addSelected': '添加 {count} 张照片', + 'memories.alreadyAdded': '已添加', + 'memories.private': '私密', + 'memories.stopSharing': '停止分享', + 'memories.tripDates': '旅行日期', + 'memories.allPhotos': '所有照片', + 'memories.confirmShareTitle': '与旅行成员分享?', + 'memories.confirmShareHint': + '{count} 张照片将对本次旅行的所有成员可见。你可以稍后将单张照片设为私密。', + 'memories.confirmShareButton': '分享照片', + 'memories.error.loadAlbums': '加载相册失败', + 'memories.error.linkAlbum': '关联相册失败', + 'memories.error.unlinkAlbum': '取消关联相册失败', + 'memories.error.syncAlbum': '同步相册失败', + 'memories.error.loadPhotos': '加载照片失败', + 'memories.error.addPhotos': '添加照片失败', + 'memories.error.removePhoto': '删除照片失败', + 'memories.error.toggleSharing': '更新共享设置失败', + 'memories.saveRouteNotConfigured': '此提供商未配置保存路由', + 'memories.testRouteNotConfigured': '此提供商未配置测试路由', + 'memories.fillRequiredFields': '请填写所有必填字段', +}; +export default memories; diff --git a/shared/src/i18n/zh/nav.ts b/shared/src/i18n/zh/nav.ts new file mode 100644 index 00000000..d632bde6 --- /dev/null +++ b/shared/src/i18n/zh/nav.ts @@ -0,0 +1,20 @@ +import type { TranslationStrings } from '../types'; + +const nav: TranslationStrings = { + 'nav.trip': '旅行', + 'nav.share': '分享', + 'nav.settings': '设置', + 'nav.admin': '管理', + 'nav.logout': '退出登录', + 'nav.lightMode': '浅色模式', + 'nav.darkMode': '深色模式', + 'nav.autoMode': '自动模式', + 'nav.administrator': '管理员', + 'nav.myTrips': '我的旅行', + 'nav.profile': '个人资料', + 'nav.bottomSettings': '设置', + 'nav.bottomAdmin': '管理设置', + 'nav.bottomLogout': '退出登录', + 'nav.bottomAdminBadge': '管理员', +}; +export default nav; diff --git a/shared/src/i18n/zh/notif.ts b/shared/src/i18n/zh/notif.ts new file mode 100644 index 00000000..8c123376 --- /dev/null +++ b/shared/src/i18n/zh/notif.ts @@ -0,0 +1,41 @@ +import type { TranslationStrings } from '../types'; + +const notif: TranslationStrings = { + 'notif.test.title': '[测试] 通知', + 'notif.test.simple.text': '这是一条简单的测试通知。', + 'notif.test.boolean.text': '您是否接受此测试通知?', + 'notif.test.navigate.text': '点击下方前往控制台。', + 'notif.trip_invite.title': '旅行邀请', + 'notif.trip_invite.text': '{actor} 邀请您加入 {trip}', + 'notif.booking_change.title': '预订已更新', + 'notif.booking_change.text': '{actor} 更新了 {trip} 中的预订', + 'notif.trip_reminder.title': '旅行提醒', + 'notif.trip_reminder.text': '您的旅行 {trip} 即将开始!', + 'notif.todo_due.title': '待办事项即将到期', + 'notif.todo_due.text': '{trip} 中的 {todo} 将于 {due} 到期', + 'notif.vacay_invite.title': 'Vacay 融合邀请', + 'notif.vacay_invite.text': '{actor} 邀请您合并假期计划', + 'notif.photos_shared.title': '照片已分享', + 'notif.photos_shared.text': '{actor} 在 {trip} 中分享了 {count} 张照片', + 'notif.collab_message.title': '新消息', + 'notif.collab_message.text': '{actor} 在 {trip} 中发送了消息', + 'notif.packing_tagged.title': '行李分配', + 'notif.packing_tagged.text': '{actor} 将您分配到 {trip} 中的 {category}', + 'notif.version_available.title': '新版本可用', + 'notif.version_available.text': 'TREK {version} 现已可用', + 'notif.action.view_trip': '查看旅行', + 'notif.action.view_collab': '查看消息', + 'notif.action.view_packing': '查看行李', + 'notif.action.view_photos': '查看照片', + 'notif.action.view_vacay': '查看 Vacay', + 'notif.action.view_admin': '前往管理', + 'notif.action.view': '查看', + 'notif.action.accept': '接受', + 'notif.action.decline': '拒绝', + 'notif.generic.title': '通知', + 'notif.generic.text': '您有一条新通知', + 'notif.dev.unknown_event.title': '[DEV] 未知事件', + 'notif.dev.unknown_event.text': + '事件类型 "{event}" 未在 EVENT_NOTIFICATION_CONFIG 中注册', +}; +export default notif; diff --git a/shared/src/i18n/zh/notifications.ts b/shared/src/i18n/zh/notifications.ts new file mode 100644 index 00000000..daf0f462 --- /dev/null +++ b/shared/src/i18n/zh/notifications.ts @@ -0,0 +1,36 @@ +import type { TranslationStrings } from '../types'; + +const notifications: TranslationStrings = { + 'notifications.title': '通知', + 'notifications.markAllRead': '全部标为已读', + 'notifications.deleteAll': '全部删除', + 'notifications.showAll': '查看所有通知', + 'notifications.empty': '暂无通知', + 'notifications.emptyDescription': '您已全部查阅!', + 'notifications.all': '全部', + 'notifications.unreadOnly': '未读', + 'notifications.markRead': '标为已读', + 'notifications.markUnread': '标为未读', + 'notifications.delete': '删除', + 'notifications.system': '系统', + 'notifications.synologySessionCleared.title': 'Synology Photos 已断开连接', + 'notifications.synologySessionCleared.text': + '您的服务器或账户已更改 — 请前往设置重新测试您的连接。', + 'notifications.test.title': '来自 {actor} 的测试通知', + 'notifications.test.text': '这是一条简单的测试通知。', + 'notifications.test.booleanTitle': '{actor} 请求您的审批', + 'notifications.test.booleanText': '测试布尔通知。', + 'notifications.test.accept': '批准', + 'notifications.test.decline': '拒绝', + 'notifications.test.navigateTitle': '查看详情', + 'notifications.test.navigateText': '测试跳转通知。', + 'notifications.test.goThere': '前往', + 'notifications.test.adminTitle': '管理员广播', + 'notifications.test.adminText': '{actor} 向所有管理员发送了测试通知。', + 'notifications.test.tripTitle': '{actor} 在您的行程中发帖', + 'notifications.test.tripText': '行程"{trip}"的测试通知。', + 'notifications.versionAvailable.title': '有可用更新', + 'notifications.versionAvailable.text': 'TREK {version} 现已可用。', + 'notifications.versionAvailable.button': '查看详情', +}; +export default notifications; diff --git a/shared/src/i18n/zh/oauth.ts b/shared/src/i18n/zh/oauth.ts new file mode 100644 index 00000000..87300b17 --- /dev/null +++ b/shared/src/i18n/zh/oauth.ts @@ -0,0 +1,77 @@ +import type { TranslationStrings } from '../types'; + +const oauth: TranslationStrings = { + 'oauth.scope.group.trips': '行程', + 'oauth.scope.group.places': '地点', + 'oauth.scope.group.atlas': 'Atlas', + 'oauth.scope.group.packing': '行李', + 'oauth.scope.group.todos': '待办事项', + 'oauth.scope.group.budget': '预算', + 'oauth.scope.group.reservations': '预订', + 'oauth.scope.group.collab': '协作', + 'oauth.scope.group.notifications': '通知', + 'oauth.scope.group.vacay': '假期', + 'oauth.scope.group.geo': 'Geo', + 'oauth.scope.group.weather': '天气', + 'oauth.scope.group.journey': '旅程', + 'oauth.scope.trips:read.label': '查看行程和行程计划', + 'oauth.scope.trips:read.description': '读取行程、天数、每日笔记和成员', + 'oauth.scope.trips:write.label': '编辑行程和行程计划', + 'oauth.scope.trips:write.description': '创建和更新行程、天数、笔记并管理成员', + 'oauth.scope.trips:delete.label': '删除行程', + 'oauth.scope.trips:delete.description': '永久删除整个行程——此操作不可撤销', + 'oauth.scope.trips:share.label': '管理分享链接', + 'oauth.scope.trips:share.description': '创建、更新和撤销行程的公开分享链接', + 'oauth.scope.places:read.label': '查看地点和地图数据', + 'oauth.scope.places:read.description': '读取地点、每日分配、标签和分类', + 'oauth.scope.places:write.label': '管理地点', + 'oauth.scope.places:write.description': '创建、更新和删除地点、分配和标签', + 'oauth.scope.atlas:read.label': '查看 Atlas', + 'oauth.scope.atlas:read.description': '读取已访问国家、地区和心愿清单', + 'oauth.scope.atlas:write.label': '管理 Atlas', + 'oauth.scope.atlas:write.description': '标记已访问国家和地区,管理心愿清单', + 'oauth.scope.packing:read.label': '查看行李清单', + 'oauth.scope.packing:read.description': '读取行李物品、包袋和分类负责人', + 'oauth.scope.packing:write.label': '管理行李清单', + 'oauth.scope.packing:write.description': + '添加、更新、删除、勾选和重新排列行李物品和包袋', + 'oauth.scope.todos:read.label': '查看待办清单', + 'oauth.scope.todos:read.description': '读取行程待办事项和分类负责人', + 'oauth.scope.todos:write.label': '管理待办清单', + 'oauth.scope.todos:write.description': + '创建、更新、勾选、删除和重新排列待办事项', + 'oauth.scope.budget:read.label': '查看预算', + 'oauth.scope.budget:read.description': '读取预算条目和费用明细', + 'oauth.scope.budget:write.label': '管理预算', + 'oauth.scope.budget:write.description': '创建、更新和删除预算条目', + 'oauth.scope.reservations:read.label': '查看预订', + 'oauth.scope.reservations:read.description': '读取预订和住宿详情', + 'oauth.scope.reservations:write.label': '管理预订', + 'oauth.scope.reservations:write.description': + '创建、更新、删除和重新排列预订', + 'oauth.scope.collab:read.label': '查看协作', + 'oauth.scope.collab:read.description': '读取协作笔记、投票和消息', + 'oauth.scope.collab:write.label': '管理协作', + 'oauth.scope.collab:write.description': + '创建、更新和删除协作笔记、投票和消息', + 'oauth.scope.notifications:read.label': '查看通知', + 'oauth.scope.notifications:read.description': '读取应用内通知和未读数量', + 'oauth.scope.notifications:write.label': '管理通知', + 'oauth.scope.notifications:write.description': '将通知标记为已读并回复', + 'oauth.scope.vacay:read.label': '查看假期计划', + 'oauth.scope.vacay:read.description': '读取假期计划数据、条目和统计', + 'oauth.scope.vacay:write.label': '管理假期计划', + 'oauth.scope.vacay:write.description': '创建和管理假期条目、节假日和团队计划', + 'oauth.scope.geo:read.label': '地图和地理编码', + 'oauth.scope.geo:read.description': + '搜索位置、解析地图 URL 和反向地理编码坐标', + 'oauth.scope.weather:read.label': '天气预报', + 'oauth.scope.weather:read.description': '获取行程地点和日期的天气预报', + 'oauth.scope.journey:read.label': '查看旅程', + 'oauth.scope.journey:read.description': '读取旅程、条目和贡献者列表', + 'oauth.scope.journey:write.label': '管理旅程', + 'oauth.scope.journey:write.description': '创建、更新和删除旅程及其条目', + 'oauth.scope.journey:share.label': '管理旅程链接', + 'oauth.scope.journey:share.description': '创建、更新和撤销旅程的公开分享链接', +}; +export default oauth; diff --git a/shared/src/i18n/zh/packing.ts b/shared/src/i18n/zh/packing.ts new file mode 100644 index 00000000..88b44160 --- /dev/null +++ b/shared/src/i18n/zh/packing.ts @@ -0,0 +1,183 @@ +import type { TranslationStrings } from '../types'; + +const packing: TranslationStrings = { + 'packing.title': '行李清单', + 'packing.empty': '行李清单为空', + 'packing.import': '导入', + 'packing.importTitle': '导入装箱清单', + 'packing.importHint': + '每行一个物品。可选用逗号、分号或制表符分隔类别和数量:名称, 类别, 数量', + 'packing.importPlaceholder': '牙刷\n防晒霜, 卫生\nT恤, 衣物, 5\n护照, 证件', + 'packing.importCsv': '加载 CSV/TXT', + 'packing.importAction': '导入 {count}', + 'packing.importSuccess': '已导入 {count} 项', + 'packing.importError': '导入失败', + 'packing.importEmpty': '没有可导入的项目', + 'packing.progress': '已打包 {packed}/{total}({percent}%)', + 'packing.clearChecked': '移除 {count} 个已勾选', + 'packing.clearCheckedShort': '移除 {count} 个', + 'packing.suggestions': '建议', + 'packing.suggestionsTitle': '添加建议', + 'packing.allSuggested': '所有建议已添加', + 'packing.allPacked': '全部打包完成!', + 'packing.addPlaceholder': '添加新物品...', + 'packing.categoryPlaceholder': '分类...', + 'packing.filterAll': '全部', + 'packing.filterOpen': '未完成', + 'packing.filterDone': '已完成', + 'packing.emptyTitle': '行李清单为空', + 'packing.emptyHint': '添加物品或使用建议', + 'packing.emptyFiltered': '没有匹配的物品', + 'packing.menuRename': '重命名', + 'packing.menuCheckAll': '全部勾选', + 'packing.menuUncheckAll': '取消全部勾选', + 'packing.menuDeleteCat': '删除分类', + 'packing.addItem': '添加物品', + 'packing.addItemPlaceholder': '物品名称...', + 'packing.addCategory': '添加分类', + 'packing.newCategoryPlaceholder': '分类名称(如:衣物)', + 'packing.applyTemplate': '应用模板', + 'packing.template': '模板', + 'packing.templateApplied': '已从模板添加 {count} 个物品', + 'packing.templateError': '应用模板失败', + 'packing.saveAsTemplate': '保存为模板', + 'packing.templateName': '模板名称', + 'packing.templateSaved': '行李清单已保存为模板', + 'packing.noMembers': '无成员', + 'packing.bags': '行李', + 'packing.noBag': '未分配', + 'packing.totalWeight': '总重量', + 'packing.bagName': '名称...', + 'packing.addBag': '添加行李', + 'packing.changeCategory': '更改分类', + 'packing.confirm.clearChecked': '确定移除 {count} 个已勾选的物品?', + 'packing.confirm.deleteCat': '确定删除分类「{name}」及其 {count} 个物品?', + 'packing.defaultCategory': '其他', + 'packing.toast.saveError': '保存失败', + 'packing.toast.deleteError': '删除失败', + 'packing.toast.renameError': '重命名失败', + 'packing.toast.addError': '添加失败', + 'packing.suggestions.items': [ + { + name: '护照', + category: '证件', + }, + { + name: '身份证', + category: '证件', + }, + { + name: '旅行保险', + category: '证件', + }, + { + name: '机票', + category: '证件', + }, + { + name: '信用卡', + category: '财务', + }, + { + name: '现金', + category: '财务', + }, + { + name: '签证', + category: '证件', + }, + { + name: 'T恤', + category: '衣物', + }, + { + name: '裤子', + category: '衣物', + }, + { + name: '内衣', + category: '衣物', + }, + { + name: '袜子', + category: '衣物', + }, + { + name: '外套', + category: '衣物', + }, + { + name: '睡衣', + category: '衣物', + }, + { + name: '泳衣', + category: '衣物', + }, + { + name: '雨衣', + category: '衣物', + }, + { + name: '舒适的鞋子', + category: '衣物', + }, + { + name: '牙刷', + category: '洗漱用品', + }, + { + name: '牙膏', + category: '洗漱用品', + }, + { + name: '洗发水', + category: '洗漱用品', + }, + { + name: '除臭剂', + category: '洗漱用品', + }, + { + name: '防晒霜', + category: '洗漱用品', + }, + { + name: '剃须刀', + category: '洗漱用品', + }, + { + name: '充电器', + category: '电子产品', + }, + { + name: '充电宝', + category: '电子产品', + }, + { + name: '耳机', + category: '电子产品', + }, + { + name: '旅行转换插头', + category: '电子产品', + }, + { + name: '相机', + category: '电子产品', + }, + { + name: '止痛药', + category: '健康', + }, + { + name: '创可贴', + category: '健康', + }, + { + name: '消毒液', + category: '健康', + }, + ], +}; +export default packing; diff --git a/shared/src/i18n/zh/pdf.ts b/shared/src/i18n/zh/pdf.ts new file mode 100644 index 00000000..2eb2e11c --- /dev/null +++ b/shared/src/i18n/zh/pdf.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const pdf: TranslationStrings = { + 'pdf.travelPlan': '旅行计划', + 'pdf.planned': '已规划', + 'pdf.costLabel': '费用 EUR', + 'pdf.preview': 'PDF 预览', + 'pdf.saveAsPdf': '保存为 PDF', +}; +export default pdf; diff --git a/shared/src/i18n/zh/perm.ts b/shared/src/i18n/zh/perm.ts new file mode 100644 index 00000000..1df2b6c0 --- /dev/null +++ b/shared/src/i18n/zh/perm.ts @@ -0,0 +1,51 @@ +import type { TranslationStrings } from '../types'; + +const perm: TranslationStrings = { + 'perm.title': '权限设置', + 'perm.subtitle': '控制谁可以在应用中执行操作', + 'perm.saved': '权限设置已保存', + 'perm.resetDefaults': '恢复默认', + 'perm.customized': '已自定义', + 'perm.level.admin': '仅管理员', + 'perm.level.tripOwner': '旅行所有者', + 'perm.level.tripMember': '旅行成员', + 'perm.level.everybody': '所有人', + 'perm.cat.trip': '旅行管理', + 'perm.cat.members': '成员管理', + 'perm.cat.files': '文件', + 'perm.cat.content': '内容与日程', + 'perm.cat.extras': '预算、行李与协作', + 'perm.action.trip_create': '创建旅行', + 'perm.action.trip_edit': '编辑旅行详情', + 'perm.action.trip_delete': '删除旅行', + 'perm.action.trip_archive': '归档 / 取消归档旅行', + 'perm.action.trip_cover_upload': '上传封面图片', + 'perm.action.member_manage': '添加 / 移除成员', + 'perm.action.file_upload': '上传文件', + 'perm.action.file_edit': '编辑文件元数据', + 'perm.action.file_delete': '删除文件', + 'perm.action.place_edit': '添加 / 编辑 / 删除地点', + 'perm.action.day_edit': '编辑日程、备注与分配', + 'perm.action.reservation_edit': '管理预订', + 'perm.action.budget_edit': '管理预算', + 'perm.action.packing_edit': '管理行李清单', + 'perm.action.collab_edit': '协作(笔记、投票、聊天)', + 'perm.action.share_manage': '管理分享链接', + 'perm.actionHint.trip_create': '谁可以创建新旅行', + 'perm.actionHint.trip_edit': '谁可以更改旅行名称、日期、描述和货币', + 'perm.actionHint.trip_delete': '谁可以永久删除旅行', + 'perm.actionHint.trip_archive': '谁可以归档或取消归档旅行', + 'perm.actionHint.trip_cover_upload': '谁可以上传或更改封面图片', + 'perm.actionHint.member_manage': '谁可以邀请或移除旅行成员', + 'perm.actionHint.file_upload': '谁可以向旅行上传文件', + 'perm.actionHint.file_edit': '谁可以编辑文件描述和链接', + 'perm.actionHint.file_delete': '谁可以将文件移至回收站或永久删除', + 'perm.actionHint.place_edit': '谁可以添加、编辑或删除地点', + 'perm.actionHint.day_edit': '谁可以编辑日程、日程备注和地点分配', + 'perm.actionHint.reservation_edit': '谁可以创建、编辑或删除预订', + 'perm.actionHint.budget_edit': '谁可以创建、编辑或删除预算项目', + 'perm.actionHint.packing_edit': '谁可以管理行李物品和包袋', + 'perm.actionHint.collab_edit': '谁可以创建笔记、投票和发送消息', + 'perm.actionHint.share_manage': '谁可以创建或删除公开分享链接', +}; +export default perm; diff --git a/shared/src/i18n/zh/photos.ts b/shared/src/i18n/zh/photos.ts new file mode 100644 index 00000000..ee8e4751 --- /dev/null +++ b/shared/src/i18n/zh/photos.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const photos: TranslationStrings = { + 'photos.title': '照片', + 'photos.subtitle': '{trip} 的 {count} 张照片', + 'photos.dropHere': '将照片拖放至此...', + 'photos.dropHereActive': '将照片拖放至此', + 'photos.captionForAll': '标题(所有)', + 'photos.captionPlaceholder': '可选标题...', + 'photos.addCaption': '添加标题...', + 'photos.allDays': '所有天', + 'photos.noPhotos': '暂无照片', + 'photos.uploadHint': '上传你的旅行照片', + 'photos.clickToSelect': '或点击选择', + 'photos.linkPlace': '关联地点', + 'photos.noPlace': '无地点', + 'photos.uploadN': '上传 {n} 张照片', + 'photos.linkDay': '关联天数', + 'photos.noDay': '无天数', + 'photos.dayLabel': '第 {number} 天', + 'photos.photoSelected': '张照片已选择', + 'photos.photosSelected': '张照片已选择', + 'photos.fileTypeHint': 'JPG, PNG, WebP · 最大 10 MB · 最多 30 张照片', +}; +export default photos; diff --git a/shared/src/i18n/zh/places.ts b/shared/src/i18n/zh/places.ts new file mode 100644 index 00000000..5bbb8cb7 --- /dev/null +++ b/shared/src/i18n/zh/places.ts @@ -0,0 +1,87 @@ +import type { TranslationStrings } from '../types'; + +const places: TranslationStrings = { + 'places.addPlace': '添加地点/活动', + 'places.importFile': '导入文件', + 'places.sidebarDrop': '拖放以导入', + 'places.importFileHint': + '从 Google My Maps、Google Earth 或 GPS 追踪器等工具导入 .gpx、.kml 或 .kmz 文件。', + 'places.importFileDropHere': '点击选择文件或拖放到此处', + 'places.importFileDropActive': '释放文件以选择', + 'places.importFileUnsupported': + '不支持的文件类型,请使用 .gpx、.kml 或 .kmz。', + 'places.importFileTooLarge': '文件过大。最大上传大小为 {maxMb} MB。', + 'places.importFileError': '导入失败', + 'places.importAllSkipped': '所有地点已在行程中。', + 'places.gpxImported': '已从 GPX 导入 {count} 个地点', + 'places.gpxImportTypes': '要导入什么?', + 'places.gpxImportWaypoints': '路点', + 'places.gpxImportRoutes': '路线', + 'places.gpxImportTracks': '轨迹(含路径几何)', + 'places.gpxImportNoneSelected': '请至少选择一种导入类型。', + 'places.kmlImportTypes': '要导入什么?', + 'places.kmlImportPoints': '点(Placemarks)', + 'places.kmlImportPaths': '路径(LineStrings)', + 'places.kmlImportNoneSelected': '请至少选择一种类型。', + 'places.selectionCount': '已选 {count} 项', + 'places.deleteSelected': '删除所选', + 'places.kmlKmzImported': '已从 KMZ/KML 导入 {count} 个地点', + 'places.urlResolved': '已从 URL 导入地点', + 'places.importList': '列表导入', + 'places.kmlKmzSummaryValues': + 'Placemarks:{total} • 已导入:{created} • 已跳过:{skipped}', + 'places.importGoogleList': 'Google 列表', + 'places.importNaverList': 'Naver 列表', + 'places.googleListHint': '粘贴共享的 Google Maps 列表链接以导入所有地点。', + 'places.googleListImported': '已从"{list}"导入 {count} 个地点', + 'places.googleListError': 'Google Maps 列表导入失败', + 'places.naverListHint': '粘贴共享的 Naver Maps 列表链接以导入所有地点。', + 'places.naverListImported': '已从"{list}"导入 {count} 个地点', + 'places.naverListError': 'Naver Maps 列表导入失败', + 'places.viewDetails': '查看详情', + 'places.assignToDay': '添加到哪一天?', + 'places.all': '全部', + 'places.unplanned': '未规划', + 'places.filterTracks': '路线', + 'places.search': '搜索地点...', + 'places.allCategories': '所有分类', + 'places.categoriesSelected': '个分类', + 'places.clearFilter': '清除筛选', + 'places.count': '{count} 个地点', + 'places.countSingular': '1 个地点', + 'places.allPlanned': '所有地点已规划', + 'places.noneFound': '未找到地点', + 'places.editPlace': '编辑地点', + 'places.formName': '名称', + 'places.formNamePlaceholder': '如:埃菲尔铁塔', + 'places.formDescription': '描述', + 'places.formDescriptionPlaceholder': '简短描述...', + 'places.formAddress': '地址', + 'places.formAddressPlaceholder': '街道、城市、国家', + 'places.formLat': '纬度(如 48.8566)', + 'places.formLng': '经度(如 2.3522)', + 'places.formCategory': '分类', + 'places.noCategory': '无分类', + 'places.categoryNamePlaceholder': '分类名称', + 'places.formTime': '时间', + 'places.startTime': '开始', + 'places.endTime': '结束', + 'places.endTimeBeforeStart': '结束时间早于开始时间', + 'places.timeCollision': '时间冲突:', + 'places.formWebsite': '网站', + 'places.formNotes': '备注', + 'places.formNotesPlaceholder': '个人备注...', + 'places.formReservation': '预订', + 'places.reservationNotesPlaceholder': '预订备注、确认号...', + 'places.mapsSearchPlaceholder': '搜索地点...', + 'places.mapsSearchError': '地点搜索失败。', + 'places.loadingDetails': '正在加载地点详情…', + 'places.osmHint': + '使用 OpenStreetMap 搜索(无照片、营业时间或评分)。在设置中添加 Google API 密钥以获取完整信息。', + 'places.osmActive': + '通过 OpenStreetMap 搜索(无照片、评分或营业时间)。在设置中添加 Google API 密钥以获取增强数据。', + 'places.categoryCreateError': '创建分类失败', + 'places.nameRequired': '请输入名称', + 'places.saveError': '保存失败', +}; +export default places; diff --git a/shared/src/i18n/zh/planner.ts b/shared/src/i18n/zh/planner.ts new file mode 100644 index 00000000..3722b1de --- /dev/null +++ b/shared/src/i18n/zh/planner.ts @@ -0,0 +1,67 @@ +import type { TranslationStrings } from '../types'; + +const planner: TranslationStrings = { + 'planner.places': '地点', + 'planner.bookings': '预订', + 'planner.packingList': '行李清单', + 'planner.documents': '文档', + 'planner.dayPlan': '日程计划', + 'planner.reservations': '预订', + 'planner.minTwoPlaces': '至少需要 2 个有坐标的地点', + 'planner.noGeoPlaces': '没有有坐标的地点', + 'planner.routeCalculated': '路线已计算', + 'planner.routeCalcFailed': '无法计算路线', + 'planner.routeError': '路线计算错误', + 'planner.icsExportFailed': 'ICS 导出失败', + 'planner.routeOptimized': '路线已优化', + 'planner.reservationUpdated': '预订已更新', + 'planner.reservationAdded': '预订已添加', + 'planner.confirmDeleteReservation': '删除预订?', + 'planner.reservationDeleted': '预订已删除', + 'planner.days': '天', + 'planner.allPlaces': '所有地点', + 'planner.totalPlaces': '共 {n} 个地点', + 'planner.noDaysPlanned': '尚未规划天数', + 'planner.editTrip': '编辑旅行 →', + 'planner.placeOne': '1 个地点', + 'planner.placeN': '{n} 个地点', + 'planner.addNote': '添加备注', + 'planner.noEntries': '当天无条目', + 'planner.addPlace': '添加地点/活动', + 'planner.addPlaceShort': '+ 添加地点/活动', + 'planner.resPending': '预订待确认 · ', + 'planner.resConfirmed': '预订已确认 · ', + 'planner.notePlaceholder': '备注…', + 'planner.noteTimePlaceholder': '时间(可选)', + 'planner.noteExamplePlaceholder': + '如:14:30 从中央车站乘 S3,7 号码头渡轮,午餐休息…', + 'planner.totalCost': '总费用', + 'planner.searchPlaces': '搜索地点…', + 'planner.allCategories': '所有分类', + 'planner.noPlacesFound': '未找到地点', + 'planner.addFirstPlace': '添加第一个地点', + 'planner.noReservations': '暂无预订', + 'planner.addFirstReservation': '添加第一个预订', + 'planner.new': '新建', + 'planner.addToDay': '+ 天', + 'planner.calculating': '计算中…', + 'planner.route': '路线', + 'planner.optimize': '优化', + 'planner.openGoogleMaps': '在 Google Maps 中打开', + 'planner.selectDayHint': '从左侧列表选择一天以查看日程计划', + 'planner.noPlacesForDay': '当天暂无地点', + 'planner.addPlacesLink': '添加地点 →', + 'planner.minTotal': '分钟 合计', + 'planner.noReservation': '无预订', + 'planner.removeFromDay': '从当天移除', + 'planner.addToThisDay': '添加到当天', + 'planner.overview': '概览', + 'planner.noDays': '暂无天数', + 'planner.editTripToAddDays': '编辑旅行以添加天数', + 'planner.dayCount': '{n} 天', + 'planner.clickToUnlock': '点击解锁', + 'planner.keepPosition': '路线优化时保持位置', + 'planner.dayDetails': '日程详情', + 'planner.dayN': '第 {n} 天', +}; +export default planner; diff --git a/shared/src/i18n/zh/register.ts b/shared/src/i18n/zh/register.ts new file mode 100644 index 00000000..e7c74448 --- /dev/null +++ b/shared/src/i18n/zh/register.ts @@ -0,0 +1,25 @@ +import type { TranslationStrings } from '../types'; + +const register: TranslationStrings = { + 'register.passwordMismatch': '两次输入的密码不一致', + 'register.passwordTooShort': '密码至少需要 8 个字符', + 'register.failed': '注册失败', + 'register.getStarted': '开始使用', + 'register.subtitle': '创建账户,开始规划你的梦想旅行。', + 'register.feature1': '无限旅行计划', + 'register.feature2': '互动地图视图', + 'register.feature3': '管理地点和分类', + 'register.feature4': '跟踪预订', + 'register.feature5': '创建行李清单', + 'register.feature6': '存储照片和文件', + 'register.createAccount': '创建账户', + 'register.startPlanning': '开始规划你的旅行', + 'register.minChars': '至少 6 个字符', + 'register.confirmPassword': '确认密码', + 'register.repeatPassword': '重复密码', + 'register.registering': '注册中...', + 'register.register': '注册', + 'register.hasAccount': '已有账户?', + 'register.signIn': '登录', +}; +export default register; diff --git a/shared/src/i18n/zh/reservations.ts b/shared/src/i18n/zh/reservations.ts new file mode 100644 index 00000000..6a2f246d --- /dev/null +++ b/shared/src/i18n/zh/reservations.ts @@ -0,0 +1,115 @@ +import type { TranslationStrings } from '../types'; + +const reservations: TranslationStrings = { + 'reservations.title': '预订', + 'reservations.empty': '暂无预订', + 'reservations.emptyHint': '添加航班、酒店等预订信息', + 'reservations.add': '添加预订', + 'reservations.addManual': '手动添加', + 'reservations.placeHint': + '提示:建议从地点直接创建预订,以便与日程计划关联。', + 'reservations.confirmed': '已确认', + 'reservations.pending': '待确认', + 'reservations.summary': '{confirmed} 已确认,{pending} 待确认', + 'reservations.fromPlan': '来自计划', + 'reservations.showFiles': '查看文件', + 'reservations.editTitle': '编辑预订', + 'reservations.status': '状态', + 'reservations.datetime': '日期和时间', + 'reservations.startTime': '开始时间', + 'reservations.endTime': '结束时间', + 'reservations.date': '日期', + 'reservations.time': '时间', + 'reservations.timeAlt': '时间(备选,如 19:30)', + 'reservations.notes': '备注', + 'reservations.notesPlaceholder': '其他备注...', + 'reservations.meta.airline': '航空公司', + 'reservations.meta.flightNumber': '航班号', + 'reservations.meta.from': '出发', + 'reservations.meta.to': '到达', + 'reservations.needsReview': '待确认', + 'reservations.needsReviewHint': '无法自动匹配机场 — 请确认位置。', + 'reservations.searchLocation': '搜索车站、港口、地址...', + 'reservations.meta.trainNumber': '车次', + 'reservations.meta.platform': '站台', + 'reservations.meta.seat': '座位', + 'reservations.meta.checkIn': '入住', + 'reservations.meta.checkInUntil': '入住截止', + 'reservations.meta.checkOut': '退房', + 'reservations.meta.linkAccommodation': '住宿', + 'reservations.meta.pickAccommodation': '关联住宿', + 'reservations.meta.noAccommodation': '无', + 'reservations.meta.hotelPlace': '住宿', + 'reservations.meta.pickHotel': '选择住宿', + 'reservations.meta.fromDay': '从', + 'reservations.meta.toDay': '到', + 'reservations.meta.selectDay': '选择日期', + 'reservations.type.flight': '航班', + 'reservations.type.hotel': '住宿', + 'reservations.type.restaurant': '餐厅', + 'reservations.type.train': '火车', + 'reservations.type.car': '汽车', + 'reservations.type.cruise': '邮轮', + 'reservations.type.event': '活动', + 'reservations.type.tour': '旅游团', + 'reservations.type.other': '其他', + 'reservations.confirm.delete': '确定要删除预订「{name}」吗?', + 'reservations.confirm.deleteTitle': '删除预订?', + 'reservations.confirm.deleteBody': '"{name}" 将被永久删除。', + 'reservations.toast.updated': '预订已更新', + 'reservations.toast.removed': '预订已删除', + 'reservations.toast.fileUploaded': '文件已上传', + 'reservations.toast.uploadError': '上传失败', + 'reservations.newTitle': '新建预订', + 'reservations.bookingType': '预订类型', + 'reservations.titleLabel': '标题', + 'reservations.titlePlaceholder': '如:汉莎 LH123、阿德隆酒店...', + 'reservations.locationAddress': '地点 / 地址', + 'reservations.locationPlaceholder': '地址、机场、酒店...', + 'reservations.confirmationCode': '预订码', + 'reservations.confirmationPlaceholder': '如:ABC12345', + 'reservations.day': '日期', + 'reservations.noDay': '无日期', + 'reservations.place': '地点', + 'reservations.noPlace': '无地点', + 'reservations.pendingSave': '将被保存…', + 'reservations.uploading': '上传中...', + 'reservations.attachFile': '附加文件', + 'reservations.linkExisting': '关联已有文件', + 'reservations.toast.saveError': '保存失败', + 'reservations.toast.updateError': '更新失败', + 'reservations.toast.deleteError': '删除失败', + 'reservations.confirm.remove': '移除「{name}」的预订?', + 'reservations.linkAssignment': '关联日程分配', + 'reservations.pickAssignment': '从计划中选择一个分配...', + 'reservations.noAssignment': '无关联(独立)', + 'reservations.price': '价格', + 'reservations.budgetCategory': '预算类别', + 'reservations.budgetCategoryPlaceholder': '例:交通、住宿', + 'reservations.budgetCategoryAuto': '自动(按预订类型)', + 'reservations.budgetHint': '保存时将自动创建预算条目。', + 'reservations.departureDate': '出发', + 'reservations.arrivalDate': '到达', + 'reservations.departureTime': '出发时间', + 'reservations.arrivalTime': '到达时间', + 'reservations.pickupDate': '取车', + 'reservations.returnDate': '还车', + 'reservations.pickupTime': '取车时间', + 'reservations.returnTime': '还车时间', + 'reservations.endDate': '结束日期', + 'reservations.meta.departureTimezone': '出发时区', + 'reservations.meta.arrivalTimezone': '到达时区', + 'reservations.span.departure': '出发', + 'reservations.span.arrival': '到达', + 'reservations.span.inTransit': '途中', + 'reservations.span.pickup': '取车', + 'reservations.span.return': '还车', + 'reservations.span.active': '使用中', + 'reservations.span.start': '开始', + 'reservations.span.end': '结束', + 'reservations.span.ongoing': '进行中', + 'reservations.validation.endBeforeStart': + '结束日期/时间必须晚于开始日期/时间', + 'reservations.addBooking': '添加预订', +}; +export default reservations; diff --git a/shared/src/i18n/zh/settings.ts b/shared/src/i18n/zh/settings.ts new file mode 100644 index 00000000..fc7f60a8 --- /dev/null +++ b/shared/src/i18n/zh/settings.ts @@ -0,0 +1,285 @@ +import type { TranslationStrings } from '../types'; + +const settings: TranslationStrings = { + 'settings.title': '设置', + 'settings.subtitle': '配置你的个人设置', + 'settings.tabs.display': '显示', + 'settings.tabs.map': '地图', + 'settings.tabs.notifications': '通知', + 'settings.tabs.integrations': '集成', + 'settings.tabs.account': '账户', + 'settings.tabs.offline': 'Offline', + 'settings.tabs.about': '关于', + 'settings.map': '地图', + 'settings.mapTemplate': '地图模板', + 'settings.mapTemplatePlaceholder.select': '选择模板...', + 'settings.mapDefaultHint': '留空则使用 OpenStreetMap(默认)', + 'settings.mapTemplatePlaceholder': + 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', + 'settings.mapHint': '地图瓦片 URL 模板', + 'settings.mapProvider': '地图提供商', + 'settings.mapProviderHint': + '影响行程规划和旅程地图。Atlas 始终使用 Leaflet。', + 'settings.mapLeafletSubtitle': '经典 2D,任何栅格瓦片', + 'settings.mapMapboxSubtitle': '矢量瓦片、3D 建筑和地形', + 'settings.mapExperimental': '实验性', + 'settings.mapMapboxToken': 'Mapbox 访问令牌', + 'settings.mapMapboxTokenHint': '公共令牌 (pk.*) 来自', + 'settings.mapMapboxTokenLink': 'mapbox.com → 访问令牌', + 'settings.mapStyle': '地图样式', + 'settings.mapStylePlaceholder': '选择 Mapbox 样式', + 'settings.mapStyleHint': '预设或您自己的 mapbox://styles/USER/ID URL', + 'settings.map3dBuildings': '3D 建筑和地形', + 'settings.map3dHint': '倾斜 + 真实 3D 建筑拉伸 — 适用于所有样式,包括卫星。', + 'settings.mapHighQuality': '高画质模式', + 'settings.mapHighQualityHint': + '抗锯齿 + 地球投影,带来更清晰的边缘和更真实的世界视图。', + 'settings.mapHighQualityWarning': '可能影响低端设备的性能。', + 'settings.mapTipLabel': '提示:', + 'settings.mapTip': + '右键点击并拖动以旋转/倾斜地图。中键点击添加地点(右键用于旋转)。', + 'settings.latitude': '纬度', + 'settings.longitude': '经度', + 'settings.saveMap': '保存地图', + 'settings.apiKeys': 'API 密钥', + 'settings.mapsKey': 'Google Maps API 密钥', + 'settings.mapsKeyHint': + '用于地点搜索。需要 Places API (New)。在 console.cloud.google.com 获取', + 'settings.weatherKey': 'OpenWeatherMap API 密钥', + 'settings.weatherKeyHint': '用于天气数据。在 openweathermap.org/api 免费获取', + 'settings.keyPlaceholder': '输入密钥...', + 'settings.configured': '已配置', + 'settings.saveKeys': '保存密钥', + 'settings.display': '显示', + 'settings.colorMode': '颜色模式', + 'settings.light': '浅色', + 'settings.dark': '深色', + 'settings.auto': '自动', + 'settings.language': '语言', + 'settings.temperature': '温度单位', + 'settings.timeFormat': '时间格式', + 'settings.blurBookingCodes': '模糊预订代码', + 'settings.notifications': '通知', + 'settings.notifyTripInvite': '旅行邀请', + 'settings.notifyBookingChange': '预订变更', + 'settings.notifyTripReminder': '旅行提醒', + 'settings.notifyTodoDue': '待办事项即将到期', + 'settings.notifyVacayInvite': 'Vacay 融合邀请', + 'settings.notifyPhotosShared': '共享照片 (Immich)', + 'settings.notifyCollabMessage': '聊天消息 (Collab)', + 'settings.notifyPackingTagged': '行李清单:分配', + 'settings.notifyWebhook': 'Webhook 通知', + 'settings.notificationsDisabled': + '通知尚未配置。请联系管理员启用电子邮件或 Webhook 通知。', + 'settings.notificationsActive': '活跃频道', + 'settings.notificationsManagedByAdmin': '通知事件由管理员配置。', + 'settings.on': '开', + 'settings.off': '关', + 'settings.mcp.title': 'MCP 配置', + 'settings.mcp.endpoint': 'MCP 端点', + 'settings.mcp.clientConfig': '客户端配置', + 'settings.mcp.clientConfigHint': + '将 替换为下方列表中的 API 令牌。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。', + 'settings.mcp.clientConfigHintOAuth': + '将 替换为上方创建的 OAuth 2.1 客户端凭据。首次连接时,mcp-remote 将打开浏览器完成授权。npx 的路径可能需要根据您的系统进行调整(例如 Windows 上为 C:\\PROGRA~1\\nodejs\\npx.cmd)。', + 'settings.mcp.copy': '复制', + 'settings.mcp.copied': '已复制!', + 'settings.mcp.apiTokens': 'API 令牌', + 'settings.mcp.createToken': '创建新令牌', + 'settings.mcp.noTokens': '暂无令牌,请创建一个以连接 MCP 客户端。', + 'settings.mcp.tokenCreatedAt': '创建于', + 'settings.mcp.tokenUsedAt': '使用于', + 'settings.mcp.deleteTokenTitle': '删除令牌', + 'settings.mcp.deleteTokenMessage': + '此令牌将立即失效,使用它的所有 MCP 客户端将失去访问权限。', + 'settings.mcp.modal.createTitle': '创建 API 令牌', + 'settings.mcp.modal.tokenName': '令牌名称', + 'settings.mcp.modal.tokenNamePlaceholder': '例如:Claude Desktop、工作电脑', + 'settings.mcp.modal.creating': '创建中…', + 'settings.mcp.modal.create': '创建令牌', + 'settings.mcp.modal.createdTitle': '令牌已创建', + 'settings.mcp.modal.createdWarning': + '此令牌只会显示一次,请立即复制并妥善保存——无法找回。', + 'settings.mcp.modal.done': '完成', + 'settings.mcp.toast.created': '令牌已创建', + 'settings.mcp.toast.createError': '创建令牌失败', + 'settings.mcp.toast.deleted': '令牌已删除', + 'settings.mcp.toast.deleteError': '删除令牌失败', + 'settings.mcp.apiTokensDeprecated': + 'API 令牌已弃用,将在未来版本中移除。请改用 OAuth 2.1 客户端。', + 'settings.oauth.clients': 'OAuth 2.1 客户端', + 'settings.oauth.clientsHint': + '注册 OAuth 2.1 客户端,让第三方 MCP 应用程序(Claude Web、Cursor 等)无需静态令牌即可连接。', + 'settings.oauth.createClient': '新建客户端', + 'settings.oauth.noClients': '没有已注册的 OAuth 客户端。', + 'settings.oauth.clientId': '客户端 ID', + 'settings.oauth.clientSecret': '客户端密钥', + 'settings.oauth.deleteClient': '删除客户端', + 'settings.oauth.deleteClientMessage': + '此客户端及所有活跃会话将被永久删除。使用此客户端的任何应用程序将立即失去访问权限。', + 'settings.oauth.rotateSecret': '轮换密钥', + 'settings.oauth.rotateSecretMessage': + '将生成新的客户端密钥,所有现有会话将立即失效。在关闭此对话框之前,请更新您的应用程序。', + 'settings.oauth.rotateSecretConfirm': '轮换', + 'settings.oauth.rotateSecretConfirming': '轮换中…', + 'settings.oauth.rotateSecretDoneTitle': '已生成新密钥', + 'settings.oauth.rotateSecretDoneWarning': + '此密钥仅显示一次。请立即复制并更新您的应用程序——所有之前的会话已失效。', + 'settings.oauth.activeSessions': '活跃的 OAuth 会话', + 'settings.oauth.sessionScopes': '权限范围', + 'settings.oauth.sessionExpires': '过期时间', + 'settings.oauth.revoke': '撤销', + 'settings.oauth.revokeSession': '撤销会话', + 'settings.oauth.revokeSessionMessage': + '这将立即撤销此 OAuth 会话的访问权限。', + 'settings.oauth.modal.createTitle': '注册 OAuth 客户端', + 'settings.oauth.modal.presets': '快速预设', + 'settings.oauth.modal.clientName': '应用程序名称', + 'settings.oauth.modal.clientNamePlaceholder': + '例如 Claude Web、我的 MCP 应用', + 'settings.oauth.modal.redirectUris': '重定向 URI', + 'settings.oauth.modal.redirectUrisPlaceholder': + 'https://your-app.com/callback\nhttps://your-app.com/auth', + 'settings.oauth.modal.redirectUrisHint': + '每行一个 URI。需要 HTTPS(localhost 除外)。要求精确匹配。', + 'settings.oauth.modal.scopes': '允许的权限范围', + 'settings.oauth.modal.scopesHint': + 'list_trips 和 get_trip_summary 始终可用——无需权限范围。它们帮助 AI 发现所需的行程 ID。', + 'settings.oauth.modal.selectAll': '全选', + 'settings.oauth.modal.deselectAll': '取消全选', + 'settings.oauth.modal.creating': '注册中…', + 'settings.oauth.modal.create': '注册客户端', + 'settings.oauth.modal.createdTitle': '客户端已注册', + 'settings.oauth.modal.createdWarning': + '客户端密钥仅显示一次。请立即复制——无法恢复。', + 'settings.oauth.toast.createError': '注册 OAuth 客户端失败', + 'settings.oauth.toast.deleted': 'OAuth 客户端已删除', + 'settings.oauth.toast.deleteError': '删除 OAuth 客户端失败', + 'settings.oauth.toast.revoked': '会话已撤销', + 'settings.oauth.toast.revokeError': '撤销会话失败', + 'settings.oauth.toast.rotateError': '轮换客户端密钥失败', + 'settings.oauth.modal.machineClient': '机器客户端(无需浏览器登录)', + 'settings.oauth.modal.machineClientHint': + '使用 client_credentials 授权——无需重定向 URI。令牌通过 client_id + client_secret 直接颁发,并在所选范围内以您的身份运行。', + 'settings.oauth.modal.machineClientUsage': + '获取令牌:向 /oauth/token 发送 POST 请求,携带 grant_type=client_credentials、client_id 和 client_secret。无需浏览器,无刷新令牌。', + 'settings.oauth.badge.machine': '机器', + 'settings.account': '账户', + 'settings.about': '关于', + 'settings.about.reportBug': '报告错误', + 'settings.about.reportBugHint': '发现问题?告诉我们', + 'settings.about.featureRequest': '功能建议', + 'settings.about.featureRequestHint': '建议一个新功能', + 'settings.about.wikiHint': '文档和指南', + 'settings.about.supporters.badge': '月度支持者', + 'settings.about.supporters.title': '与 TREK 同行的伙伴', + 'settings.about.supporters.subtitle': + '当你在规划下一段路线时,这些人也在一起规划 TREK 的未来。他们每月的支持直接用于开发与真实投入的时间——让 TREK 保持开源。', + 'settings.about.supporters.since': '{date} 起的支持者', + 'settings.about.supporters.tierEmpty': '成为第一个', + 'settings.about.supporter.tier.noReturnTicket': 'No Return Ticket', + 'settings.about.supporter.tier.lostLuggageVip': 'Lost Luggage VIP', + 'settings.about.supporter.tier.businessClassDreamer': + 'Business Class Dreamer', + 'settings.about.supporter.tier.budgetTraveller': 'Budget Traveller', + 'settings.about.supporter.tier.hostelBunkmate': 'Hostel Bunkmate', + 'settings.about.description': + 'TREK 是一个自托管的旅行规划工具,帮助你从最初的想法到最后的回忆,全程组织你的旅行。日程规划、预算、行李清单、照片等——一切尽在一处,在你自己的服务器上。', + 'settings.about.madeWith': '用', + 'settings.about.madeBy': '由 Maurice 和不断壮大的开源社区打造。', + 'settings.username': '用户名', + 'settings.email': '邮箱', + 'settings.role': '角色', + 'settings.roleAdmin': '管理员', + 'settings.oidcLinked': '已关联', + 'settings.changePassword': '修改密码', + 'settings.mustChangePassword': '您必须更改密码才能继续。请在下方设置新密码。', + 'settings.currentPassword': '当前密码', + 'settings.currentPasswordRequired': '请输入当前密码', + 'settings.newPassword': '新密码', + 'settings.confirmPassword': '确认新密码', + 'settings.updatePassword': '更新密码', + 'settings.passwordRequired': '请输入当前密码和新密码', + 'settings.passwordTooShort': '密码至少需要 8 个字符', + 'settings.passwordMismatch': '两次输入的密码不一致', + 'settings.passwordWeak': '密码必须包含大写字母、小写字母、数字和特殊字符', + 'settings.passwordChanged': '密码修改成功', + 'settings.deleteAccount': '删除账户', + 'settings.deleteAccountTitle': '确定删除账户?', + 'settings.deleteAccountWarning': + '你的账户以及所有旅行、地点和文件将被永久删除。此操作无法撤销。', + 'settings.deleteAccountConfirm': '永久删除', + 'settings.deleteBlockedTitle': '无法删除', + 'settings.deleteBlockedMessage': + '你是唯一的管理员。请先将其他用户提升为管理员,然后再删除账户。', + 'settings.roleUser': '用户', + 'settings.saveProfile': '保存资料', + 'settings.mfa.title': '双因素认证 (2FA)', + 'settings.mfa.description': + '登录时添加第二步验证。使用身份验证器应用(Google Authenticator、Authy 等)。', + 'settings.mfa.requiredByPolicy': + '管理员要求双因素身份验证。请先完成下方的身份验证器设置后再继续。', + 'settings.mfa.backupTitle': '备用代码', + 'settings.mfa.backupDescription': + '如果你无法使用身份验证器应用,可使用这些一次性备用代码登录。', + 'settings.mfa.backupWarning': '请立即保存这些代码。每个代码只能使用一次。', + 'settings.mfa.backupCopy': '复制代码', + 'settings.mfa.backupDownload': '下载 TXT', + 'settings.mfa.backupPrint': '打印 / PDF', + 'settings.mfa.backupCopied': '备用代码已复制', + 'settings.mfa.enabled': '您的账户已启用 2FA。', + 'settings.mfa.disabled': '2FA 未启用。', + 'settings.mfa.setup': '设置身份验证器', + 'settings.mfa.scanQr': '使用应用扫描此二维码,或手动输入密钥。', + 'settings.mfa.secretLabel': '密钥(手动输入)', + 'settings.mfa.codePlaceholder': '6 位验证码', + 'settings.mfa.enable': '启用 2FA', + 'settings.mfa.cancelSetup': '取消', + 'settings.mfa.disableTitle': '停用 2FA', + 'settings.mfa.disableHint': '输入您的账户密码和身份验证器中的当前验证码。', + 'settings.mfa.disable': '停用 2FA', + 'settings.mfa.toastEnabled': '双因素认证已启用', + 'settings.mfa.toastDisabled': '双因素认证已停用', + 'settings.mfa.demoBlocked': '演示模式下不可用', + 'settings.toast.mapSaved': '地图设置已保存', + 'settings.toast.keysSaved': 'API 密钥已保存', + 'settings.toast.displaySaved': '显示设置已保存', + 'settings.toast.profileSaved': '资料已保存', + 'settings.uploadAvatar': '上传头像', + 'settings.removeAvatar': '移除头像', + 'settings.avatarUploaded': '头像已更新', + 'settings.avatarRemoved': '头像已移除', + 'settings.avatarError': '上传失败', + 'settings.bookingLabels': '预订路线标签', + 'settings.bookingLabelsHint': + '在地图上显示车站 / 机场名称。关闭时仅显示图标。', + 'settings.notifyVersionAvailable': '有新版本可用', + 'settings.notificationPreferences.noChannels': + '未配置通知渠道。请联系管理员设置电子邮件或 Webhook 通知。', + 'settings.webhookUrl.label': 'Webhook URL', + 'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...', + 'settings.webhookUrl.hint': + '输入您的 Discord、Slack 或自定义 Webhook URL 以接收通知。', + 'settings.webhookUrl.saved': 'Webhook URL 已保存', + 'settings.webhookUrl.test': '测试', + 'settings.webhookUrl.testSuccess': '测试 Webhook 发送成功', + 'settings.webhookUrl.testFailed': '测试 Webhook 失败', + 'settings.ntfyUrl.topicLabel': 'Ntfy 主题', + 'settings.ntfyUrl.topicPlaceholder': 'my-trek-alerts', + 'settings.ntfyUrl.serverLabel': 'Ntfy 服务器 URL(可选)', + 'settings.ntfyUrl.serverPlaceholder': 'https://ntfy.sh', + 'settings.ntfyUrl.hint': + '输入您的 Ntfy 主题以接收推送通知。将服务器留空以使用管理员配置的默认值。', + 'settings.ntfyUrl.tokenLabel': '访问令牌(可选)', + 'settings.ntfyUrl.tokenHint': '受密码保护的主题需要此项。', + 'settings.ntfyUrl.saved': 'Ntfy 设置已保存', + 'settings.ntfyUrl.test': '测试', + 'settings.ntfyUrl.testSuccess': '测试 Ntfy 通知发送成功', + 'settings.ntfyUrl.testFailed': '测试 Ntfy 通知失败', + 'settings.ntfyUrl.tokenCleared': '访问令牌已清除', + 'settings.notificationPreferences.inapp': 'In-App', + 'settings.notificationPreferences.webhook': 'Webhook', + 'settings.notificationPreferences.email': 'Email', + 'settings.notificationPreferences.ntfy': 'Ntfy', +}; +export default settings; diff --git a/shared/src/i18n/zh/share.ts b/shared/src/i18n/zh/share.ts new file mode 100644 index 00000000..d7a6e767 --- /dev/null +++ b/shared/src/i18n/zh/share.ts @@ -0,0 +1,16 @@ +import type { TranslationStrings } from '../types'; + +const share: TranslationStrings = { + 'share.linkTitle': '公开链接', + 'share.linkHint': + '创建一个链接,任何人无需登录即可查看此旅行。仅可查看,无法编辑。', + 'share.createLink': '创建链接', + 'share.deleteLink': '删除链接', + 'share.createError': '无法创建链接', + 'share.permMap': '地图与计划', + 'share.permBookings': '预订', + 'share.permPacking': '行李', + 'share.permBudget': '预算', + 'share.permCollab': '聊天', +}; +export default share; diff --git a/shared/src/i18n/zh/shared.ts b/shared/src/i18n/zh/shared.ts new file mode 100644 index 00000000..e76f2116 --- /dev/null +++ b/shared/src/i18n/zh/shared.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const shared: TranslationStrings = { + 'shared.expired': '链接已过期或无效', + 'shared.expiredHint': '此共享旅行链接已失效。', + 'shared.readOnly': '只读共享视图', + 'shared.tabPlan': '计划', + 'shared.tabBookings': '预订', + 'shared.tabPacking': '行李', + 'shared.tabBudget': '预算', + 'shared.tabChat': '聊天', + 'shared.days': '天', + 'shared.places': '个地点', + 'shared.other': '其他', + 'shared.totalBudget': '总预算', + 'shared.messages': '条消息', + 'shared.sharedVia': '通过以下分享', + 'shared.confirmed': '已确认', + 'shared.pending': '待确认', +}; +export default shared; diff --git a/shared/src/i18n/zh/stats.ts b/shared/src/i18n/zh/stats.ts new file mode 100644 index 00000000..268f186c --- /dev/null +++ b/shared/src/i18n/zh/stats.ts @@ -0,0 +1,13 @@ +import type { TranslationStrings } from '../types'; + +const stats: TranslationStrings = { + 'stats.countries': '国家', + 'stats.cities': '城市', + 'stats.trips': '旅行', + 'stats.places': '地点', + 'stats.worldProgress': '全球进度', + 'stats.visited': '已访问', + 'stats.remaining': '未访问', + 'stats.visitedCountries': '已访问国家', +}; +export default stats; diff --git a/shared/src/i18n/zh/system_notice.ts b/shared/src/i18n/zh/system_notice.ts new file mode 100644 index 00000000..26436fcf --- /dev/null +++ b/shared/src/i18n/zh/system_notice.ts @@ -0,0 +1,50 @@ +import type { TranslationStrings } from '../types'; + +const system_notice: TranslationStrings = { + 'system_notice.welcome_v1.title': '欢迎使用 TREK', + 'system_notice.welcome_v1.body': + '您的全能旅行规划器。制定行程、与朋友分享旅行,随时保持井然有序——在线或离线均可。', + 'system_notice.welcome_v1.cta_label': '规划行程', + 'system_notice.welcome_v1.hero_alt': '风景优美的旅游目的地与 TREK 界面', + 'system_notice.welcome_v1.highlight_plan': '逐日行程规划', + 'system_notice.welcome_v1.highlight_share': '与旅行伙伴协作', + 'system_notice.welcome_v1.highlight_offline': '移动端支持离线使用', + 'system_notice.dev_test_modal.title': '[Dev] Test notice', + 'system_notice.dev_test_modal.body': 'This is a dev-only test notice.', + 'system_notice.pager.prev': '上一条通知', + 'system_notice.pager.next': '下一条通知', + 'system_notice.pager.counter': '{current} / {total}', + 'system_notice.pager.goto': '转到通知 {n}', + 'system_notice.pager.position': '通知 {current}/{total}', + 'system_notice.v3_photos.title': '3.0 版照片已迁移', + 'system_notice.v3_photos.body': + '行程规划器中的​**照片**标签已被移除。您的照片安全无虑 — TREK 从未修改您的 Immich 或 Synology 相册。\n\n照片现在位于 **Journey** 插件中。Journey 是可选的 — 如果尚未启用,请联系管理员在 Admin → 插件 中开启。', + 'system_notice.v3_journey.title': '认识 Journey — 旅行日记', + 'system_notice.v3_journey.body': + '将您的旅程记录为展示时间线、照片画廊和互动地图的丰富旅行故事。', + 'system_notice.v3_journey.cta_label': '打开 Journey', + 'system_notice.v3_journey.highlight_timeline': '每日时间线与画廊', + 'system_notice.v3_journey.highlight_photos': '从 Immich 或 Synology 导入', + 'system_notice.v3_journey.highlight_share': '公开分享 — 无需登录', + 'system_notice.v3_journey.highlight_export': '导出为 PDF 相册书', + 'system_notice.v3_features.title': '3.0 版更多亮点', + 'system_notice.v3_features.body': '此版本还有一些其他值得了解的新功能。', + 'system_notice.v3_features.highlight_dashboard': '移动优先仪表板重设计', + 'system_notice.v3_features.highlight_offline': '作为 PWA 的完整离线模式', + 'system_notice.v3_features.highlight_search': '地点搜索实时自动补全', + 'system_notice.v3_features.highlight_import': '从 KMZ/KML 文件导入地点', + 'system_notice.v3_mcp.title': 'MCP:OAuth 2.1 升级', + 'system_notice.v3_mcp.body': + 'MCP 集成已全面重构。OAuth 2.1 现为推荐的身份验证方式。静态令牌(trek_…)已弃用,将在未来版本中移除。', + 'system_notice.v3_mcp.highlight_oauth': 'OAuth 2.1 推荐(mcp-remote)', + 'system_notice.v3_mcp.highlight_scopes': '24 个细粒度权限范围', + 'system_notice.v3_mcp.highlight_deprecated': '静态 trek_ 令牌已弃用', + 'system_notice.v3_mcp.highlight_tools': '扩展工具集与提示词', + 'system_notice.v3_thankyou.title': '来自我的一封私人信', + 'system_notice.v3_thankyou.body': + '在你继续之前——我想停下来说几句。\n\nTREK 最初只是我为自己的旅行而做的一个业余项目。我从未想过它会成长为 4,000 人信赖的冒险规划工具。每一颗星标、每一个 issue、每一个功能请求——我都会读,它们在全职工作和大学学业之间的深夜里支撑着我继续前行。\n\n我想让你们知道:TREK 将永远开源,永远可自托管,永远属于你们。没有追踪,没有订阅,没有任何附加条件。只是一个热爱旅行的人为同样热爱旅行的你们打造的工具。\n\n特别感谢 [jubnl](https://github.com/jubnl)——你已经成为一位不可思议的合作者。3.0 版本中许多精彩之处都留下了你的印记。感谢你在这个项目还很粗糙的时候就选择了相信它。\n\n也感谢你们每一位——报告了 bug、翻译了文本、向朋友分享了 TREK,或者只是用它规划了一次旅行——**谢谢你们**。你们是这一切存在的原因。\n\n愿我们一起踏上更多的冒险旅程。\n\n— Maurice\n\n---\n\n[加入 Discord 社区](https://discord.gg/7Q6M6jDwzf)\n\n如果 TREK 让你的旅行更美好,一杯[小小的咖啡](https://ko-fi.com/mauriceboe)能让这盏灯一直亮着。', + 'system_notice.v3014_whitespace_collision.title': '需要操作:用户账户冲突', + 'system_notice.v3014_whitespace_collision.body': + '3.0.14 版本升级检测到一个或多个由存储账户中首尾空白字符引发的用户名或邮箱冲突。受影响的账户已自动重命名。请检查服务器日志中以 **[migration] WHITESPACE COLLISION** 开头的行,以确认哪些账户需要审查。', +}; +export default system_notice; diff --git a/shared/src/i18n/zh/todo.ts b/shared/src/i18n/zh/todo.ts new file mode 100644 index 00000000..87a024f3 --- /dev/null +++ b/shared/src/i18n/zh/todo.ts @@ -0,0 +1,40 @@ +import type { TranslationStrings } from '../types'; + +const todo: TranslationStrings = { + 'todo.subtab.packing': '行李清单', + 'todo.subtab.todo': '待办事项', + 'todo.completed': '已完成', + 'todo.filter.all': '全部', + 'todo.filter.open': '进行中', + 'todo.filter.done': '已完成', + 'todo.uncategorized': '未分类', + 'todo.namePlaceholder': '任务名称', + 'todo.descriptionPlaceholder': '描述(可选)', + 'todo.unassigned': '未分配', + 'todo.noCategory': '无分类', + 'todo.hasDescription': '有描述', + 'todo.addItem': '新建任务', + 'todo.sidebar.sortBy': '排序方式', + 'todo.priority': '优先级', + 'todo.newCategoryLabel': '新建', + 'todo.newCategory': '分类名称', + 'todo.addCategory': '添加分类', + 'todo.newItem': '新任务', + 'todo.empty': '暂无任务,添加一个任务开始吧!', + 'todo.filter.my': '我的任务', + 'todo.filter.overdue': '已逾期', + 'todo.sidebar.tasks': '任务', + 'todo.sidebar.categories': '分类', + 'todo.detail.title': '任务', + 'todo.detail.description': '描述', + 'todo.detail.category': '分类', + 'todo.detail.dueDate': '截止日期', + 'todo.detail.assignedTo': '分配给', + 'todo.detail.delete': '删除', + 'todo.detail.save': '保存更改', + 'todo.detail.create': '创建任务', + 'todo.detail.priority': '优先级', + 'todo.detail.noPriority': '无', + 'todo.sortByPrio': '优先级', +}; +export default todo; diff --git a/shared/src/i18n/zh/transport.ts b/shared/src/i18n/zh/transport.ts new file mode 100644 index 00000000..886b68f5 --- /dev/null +++ b/shared/src/i18n/zh/transport.ts @@ -0,0 +1,10 @@ +import type { TranslationStrings } from '../types'; + +const transport: TranslationStrings = { + 'transport.addTransport': '添加交通', + 'transport.modalTitle.create': '添加交通', + 'transport.modalTitle.edit': '编辑交通', + 'transport.title': '交通', + 'transport.addManual': '手动添加交通', +}; +export default transport; diff --git a/shared/src/i18n/zh/trip.ts b/shared/src/i18n/zh/trip.ts new file mode 100644 index 00000000..1a2eee7d --- /dev/null +++ b/shared/src/i18n/zh/trip.ts @@ -0,0 +1,31 @@ +import type { TranslationStrings } from '../types'; + +const trip: TranslationStrings = { + 'trip.tabs.plan': '计划', + 'trip.tabs.transports': '交通', + 'trip.tabs.reservations': '预订', + 'trip.tabs.reservationsShort': '预订', + 'trip.tabs.packing': '行李清单', + 'trip.tabs.packingShort': '行李', + 'trip.tabs.lists': '列表', + 'trip.tabs.listsShort': '列表', + 'trip.tabs.budget': '预算', + 'trip.tabs.files': '文件', + 'trip.loading': '加载旅行中...', + 'trip.loadingPhotos': '正在加载地点照片...', + 'trip.mobilePlan': '计划', + 'trip.mobilePlaces': '地点', + 'trip.toast.placeUpdated': '地点已更新', + 'trip.toast.placeAdded': '地点已添加', + 'trip.toast.placeDeleted': '地点已删除', + 'trip.toast.selectDay': '请先选择一天', + 'trip.toast.assignedToDay': '地点已分配到当天', + 'trip.toast.reorderError': '排序失败', + 'trip.toast.reservationUpdated': '预订已更新', + 'trip.toast.reservationAdded': '预订已添加', + 'trip.toast.deleted': '已删除', + 'trip.confirm.deletePlace': '确定要删除这个地点吗?', + 'trip.confirm.deletePlaces': '删除 {count} 个地点?', + 'trip.toast.placesDeleted': '已删除 {count} 个地点', +}; +export default trip; diff --git a/shared/src/i18n/zh/trips.ts b/shared/src/i18n/zh/trips.ts new file mode 100644 index 00000000..bf13ecef --- /dev/null +++ b/shared/src/i18n/zh/trips.ts @@ -0,0 +1,17 @@ +import type { TranslationStrings } from '../types'; + +const trips: TranslationStrings = { + 'trips.memberRemoved': '{username} 已移除', + 'trips.memberRemoveError': '移除失败', + 'trips.memberAdded': '{username} 已添加', + 'trips.memberAddError': '添加失败', + 'trips.reminder': '提醒', + 'trips.reminderNone': '无', + 'trips.reminderDay': '天', + 'trips.reminderDays': '天', + 'trips.reminderCustom': '自定义', + 'trips.reminderDaysBefore': '天前提醒', + 'trips.reminderDisabledHint': + '旅行提醒已禁用。请在管理 > 设置 > 通知中启用。', +}; +export default trips; diff --git a/shared/src/i18n/zh/undo.ts b/shared/src/i18n/zh/undo.ts new file mode 100644 index 00000000..42de238d --- /dev/null +++ b/shared/src/i18n/zh/undo.ts @@ -0,0 +1,21 @@ +import type { TranslationStrings } from '../types'; + +const undo: TranslationStrings = { + 'undo.button': '撤销', + 'undo.tooltip': '撤销:{action}', + 'undo.assignPlace': '地点已分配至某天', + 'undo.removeAssignment': '地点已从某天移除', + 'undo.reorder': '地点已重新排序', + 'undo.optimize': '路线已优化', + 'undo.deletePlace': '地点已删除', + 'undo.deletePlaces': '地点已删除', + 'undo.moveDay': '地点已移至另一天', + 'undo.lock': '地点锁定已切换', + 'undo.importGpx': 'GPX 导入', + 'undo.importKeyholeMarkup': 'KMZ/KML 导入', + 'undo.importGoogleList': 'Google 地图导入', + 'undo.importNaverList': 'Naver 地图导入', + 'undo.addPlace': '地点已添加', + 'undo.done': '已撤销:{action}', +}; +export default undo; diff --git a/shared/src/i18n/zh/vacay.ts b/shared/src/i18n/zh/vacay.ts new file mode 100644 index 00000000..804927f2 --- /dev/null +++ b/shared/src/i18n/zh/vacay.ts @@ -0,0 +1,93 @@ +import type { TranslationStrings } from '../types'; + +const vacay: TranslationStrings = { + 'vacay.subtitle': '规划和管理假期', + 'vacay.settings': '设置', + 'vacay.year': '年份', + 'vacay.addYear': '添加下一年', + 'vacay.addPrevYear': '添加上一年', + 'vacay.removeYear': '移除年份', + 'vacay.removeYearConfirm': '移除 {year}?', + 'vacay.removeYearHint': '该年度所有假期记录和公司假日将被永久删除。', + 'vacay.remove': '移除', + 'vacay.persons': '成员', + 'vacay.noPersons': '暂无成员', + 'vacay.addPerson': '添加成员', + 'vacay.editPerson': '编辑成员', + 'vacay.removePerson': '移除成员', + 'vacay.removePersonConfirm': '移除 {name}?', + 'vacay.removePersonHint': '该成员的所有假期记录将被永久删除。', + 'vacay.personName': '姓名', + 'vacay.personNamePlaceholder': '输入姓名', + 'vacay.color': '颜色', + 'vacay.add': '添加', + 'vacay.legend': '图例', + 'vacay.publicHoliday': '公共假日', + 'vacay.companyHoliday': '公司假日', + 'vacay.weekend': '周末', + 'vacay.modeVacation': '休假', + 'vacay.modeCompany': '公司假日', + 'vacay.entitlement': '年假额度', + 'vacay.entitlementDays': '天', + 'vacay.used': '已用', + 'vacay.remaining': '剩余', + 'vacay.carriedOver': '从 {year} 结转', + 'vacay.blockWeekends': '锁定周末', + 'vacay.blockWeekendsHint': '禁止在周六和周日安排假期', + 'vacay.weekendDays': '周末', + 'vacay.mon': '周一', + 'vacay.tue': '周二', + 'vacay.wed': '周三', + 'vacay.thu': '周四', + 'vacay.fri': '周五', + 'vacay.sat': '周六', + 'vacay.sun': '周日', + 'vacay.publicHolidays': '公共假日', + 'vacay.publicHolidaysHint': '在日历中标记公共假日', + 'vacay.selectCountry': '选择国家', + 'vacay.selectRegion': '选择地区(可选)', + 'vacay.companyHolidays': '公司假日', + 'vacay.companyHolidaysHint': '允许标记公司统一休假日', + 'vacay.companyHolidaysNoDeduct': '公司假日不计入年假天数。', + 'vacay.weekStart': '每周开始于', + 'vacay.weekStartHint': '选择日历周从周一还是周日开始', + 'vacay.carryOver': '结转', + 'vacay.carryOverHint': '自动将剩余年假天数结转到下一年', + 'vacay.sharing': '共享', + 'vacay.sharingHint': '与其他 TREK 用户共享你的假期计划', + 'vacay.owner': '所有者', + 'vacay.shareEmailPlaceholder': 'TREK 用户邮箱', + 'vacay.shareSuccess': '计划共享成功', + 'vacay.shareError': '无法共享计划', + 'vacay.dissolve': '解除合并', + 'vacay.dissolveHint': '重新分离日历。你的记录将被保留。', + 'vacay.dissolveAction': '解除', + 'vacay.dissolved': '日历已分离', + 'vacay.fusedWith': '已合并', + 'vacay.you': '你', + 'vacay.noData': '暂无数据', + 'vacay.changeColor': '更改颜色', + 'vacay.inviteUser': '邀请用户', + 'vacay.inviteHint': '邀请其他 TREK 用户共享合并的假期日历。', + 'vacay.selectUser': '选择用户', + 'vacay.sendInvite': '发送邀请', + 'vacay.inviteSent': '邀请已发送', + 'vacay.inviteError': '无法发送邀请', + 'vacay.pending': '待处理', + 'vacay.noUsersAvailable': '没有可用用户', + 'vacay.accept': '接受', + 'vacay.decline': '拒绝', + 'vacay.acceptFusion': '接受并合并', + 'vacay.inviteTitle': '合并请求', + 'vacay.inviteWantsToFuse': '想要与你共享假期日历。', + 'vacay.fuseInfo1': '你们双方将在一个共享日历中看到所有假期记录。', + 'vacay.fuseInfo2': '双方都可以为对方创建和编辑记录。', + 'vacay.fuseInfo3': '双方都可以删除记录和修改年假额度。', + 'vacay.fuseInfo4': '公共假日和公司假日等设置将共享。', + 'vacay.fuseInfo5': '任何一方都可以随时解除合并。你的记录将被保留。', + 'vacay.addCalendar': '添加日历', + 'vacay.calendarColor': '颜色', + 'vacay.calendarLabel': '标签', + 'vacay.noCalendars': '无日历', +}; +export default vacay; diff --git a/shared/src/index.ts b/shared/src/index.ts index 9769fe55..f439e81d 100644 --- a/shared/src/index.ts +++ b/shared/src/index.ts @@ -13,3 +13,6 @@ export * from './common/pagination.schema'; // Domain contracts export * from './weather/weather.schema'; + +// i18n registry (language list + pure helpers — no locale data) +export * from './i18n/languages'; diff --git a/shared/src/weather/weather.schema.spec.ts b/shared/src/weather/weather.schema.spec.ts index 77aad873..60b4ba25 100644 --- a/shared/src/weather/weather.schema.spec.ts +++ b/shared/src/weather/weather.schema.spec.ts @@ -1,10 +1,11 @@ -import { describe, it, expect } from 'vitest'; import { weatherQuerySchema, detailedWeatherQuerySchema, weatherResultSchema, } from './weather.schema'; +import { describe, it, expect } from 'vitest'; + describe('weatherQuerySchema', () => { it('accepts lat/lng and defaults lang to "de"', () => { const parsed = weatherQuerySchema.parse({ lat: '52.5', lng: '13.4' }); @@ -12,41 +13,80 @@ describe('weatherQuerySchema', () => { }); it('keeps an explicit lang and optional date', () => { - const parsed = weatherQuerySchema.parse({ lat: '1', lng: '2', date: '2026-07-01', lang: 'en' }); + const parsed = weatherQuerySchema.parse({ + lat: '1', + lng: '2', + date: '2026-07-01', + lang: 'en', + }); expect(parsed.lang).toBe('en'); expect(parsed.date).toBe('2026-07-01'); }); it('rejects missing lat/lng', () => { expect(weatherQuerySchema.safeParse({ lng: '13.4' }).success).toBe(false); - expect(weatherQuerySchema.safeParse({ lat: '', lng: '13.4' }).success).toBe(false); + expect(weatherQuerySchema.safeParse({ lat: '', lng: '13.4' }).success).toBe( + false, + ); }); }); describe('detailedWeatherQuerySchema', () => { it('requires a date', () => { - expect(detailedWeatherQuerySchema.safeParse({ lat: '1', lng: '2' }).success).toBe(false); - expect(detailedWeatherQuerySchema.safeParse({ lat: '1', lng: '2', date: '2026-07-01' }).success).toBe(true); + expect( + detailedWeatherQuerySchema.safeParse({ lat: '1', lng: '2' }).success, + ).toBe(false); + expect( + detailedWeatherQuerySchema.safeParse({ + lat: '1', + lng: '2', + date: '2026-07-01', + }).success, + ).toBe(true); }); }); describe('weatherResultSchema', () => { it('accepts a minimal current-weather result', () => { - const r = weatherResultSchema.parse({ temp: 21, main: 'Clear', description: 'Klar', type: 'current' }); + const r = weatherResultSchema.parse({ + temp: 21, + main: 'Clear', + description: 'Klar', + type: 'current', + }); expect(r.temp).toBe(21); }); it('accepts a detailed result with hourly entries and a no_forecast error', () => { expect( weatherResultSchema.safeParse({ - temp: 0, main: '', description: '', type: '', error: 'no_forecast', + temp: 0, + main: '', + description: '', + type: '', + error: 'no_forecast', }).success, ).toBe(true); expect( weatherResultSchema.safeParse({ - temp: 18, main: 'Rain', description: 'Regen', type: 'forecast', - sunrise: '05:30', sunset: '21:10', precipitation_sum: 2.4, - hourly: [{ hour: 9, temp: 17, precipitation: 0.1, precipitation_probability: 20, main: 'Clouds', wind: 12, humidity: 80 }], + temp: 18, + main: 'Rain', + description: 'Regen', + type: 'forecast', + sunrise: '05:30', + sunset: '21:10', + precipitation_sum: 2.4, + hourly: [ + { + hour: 9, + temp: 17, + precipitation: 0.1, + precipitation_probability: 20, + main: 'Clouds', + wind: 12, + humidity: 80, + }, + ], }).success, ).toBe(true); }); diff --git a/shared/tsup.config.ts b/shared/tsup.config.ts index 2f020b04..670ccde4 100644 --- a/shared/tsup.config.ts +++ b/shared/tsup.config.ts @@ -1,7 +1,8 @@ import { defineConfig } from 'tsup' export default defineConfig({ - entry: ['src/index.ts'], + // Root barrel + i18n metadata barrel + one entry per locale (lazy-load chunks) + entry: ['src/index.ts', 'src/i18n/index.ts', 'src/i18n/*/index.ts'], format: ['cjs', 'esm'], dts: true, clean: true,