mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
d152f9d02b
* fix(shared-view): render each leg of multi-leg flights correctly The read-only shared view showed the overall trip start/end airports and the first leg's flight number on every leg of a multi-leg flight. The Day Plan already expands legs (each carries __leg), but the renderer ignored it and read flat top-level metadata; the Bookings tab had the same bug. - Day Plan: use __leg for per-leg airline/flight number/route, plus dep-arr time - Bookings tab: list each leg via getFlightLegs() - unique React keys for multi-leg rows Closes #1219 * feat(pdf): add legs to pdf export * fix(demo): skip first-run admin seed in demo mode When DEMO_MODE is on, the demo seeder creates its own admin (admin@trek.app, username "admin") right after the generic seeds run. The first-run admin bootstrap was grabbing username "admin" first, so the demo seeder hit the UNIQUE(username) constraint and aborted before the demo user was ever created - which surfaced as a 500 "Demo user not found" on demo-login. Skip the generic admin bootstrap when demo mode owns the admin account. * fix(docker): ship the encryption-key migration script in the image The production image only copied server/dist, so the documented rotation command `node --import tsx scripts/migrate-encryption.ts` failed inside the container with a module-not-found error - the raw .ts was never present. The script runs via tsx straight from source and only pulls node builtins plus better-sqlite3 (both prod deps), so copying the single file into /app/server/scripts is enough to make the rotation work again. * fix(vacay): keep the mode toolbar above the mobile bottom nav The floating Vacation/Company toolbar was pinned at bottom-3 with z-30, so on mobile it landed in the same band as the fixed bottom nav (z-60) and got hidden behind it - and could scroll out of reach entirely. Pin it above the nav with the shared --bottom-nav-h variable (0px on desktop, so nothing changes there) and reserve matching space below the calendar grid so it never gets swallowed. * fix(dashboard): show the correct reservation date regardless of timezone The upcoming-reservations widget built the date with new Date(reservation_time) .toISOString(), which reinterprets the stored naive local time as UTC and can roll the displayed day forward in non-UTC timezones (e.g. a 23:30 reservation showing the next day). Read the date and time straight from the stored string parts via splitReservationDateTime, and format the time with the shared formatTime helper so it also honours the user's 12h/24h preference. * fix(atlas): cursor-following tooltips and removing countries from search Two related Atlas fixes: - Country tooltips were bound with sticky:false, which anchors them at the feature's bounds centre. For countries with overseas territories (e.g. France) that centre sits far out in the ocean, so the tooltip popped up nowhere near the area being hovered. Make them sticky so they track the cursor. - Selecting an already-visited country from the search bar always opened the "Mark / Bucket" dialog, with no way to remove it. Tiny countries like Vatican City or Singapore are hard to hit on the map, so search was the only way in. Mirror the map-click behaviour: a manually-marked country opens the Remove confirmation, a trip/place-backed one opens its detail. * fix(oidc): keep dots in generated usernames The OIDC username sanitizer stripped dots because they were missing from the allowed character class, so a name claim like "first.last" became "firstlast". Dots are valid usernames (the profile validator already allows ^[a-zA-Z0-9_.-]+$), so add the dot to the sanitizer. * fix(collab): show poll option labels in the UI The poll API formatted each option as { label, voters }, but the React poll component renders opt.text - so every option button came out blank. Emit text alongside label (kept for any other consumer) so options render again. * feat(backup): make the upload size limit configurable The restore upload was capped at a hard-coded 500 MB, so instances whose backup archive (uploads/ included) grew past that got a 413 "File too large" with no way to raise it. Add a BACKUP_UPLOAD_LIMIT_MB env var (default 500, invalid values warn and fall back), documented in .env.example. * feat(costs): create an expense from a booking, fix editing total-only items Replace the inline price + budget-category fields in the Transport and Reservation booking modals with a "Create expense" flow: the modal saves the booking, then opens the full Costs editor prefilled (name + category mapped from the booking type) and linked to the reservation. A booking with a linked expense shows it inline with edit / remove. Also fix the Costs editor so an expense with a recorded total but no payers (transport-derived or pre-rework items) opens with its amount, lets you set the currency, and saves - it previously showed 0 everywhere and could not be saved. Legacy / localized categories now map to the fixed keys, and changing a booking's type keeps its linked expense category in sync (unless it was manually set). - shared: reservation_id on budget create, typeToCostCategory helper, i18n keys - server: createBudgetItem stores reservation_id; keep total_price for payerless items; a booking update no longer wipes its linked expense and syncs the category on type change - client: shared BookingCostsSection, exported ExpenseModal with prefill and an editable total, page-level save-then-open wiring * test(reservations): align syncBudgetOnUpdate unit tests with no-wipe + type-sync The service now leaves a linked expense alone when no budget entry is on the payload (only an explicit total_price 0 deletes it) and syncs the category on a booking type change. Update the unit tests accordingly - the old "price cleared" case passed entry: undefined, which is now a no-op and left a mocked return queued that leaked into the next test. * fix(planner): keep a reservation on its day when edited (#1237) Editing a booking forced its day_id to the globally selected day, which is null when editing from the Book tab - so the booking lost its day and vanished from the Plan. Preserve the reservation own day_id on edit instead. * fix(planner): derive a booking day from its date when none is set (#1237) The client always sends day_id on a reservation update, so the server only derived it from reservation_time when the field was absent. A non-transport booking saved without a selected day (Book tab) therefore got day_id null and vanished from the Plan, even though its date matched a day. Derive the day from reservation_time whenever day_id is null, mirroring create. * fix(planner): let a booking's day follow its date when edited (#1237) Preserving the old day_id on edit left a re-dated booking on its previous start day while end_day_id followed the new date, so it spanned both. Stop sending day_id from the edit modal entirely - the server derives both ends from the booking's date (and keeps the current day when there is no date), so a re-dated booking moves cleanly to the matching day. * fix(atlas): keep the continent breakdown in sync on mark/unmark (#1225) The optimistic mark/unmark updates bumped the country total but never the per-continent counts, so the continent column froze until a full reload. Move the country to continent map into @trek/shared (single source for server and client) and adjust the matching continent count at every optimistic site: the country confirm flow plus the choose / region mark and region unmark handlers. * feat(admin): let admins set a default currency for new users Adds a currency picker to Admin > User Defaults. Stored as the default_currency user-default, so users who have not picked their own currency inherit it in Costs. * fix(atlas): give every sub-national region a distinct code (#1217) geoBoundaries fills shapeISO with the bare country code for some countries (every Spanish region got "ESP", every Chinese "CHN", also Chile/Oman), so marking one region lit up the whole country. build-atlas-geo.mjs now keeps shapeISO only when it is a real "XX-..." subdivision code and otherwise synthesizes a unique per-country id from the region name. Regenerated admin1.geojson.gz: Spain/China/ Chile/Oman now carry distinct region codes (countries with real codes, e.g. Germany, are unchanged). * fix(dashboard): never crash on a malformed reservation date A reservation with an invalid date blanked the whole My Trips page: the old Upcoming widget did new Date(value).toISOString(), which throws "Invalid time value" (fixed in #1222 by reading the string parts). Also guard splitDate so a bad date renders a dash instead of "Invalid Date" or throwing. * fix(airtrail): gate airtrail update behind a user setting, on airtrail update: rebuild payload from fresh data to prevent any data loss * fix(airtrail): add back missing tests * fix(costs): rework the cost panel UX wise and apply prettier on the shared package * chore(prettier) prettier this file * fix(airtrail): don't use cabin class as seat on import When an AirTrail flight has a cabin class but no seat number, the mapper fell back to the class for metadata.seat, so reservations showed e.g. "economy" as the seat. Use only the seat number; leave the seat blank otherwise. The class is still surfaced separately in the import picker. Closes #1246 * fix(airtrail): import scheduled flight times instead of actual AirTrail exposes both scheduled (departureScheduled/arrivalScheduled) and actual (departure/arrival) times. TREK read the actual times, so a delayed or early flight imported the wrong time for planning. Read the scheduled times on import and on poll-sync (both go through mapFlightToReservation); when a flight has no scheduled time, leave the clock blank (date preserved) rather than fabricating 00:00 or falling back to actual. The change-detection hash now tracks the scheduled values, so existing linked reservations re-sync once on the next poll. The opt-in writeback mirrors the read, pushing TREK edits to the scheduled fields so they round-trip. * fix(planner): hydrate per-assignment times when editing a place from the pool Times live per day-assignment, not on the pool place, so reopening a place from the Places panel / inspector showed empty Start/End fields (#1247). The editor now resolves a place's lone assignment when no day is in context and hydrates the fields from it; ambiguous (0 or 2+ days) edits hide the fields instead of showing non-persisting inputs. * fix(mcp): make write tools return client-valid, hydrated entities Audit of all write tools under server/src/mcp/tools (issue #1244 anchor). S1 (broken): - create_budget_item / create_budget_item_with_members now default the split to all trip members when member_ids omitted, so the entry passes the client save-gate instead of being member-less (#1244). - create_transport / update_transport backfill lat/lng/timezone for code-only flight endpoints (NOT NULL columns) and return a clean error for unresolvable endpoints instead of crashing. S2 (under-hydration): set_budget_item_members, create_journey, create_journey_entry, create_packing_bag, bulk_import_packing and update_vacay_plan now return the hydrated shape the matching read/REST route returns; bulk_import widened to accept bag/weight_grams/checked. S3 (parity): check_in_end added to accommodation tools; atlas mark_region_visited echoes the client shape; update_journey_entry/ update_journey_preferences, set_bag_members, set_packing_category_assignees, apply_packing_template return hydrated payloads; set_vacay_color echoes the color. Auth: save_packing_template now requires admin, matching the REST gate. Also refactors server/src/config.ts (JWT-secret handling). Adds getBudgetItem hydrated getter, exports EndpointInput, and MCP regression tests (incl. new tools-transports and tools-journey suites). * fix(mcp): fix ICS/maps/accommodation bugs, add settlement & template tools Bugs: - export_trip_ics: include flights that store times per-endpoint (local_date/local_time) instead of a top-level reservation_time - resolve_maps_url: follow redirects for cid=/share links and fall back to parsing the page body, all SSRF-guarded - link_hotel_accommodation: normalize accommodation_id (TEXT column) to an integer in the reservation read paths so it no longer returns "14.0" Gaps: - packing: save_packing_template returns the new template id; add list_packing_templates (read) and delete_packing_template (admin) - budget: update_budget_item accepts payers/member_ids; clarify create/ update/members descriptions to ask which members share the expense and who paid - budget: add settlement tools — get_settlement_summary, list_settlements, create/update/delete_settlement (budget_edit, mirrors REST + WS events) * chore: bump nodemailer * chore: bump multer --------- Co-authored-by: Maurice <mauriceboe@icloud.com>
343 lines
23 KiB
TypeScript
343 lines
23 KiB
TypeScript
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': '체크인, 사진, 일별 이야기가 있는 여행 기록 및 여행 일지',
|
|
'admin.passkey.title': '패스키 로그인',
|
|
'admin.passkey.cardHint': '사용자가 패스키(WebAuthn)로 로그인할 수 있게 합니다. 기본값은 꺼짐입니다.',
|
|
'admin.passkey.login': '패스키 로그인 활성화',
|
|
'admin.passkey.loginHint': '"패스키로 로그인" 옵션을 표시하고 사용자가 설정에서 패스키를 등록할 수 있게 합니다.',
|
|
'admin.passkey.notConfigured':
|
|
'이 배포에 아직 WebAuthn 도메인이 확인되지 않습니다. 아래에서 APP_URL 또는 Relying Party ID를 설정하세요 — 그 전까지 패스키는 숨겨진 상태로 유지됩니다.',
|
|
'admin.passkey.rpId': 'Relying Party ID (도메인)',
|
|
'admin.passkey.rpIdHint':
|
|
'패스키가 바인딩되는 순수 도메인입니다. 예: trek.example.org. 비워두면 APP_URL에서 자동으로 가져옵니다. 나중에 변경하면 기존 패스키가 무효화됩니다.',
|
|
'admin.passkey.origins': '허용된 오리진',
|
|
'admin.passkey.originsHint':
|
|
'쉼표로 구분된 전체 오리진입니다. 예: https://trek.example.org. 비워두면 APP_URL을 사용합니다.',
|
|
'admin.passkey.reset': '패스키 초기화',
|
|
'admin.passkey.resetHint':
|
|
'이 사용자의 모든 패스키를 삭제합니다 (예: 기기 분실 시). 사용자는 비밀번호로 계속 로그인할 수 있습니다.',
|
|
'admin.passkey.resetConfirm': '{name}의 모든 패스키를 삭제할까요?',
|
|
'admin.passkey.resetDone': '패스키 {count}개를 삭제했습니다',
|
|
'admin.defaultSettings.mapProvider': '지도 엔진',
|
|
'admin.defaultSettings.mapProviderHint':
|
|
'이 인스턴스의 모든 사용자에게 적용되는 기본 지도입니다. 각 사용자는 자신의 설정에서 이를 변경할 수 있습니다.',
|
|
'admin.defaultSettings.providerLeaflet': '표준 (무료)',
|
|
'admin.defaultSettings.providerMapbox': 'Mapbox (3D)',
|
|
'admin.defaultSettings.mapboxToken': '공유 Mapbox 토큰',
|
|
'admin.defaultSettings.mapboxTokenHint':
|
|
'자신의 토큰을 입력하지 않은 모든 사용자에게 사용됩니다 — 키를 개별적으로 공유하지 않아도 인스턴스 전체에서 Mapbox를 사용할 수 있습니다. 암호화하여 저장됩니다.',
|
|
'admin.defaultSettings.mapboxStyle': '지도 스타일',
|
|
'admin.defaultSettings.mapboxStylePlaceholder': '스타일을 선택하세요…',
|
|
'admin.defaultSettings.mapbox3d': '3D 건물 & 지형',
|
|
'admin.defaultSettings.mapboxQuality': '고품질 모드',
|
|
};
|
|
export default admin;
|