Bugs fixed: - FIT parser returns timestamps as Date objects, not strings — convert to ISO 8601 before passing to generateGpx (caused str.replace crash) - FIT parser already converts semicircles to degrees — remove redundant conversion that produced near-zero coordinates - Wahoo CDN URLs are pre-signed S3 URLs — remove Bearer auth header from download requests (caused 400 "Unsupported Authorization Type") - Filter out third-party workouts (fitness_app_id >= 1000) since Wahoo does not share their data via the API UX improvements: - Individual imports use fetcher (no page refresh), button shows "Importing..." inline - "Import all" button imports all unimported workouts on the page sequentially with progress indicator - Add @vitejs/plugin-basic-ssl for local HTTPS dev (opt-in via HTTPS=1) Also adds unit test for FIT-to-GPX conversion with real fixture file, and updates wahoo-import spec to reflect all behaviors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
228 lines
8.2 KiB
TypeScript
228 lines
8.2 KiB
TypeScript
export default {
|
|
common: {
|
|
save: "Save",
|
|
cancel: "Cancel",
|
|
delete: "Delete",
|
|
edit: "Edit",
|
|
close: "Close",
|
|
loading: "Loading...",
|
|
error: "Something went wrong",
|
|
goHome: "Go home",
|
|
pageNotFound: "Page not found",
|
|
serviceUnavailable: "Service temporarily unavailable. Please try again later.",
|
|
},
|
|
planner: {
|
|
title: "trails.cool Planner",
|
|
subtitle: "Collaborative route planning",
|
|
home: "Home",
|
|
newSession: "New Session",
|
|
saveRoute: "Save Route",
|
|
exportGpx: "Export GPX",
|
|
exportRoute: "Export Route",
|
|
exportRouteDesc: "Clean GPX track for any app",
|
|
exportPlan: "Export Plan",
|
|
exportPlanDesc: "Includes waypoints and no-go areas",
|
|
"undo.tooltip": "Undo (Ctrl+Z)",
|
|
"redo.tooltip": "Redo (Ctrl+Shift+Z)",
|
|
importGpx: "Import GPX",
|
|
importGpxError: "Could not read GPX file. Please check the file format.",
|
|
dropGpxHere: "Drop GPX file here",
|
|
replaceRouteConfirm: "Replace current route with imported GPX?",
|
|
profile: "Profile",
|
|
connecting: "Connecting...",
|
|
loadingMap: "Loading map...",
|
|
computingRoute: "Computing route...",
|
|
routeError: "Route calculation failed. Please try again.",
|
|
noRouteFound: "No route found. Try moving the waypoint to a road or path.",
|
|
host: "Host",
|
|
connected: "Connected",
|
|
saveToJournal: "Save to Journal",
|
|
saving: "Saving...",
|
|
saved: "Saved!",
|
|
returnToJournal: "Return to Journal",
|
|
profiles: {
|
|
trekking: "Hiking",
|
|
fastbike: "Cycling (fast)",
|
|
safety: "Cycling (safe)",
|
|
shortest: "Shortest",
|
|
car: "Car",
|
|
},
|
|
participants: {
|
|
you: "(you)",
|
|
host: "Host",
|
|
editName: "Click to edit your name",
|
|
joined: "{{name}} joined",
|
|
left: "{{name}} left",
|
|
},
|
|
sidebar: {
|
|
waypoints: "Waypoints",
|
|
notes: "Notes",
|
|
},
|
|
noGoAreas: {
|
|
draw: "Draw no-go area",
|
|
cancel: "Cancel no-go area",
|
|
hint: "Right-click a no-go area to delete it",
|
|
},
|
|
notes: {
|
|
placeholder: "Add notes for this session...",
|
|
},
|
|
colorMode: {
|
|
label: "Route Color",
|
|
plain: "Plain",
|
|
elevation: "Elevation",
|
|
surface: "Surface",
|
|
surfaceUnavailable: "Surface data not available for this profile",
|
|
},
|
|
rateLimitExceeded: "Too many requests. Please try again later.",
|
|
elevation: {
|
|
gain: "Elevation Gain",
|
|
loss: "Elevation Loss",
|
|
profile: "Elevation Profile",
|
|
},
|
|
landing: {
|
|
startPlanning: "Start Planning",
|
|
heroDescription: "Plan hiking and cycling routes together in real-time. No account needed.",
|
|
featureCollaborative: "Real-Time Collaboration",
|
|
featureCollaborativeDesc: "Edit waypoints together — changes sync instantly across all participants.",
|
|
featureRouting: "Smart Routing",
|
|
featureRoutingDesc: "BRouter computes optimal routes for hiking, cycling, or driving with elevation awareness.",
|
|
featureElevation: "Elevation Profile",
|
|
featureElevationDesc: "See the full elevation profile of your route with gain and loss statistics.",
|
|
featureExport: "GPX Export",
|
|
featureExportDesc: "Export your route as GPX for use in any GPS device or app.",
|
|
featureNoAccount: "No Account Needed",
|
|
featureNoAccountDesc: "Start planning immediately. Share a link and anyone can join your session.",
|
|
saveRoutes: "Want to save your routes?",
|
|
saveRoutesDesc: "Create a free account on trails.cool to keep your routes, track activities, and more.",
|
|
goToJournal: "Go to trails.cool",
|
|
footer: {
|
|
privacy: "Privacy",
|
|
source: "Source Code",
|
|
builtWith: "Built with BRouter and OpenStreetMap",
|
|
},
|
|
},
|
|
},
|
|
journal: {
|
|
title: "trails.cool",
|
|
subtitle: "Your outdoor activity journal",
|
|
welcome: "Welcome,",
|
|
addPasskeyPrompt: "Add a passkey for faster sign-in on this device.",
|
|
addPasskey: "Add Passkey",
|
|
settingUp: "Setting up...",
|
|
passkeyAdded: "Passkey added! You can now sign in instantly on this device.",
|
|
passkeyStatus: "{{count}} passkey registered",
|
|
passkeyStatus_other: "{{count}} passkeys registered",
|
|
routes: {
|
|
title: "Routes",
|
|
myRoutes: "My Routes",
|
|
new: "New Route",
|
|
edit: "Edit Route",
|
|
editInPlanner: "Edit in Planner",
|
|
opening: "Opening...",
|
|
importGpx: "Import GPX",
|
|
exportGpx: "Export GPX",
|
|
delete: "Delete Route",
|
|
distance: "Distance",
|
|
elevationGain: "Elevation Gain",
|
|
noRoutesYet: "No routes yet. Create your first route!",
|
|
noMapPreview: "No map preview",
|
|
saveChanges: "Save Changes",
|
|
},
|
|
activities: {
|
|
title: "Activities",
|
|
new: "New Activity",
|
|
duration: "Duration",
|
|
linkToRoute: "Link to Route",
|
|
createRouteFromActivity: "Create Route from Activity",
|
|
delete: "Delete Activity",
|
|
deleteConfirm: "Are you sure you want to delete this activity?",
|
|
importedFrom: "Imported from {{provider}}",
|
|
},
|
|
settings: {
|
|
title: "Settings",
|
|
profile: {
|
|
title: "Profile",
|
|
displayName: "Display Name",
|
|
bio: "Bio",
|
|
saved: "Profile saved.",
|
|
},
|
|
security: {
|
|
title: "Security",
|
|
description: "Manage your passkeys for signing in.",
|
|
addPasskey: "Add Passkey",
|
|
passkey: "Passkey",
|
|
thisDevice: "This device",
|
|
securityKey: "Security key",
|
|
bluetooth: "Bluetooth",
|
|
phoneOrTablet: "Phone or tablet",
|
|
addedOn: "Added {{date}}",
|
|
noPasskeys: "No passkeys registered. You can sign in with magic links.",
|
|
deleteConfirm: "Are you sure you want to delete this passkey?",
|
|
deleteLastWarning: "This is your only passkey. You will need to use magic links to sign in. Delete anyway?",
|
|
},
|
|
account: {
|
|
title: "Account",
|
|
changeEmail: "Change",
|
|
newEmailPlaceholder: "New email address",
|
|
sendVerification: "Verify",
|
|
verificationSent: "Verification email sent. Check your inbox.",
|
|
dangerZone: "Danger Zone",
|
|
deleteDescription: "Permanently delete your account and all associated data.",
|
|
deleteAccount: "Delete Account",
|
|
deleteConfirmPrompt: "Type \"{{username}}\" to confirm deletion:",
|
|
confirmDelete: "Permanently Delete",
|
|
},
|
|
services: {
|
|
title: "Connected Services",
|
|
connect: "Connect",
|
|
disconnect: "Disconnect",
|
|
connectedAs: "Connected (ID: {{id}})",
|
|
},
|
|
},
|
|
sync: {
|
|
import: "Import",
|
|
importFrom: "Import from {{provider}}",
|
|
imported: "Imported",
|
|
noWorkouts: "No workouts found.",
|
|
noGps: "No GPS",
|
|
noGpsTooltip: "{{provider}} does not provide route data for this activity",
|
|
importing: "Importing...",
|
|
importAll: "Import all ({{count}})",
|
|
importingProgress: "Importing {{current}} of {{total}}...",
|
|
previous: "Previous",
|
|
next: "Next",
|
|
},
|
|
nav: {
|
|
routes: "Routes",
|
|
activities: "Activities",
|
|
login: "Sign In",
|
|
register: "Register",
|
|
profile: "Profile",
|
|
settings: "Settings",
|
|
logout: "Log Out",
|
|
},
|
|
auth: {
|
|
login: "Sign In",
|
|
register: "Register",
|
|
logout: "Log Out",
|
|
email: "Email",
|
|
username: "Username",
|
|
signInWithPasskey: "Sign in with Passkey",
|
|
authenticating: "Authenticating...",
|
|
useMagicLink: "No passkey on this device? Use a magic link instead",
|
|
magicLinkHelp: "We'll send a login link to your email.",
|
|
sendMagicLink: "Send Magic Link",
|
|
sending: "Sending...",
|
|
backToPasskey: "Back to passkey login",
|
|
checkEmail: "Check your email! We sent a login link to",
|
|
linkExpires: "The link expires in 15 minutes.",
|
|
noAccount: "Don't have an account?",
|
|
registerDescription: "Register with a passkey — no password needed.",
|
|
registerWithPasskey: "Register with Passkey",
|
|
creatingPasskey: "Creating passkey...",
|
|
passkeyNotSupported: "Your browser does not support passkeys. Please use a different browser to register.",
|
|
passkeyNotSupportedRegister: "Your browser doesn't support passkeys. You can register with a magic link instead and add a passkey later from a supported browser.",
|
|
registerWithMagicLink: "Register with Magic Link",
|
|
},
|
|
},
|
|
} as const;
|