Files
DriverVault/Web App/web/src/i18n/en.json
T
tajniak81andClaude Opus 4.8 a1519f6e89 Add OCPP control for the Anker Solix EV charger (Own/Proxy CSMS)
The Anker Solix connector was read-only (cloud monitoring only). Add an
OCPP 1.6J control path with a per-user, cascading control mode:

  - off   monitoring only (default, unchanged behavior)
  - own   DriverVault is the charger's Central System (full control)
  - proxy DriverVault relays to Anker's cloud and injects commands

New internal/ocpp subsystem (stdlib-only, hand-rolled RFC 6455): a CSMS
with session management, inbound dispatch, and typed control commands
(RemoteStart/Stop, SetChargingProfile current limit, ChangeAvailability,
Reset, UnlockConnector, TriggerMessage, Get/ChangeConfiguration). Own- and
proxy-mode paths are verified end-to-end against a simulated charge point.

The charger connects to /ocpp/{serial}, authenticated with OCPP Basic auth
(serial + a per-charger control token) resolved to the owning user via an
in-memory token index. Control REST endpoints mirror the monitoring ones and
reuse the same cascade gate plus a live-session check. controlMode is a new
cascade field (global -> org -> user) advertised as a select on the plugin.

Frontend: control-mode select + provisioning card in Settings, and a real
Start/Stop/limit/reset control panel in Charging, gated on the active mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-18 16:40:22 +02:00

728 lines
25 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"errors": {
"sessionExpired": "Session expired — please log in again."
},
"common": {
"cancel": "Cancel",
"save": "Save",
"saveChanges": "Save changes",
"saving": "Saving…",
"saved": "Saved ✓",
"loading": "Loading…",
"edit": "Edit",
"remove": "Remove",
"delete": "Delete",
"done": "Done",
"undo": "Undo",
"download": "Download",
"empty": "—",
"yes": "Yes",
"no": "No"
},
"nav": {
"garage": "Garage",
"charging": "Charging",
"settings": "Settings",
"users": "Users",
"lightMode": "Light mode",
"darkMode": "Dark mode",
"signedIn": "Signed in",
"logOut": "Log out"
},
"charging": {
"eyebrow": "Charging & map",
"title": "Nearby chargers",
"liveMap": "Live map",
"youAreHere": "You are here",
"session": {
"chargingNow": "Charging now",
"rate": "Rate",
"added": "Added",
"cost": "Cost",
"done": "Done",
"stop": "Stop charging",
"idle": "Not charging",
"idleHint": "Plug in at a station to start a session."
},
"control": {
"title": "Charger control",
"connected": "Connected",
"disconnected": "Offline",
"serialPlaceholder": "Charger serial (e.g. A5191-XXXXXXXX)",
"refresh": "Refresh",
"status": "Connector",
"meter": "Energy",
"start": "Start charging",
"stop": "Stop charging",
"limit": "Current limit",
"applyLimit": "Apply limit",
"clearLimit": "Clear limit",
"reset": "Reset charger",
"connectHint": "Enter your charger's serial and refresh. The charger must be connected to DriverVault's OCPP backend (set up in Settings → Integrations)."
},
"stations": {
"heading": "Nearby",
"count": {
"one": "{n} station",
"other": "{n} stations"
},
"free": "{avail} of {total} free",
"full": "Full",
"offPeak": "off-peak",
"homeCharger": "Home charger"
}
},
"login": {
"title": "Sign in",
"tagline": "Your car, on track.",
"email": "Email",
"password": "Password",
"showPassword": "Show password",
"hidePassword": "Hide password",
"submit": "Sign in",
"submitting": "Signing in…",
"failed": "Login failed",
"serverSettings": "Server settings",
"apiServerUrl": "API server URL",
"leaveBlank": "Leave blank to use the default ({url}).",
"resetToDefault": "Reset to default"
},
"dashboard": {
"eyebrow": "Garage",
"title": "Your cars",
"subtitle": "Maintenance overview and service history.",
"addCar": "Add car",
"empty": "No cars yet. Click {action} to get started.",
"shared": "Shared",
"sharedReadOnly": "Shared · read-only",
"serviceLife": "Service life",
"lastService": "Last service",
"odometer": "Odometer",
"nextDue": "Next due",
"nextDueKm": "Next due km",
"serviceRecords": {
"one": "{n} service record",
"other": "{n} service records"
}
},
"admin": {
"eyebrow": "Admin",
"title": "Users",
"subtitleAll": "Accounts across every organization.",
"subtitleOrg": "Accounts in your organization.",
"subtitleOrgsNote": "Organizations are assigned in the API panel.",
"addUser": "Add user",
"colEmail": "Email",
"colName": "Name",
"colOrganization": "Organization",
"colRole": "Role",
"colCreated": "Created",
"you": "(you)",
"resetPassword": "Reset password",
"confirmDelete": "Delete {name}? This cannot be undone.",
"cantDeleteSelf": "You can't delete your own account.",
"onlySuperadminDeletes": "Only a superadmin can delete a superadmin.",
"cantChangeOwnRole": "You can't change your own role.",
"onlySuperadminEdits": "Only a superadmin can edit a superadmin.",
"createTitle": "Add a user",
"emailRequired": "Email *",
"passwordRequired": "Password *",
"minChars": "(min 8)",
"creating": "Creating…",
"createUser": "Create user",
"resetTitle": "Reset password — {email}",
"newPassword": "New password",
"setPassword": "Set password",
"roles": {
"user": "user",
"admin": "admin",
"superadmin": "superadmin"
}
},
"settings": {
"eyebrow": "Account",
"title": "Settings",
"subtitle": "Manage your account, appearance, and data.",
"account": {
"title": "Account",
"name": "Name",
"email": "Email",
"verified": "Verified",
"notVerified": "Not verified",
"resendVerification": "Resend verification email",
"sending": "Sending…",
"verificationRequested": "Verification email requested.",
"changePassword": "Change password",
"currentPassword": "Current password",
"newPassword": "New password",
"confirmNewPassword": "Confirm new password",
"mismatchYet": "Passwords don't match yet.",
"tooShort": "New password must be at least 8 characters.",
"mismatch": "New password and confirmation don't match.",
"updating": "Updating…",
"passwordUpdated": "Password updated ✓",
"updatePassword": "Update password"
},
"appearance": {
"title": "Appearance",
"theme": "Theme",
"themeLight": "light",
"themeDark": "dark",
"themeSystem": "system",
"language": "Language",
"languageHint": "App text, and the names of months and days.",
"languageFallbackHint": "This language isn't translated yet — the app text stays in English.",
"region": "Region",
"regionHint": "Number and currency layout.",
"dateFormat": "Date format",
"dateExample": "Example: {example}",
"currency": "Currency",
"currencyExample": "Example: {example} — display only, no amounts are converted.",
"fontSize": "Font size",
"fontSmall": "small",
"fontMedium": "medium",
"fontLarge": "large"
},
"profile": {
"title": "Profile",
"avatarAlt": "Avatar",
"uploading": "Uploading…",
"uploadPhoto": "Upload photo",
"bio": "Bio",
"bioPlaceholder": "A short note visible to other people in your household.",
"saveBio": "Save bio"
},
"integrations": {
"title": "Integrations",
"subtitle": "Connect external services to your account.",
"toyota": "Toyota Connected (MyToyota)",
"toyotaDesc": "Sign in with your MyToyota account to pull vehicle data from Toyota Connected Europe. Europe only.",
"enable": "Enable for my account",
"enableOrg": "Enable for the organization",
"unavailable": "This integration is turned off by the administrator.",
"orgDisabled": "This integration is turned off for your organization.",
"scopeMy": "My settings",
"scopeOrg": "Organization",
"scopeHint": "Organization settings apply to everyone in your organization who leaves a field blank.",
"readOnly": "You manage this integration's shared settings in the API Server panel; here it is read-only.",
"email": "MyToyota email",
"password": "MyToyota password",
"brand": "Brand",
"brandToyota": "Toyota",
"brandLexus": "Lexus",
"inherited": "Set above your level",
"inheritedFrom": "Inherited from {source}",
"sourceGlobal": "the administrator",
"sourceOrg": "your organization",
"sourceUser": "you",
"passwordKeep": "Leave blank to keep the saved password.",
"test": "Test connection",
"testing": "Testing…",
"save": "Save",
"saved": "Saved ✓",
"connected": "Connected",
"notConnected": "Not connected",
"ankerSolix": "Anker Solix (V1 Smart EV Charger)",
"ankerSolixDesc": "Sign in with your Anker account to pull EV charger data from the Anker Solix cloud.",
"ankerEmail": "Anker account email",
"ankerPassword": "Anker account password",
"country": "Country",
"countryHint": "Two-letter country code of your Anker account (e.g. DE, GB, US).",
"controlMode": "Control mode",
"controlModeHint": "How DriverVault controls the charger over OCPP.",
"controlOff": "Off (monitoring only)",
"controlOwn": "Own CSMS (full control)",
"controlProxy": "Proxy CSMS (relay + control)",
"controlTitle": "Charger control (OCPP)",
"controlOwnHint": "The charger connects directly to DriverVault as its Central System. Point the charger's OCPP backend at the endpoint below.",
"controlProxyHint": "DriverVault relays to Anker's cloud and can inject commands. Point the charger's OCPP backend at the endpoint below.",
"controlProvisionSteps": "In the Anker app (or the charger's OCPP settings), set the OCPP backend URL to the endpoint and the authorization key to the token above.",
"chargerSerial": "Charger serial",
"controlCheck": "Check status",
"controlGenerate": "Generate token",
"controlEndpoint": "OCPP endpoint",
"controlToken": "Auth token",
"controlConnected": "Connected to control backend",
"controlDisconnected": "Not connected"
},
"privacy": {
"title": "Privacy & security",
"signOut": "Sign out",
"body": "Two-factor authentication isn't available yet. Sessions are held as server-issued tokens that expire on their own, so signing out here ends this device's session only — there's no per-device list to revoke from. To lock out every device, change your password above."
},
"advanced": {
"title": "Advanced",
"exportTitle": "Export your data",
"exportBody": "Download your profile and all cars, service records, and parts as JSON.",
"preparing": "Preparing…",
"exportAction": "Export data",
"importTitle": "Import your data",
"importBody": "Add cars from a previously exported JSON file. This creates new records — it doesn't merge with or overwrite anything existing.",
"importing": "Importing…",
"importAction": "Import data",
"notJson": "That file isn't valid JSON.",
"notExport": "That file doesn't look like a DriverVault export (missing a \"cars\" list).",
"confirmImport": "Import {count} car(s) from this file? This adds new records — it does not merge with or overwrite existing cars.",
"imported": "Imported {cars} car(s), {services} service record(s), {parts} part(s)."
},
"danger": {
"title": "Danger zone",
"body": "Deleting your account removes your login and profile. It does not delete your household's shared cars or service history. There's a 3-day cooldown before the deletion is final, and you can cancel any time before then.",
"deleteAccount": "Delete my account",
"typeToConfirm": "Type {email} to confirm",
"requesting": "Requesting…",
"requestDeletion": "Request deletion",
"requestedOn": "Account deletion requested on {date}.",
"canStillCancel": "You can still cancel — it becomes permanent after the 3-day cooldown.",
"cooldownPassed": "The cooldown has passed. You can now finalize the deletion.",
"cancelRequest": "Cancel deletion request",
"finalize": "Permanently delete my account",
"confirmFinalize": "This permanently deletes your account. This cannot be undone. Continue?"
}
},
"car": {
"allCars": "← All cars",
"share": "Share",
"shared": "Shared",
"sharedReadOnly": "Shared · read-only",
"tabs": {
"info": "Information",
"services": "Service history",
"technical": "Technical check history",
"maintenance": "Maintenance",
"fuel": "Fuel",
"documents": "Documents",
"parts": "Parts catalog",
"reminders": "Reminders"
},
"info": {
"oilSpec": "Engine oil spec",
"transmissionOil": "Transmission oil",
"differentialOil": "Differential oil",
"brakeFluid": "Brake fluid",
"coolant": "Coolant",
"odometer": "Odometer",
"serviceInterval": "Service interval",
"nextDue": "Next due",
"registrationPlate": "Registration plate",
"registrationCountry": "Registration country",
"vin": "VIN",
"fuelType": "Fuel type",
"buildDate": "Build date",
"firstRegistration": "First registration"
},
"services": {
"title": "Service history",
"add": "Add service",
"empty": "No service records yet.",
"colDate": "Date",
"colKm": "Km",
"colNextDate": "Next date",
"colNextKm": "Next km",
"colOil": "Oil & Oil filter",
"colEngineFilter": "Engine air filter",
"colCabinFilter": "Cabin air filter",
"colNotes": "Notes",
"colFile": "File",
"confirmDelete": "Delete this service record?"
},
"technical": {
"title": "Technical check history",
"subtitle": "Mandatory roadworthiness inspections. Recurs on time alone, whatever the odometer reads.",
"add": "Add check",
"empty": "No technical checks yet.",
"colDate": "Date",
"colResult": "Result",
"colNextCheck": "Next check",
"colStatus": "Status",
"colStation": "Station",
"colCost": "Cost",
"colNotes": "Notes",
"colFile": "File",
"passed": "Passed",
"failed": "Failed",
"confirmDelete": "Delete this technical check?"
},
"maintenance": {
"title": "Maintenance",
"subtitle": "Workshop visits and repairs. Routine servicing lives under Service history.",
"add": "Log visit",
"empty": "No workshop visits logged yet.",
"colDate": "Date",
"colKm": "Km",
"colType": "Type",
"colWork": "Work done",
"colWorkshop": "Workshop",
"colStatus": "Status",
"colCost": "Cost",
"colFile": "File",
"underWarranty": "Under warranty · {days}d left",
"confirmDelete": "Delete this workshop visit?"
},
"fuel": {
"title": "Fuel",
"subtitle": "Consumption is measured between full tanks.",
"add": "Log refill",
"empty": "No refills logged yet.",
"average": "Average",
"best": "Best",
"worst": "Worst",
"costPerKm": "Cost per km",
"refills": "Refills",
"totalLiters": "Total litres",
"totalSpent": "Total spent",
"trackedDistance": "Tracked distance",
"avgPrice": "Avg. price {price}/L",
"needTwoTanks": "Log at least two full tanks to see consumption figures.",
"colDate": "Date",
"colKm": "Km",
"colLiters": "Litres",
"colCost": "Cost",
"colPerLiter": "Per litre",
"colDistance": "Distance",
"colConsumption": "Consumption",
"colStation": "Station",
"colFile": "File",
"partial": "partial",
"gap": "gap",
"confirmDelete": "Delete this refill?"
},
"documents": {
"title": "Documents",
"subtitle": "Insurance, pollution certificates and other paperwork with renewal dates.",
"add": "Add document",
"empty": "No documents yet.",
"colType": "Type",
"colTitle": "Title",
"colProvider": "Provider",
"colIssued": "Issued",
"colRenewal": "Renewal",
"colStatus": "Status",
"colFile": "File",
"confirmDelete": "Delete this document?"
},
"reminders": {
"title": "Reminders",
"subtitle": "Renewal and service reminders are added automatically from your documents and service history.",
"add": "Add reminder",
"empty": "Nothing to be reminded about yet.",
"automatic": "Automatic",
"repeats": "Repeats",
"at": "at {km}",
"doneRollForward": "Done · roll forward",
"markDone": "Mark done",
"reopen": "Reopen",
"confirmDelete": "Delete this reminder?"
},
"parts": {
"title": "Parts catalog",
"add": "Add part",
"empty": "No parts yet.",
"colPart": "Part",
"colPartNumber": "Part number",
"colNotes": "Notes",
"colFile": "File",
"confirmDelete": "Delete this part?"
},
"delete": {
"title": "Delete this car?",
"body": "This permanently deletes {name} and everything logged against it — {services}, {maintenance}, {fuel}, {documents} and {parts}. This cannot be undone.",
"services": {
"one": "{n} service record",
"other": "{n} service records"
},
"maintenance": {
"one": "{n} workshop visit",
"other": "{n} workshop visits"
},
"fuel": {
"one": "{n} refill",
"other": "{n} refills"
},
"documents": {
"one": "{n} document",
"other": "{n} documents"
},
"parts": {
"one": "{n} part",
"other": "{n} parts"
},
"typeToConfirm": "Type {name} to confirm",
"deleting": "Deleting…",
"confirm": "Delete permanently"
}
},
"attachment": {
"legend": "Attachment",
"hint": "PDF or image, up to 10MB.",
"attached": "Attached: {name}",
"willBeRemoved": "Attachment will be removed on save."
},
"forms": {
"car": {
"addTitle": "Add a car",
"editTitle": "Edit car",
"name": "Name *",
"make": "Make",
"model": "Model",
"year": "Year",
"registration": "Registration",
"registrationCountry": "Registration country",
"registrationCountryPlaceholder": "Poland",
"vin": "VIN",
"vinPlaceholder": "Vehicle Identification Number",
"fuelType": "Fuel type",
"buildDate": "Build date",
"firstRegistration": "First registration",
"oilSpec": "Engine oil spec",
"currentKm": "Current odometer (km)",
"transmissionOilSpec": "Transmission oil spec",
"differentialOilSpec": "Differential oil spec",
"brakeFluidSpec": "Brake fluid spec",
"coolantSpec": "Coolant spec",
"serviceIntervalDays": "Service interval (days)",
"serviceIntervalKm": "Service interval (km)",
"technicalCheckIntervalDays": "Technical check interval (days)",
"technicalCheckHint": "Prefills each check's next-due date. Any check can override it with the date printed on its certificate.",
"submit": "Add car"
},
"service": {
"addTitle": "Add service record",
"editTitle": "Edit service record",
"date": "Date *",
"odometer": "Odometer (km)",
"changedParts": "Changed parts",
"oil": "Oil & Oil filter",
"engineFilter": "Engine air filter",
"cabinFilter": "Cabin air filter",
"attachmentLegend": "Receipt or service-book page",
"notes": "Notes",
"autoHint": "Next service date (+{days}d) and km (+{km}) are computed automatically.",
"submit": "Add service"
},
"technical": {
"addTitle": "Add technical check",
"editTitle": "Edit technical check",
"date": "Check date *",
"result": "Result *",
"passed": "Passed",
"failed": "Failed",
"validUntil": "Valid until",
"failedHint": "A failed check certifies nothing, so no next date is derived from it.",
"derivedHint": "Leave blank to use the car's interval (+{days}d → {date}). Enter the date on the certificate when it differs.",
"cost": "Cost",
"station": "Station",
"stationPlaceholder": "Stacja Kontroli Pojazdów",
"attachmentLegend": "Inspection certificate",
"notes": "Notes",
"submit": "Add check"
},
"part": {
"addTitle": "Add part",
"editTitle": "Edit part",
"name": "Part name *",
"namePlaceholder": "Oil Filter",
"partNumber": "Part number",
"notes": "Notes",
"notesPlaceholder": "Fits 2015–2020 · buy in pairs",
"attachmentLegend": "Photo or spec sheet",
"submit": "Add part"
},
"fuel": {
"addTitle": "Log refill",
"editTitle": "Edit refill",
"date": "Date *",
"odometer": "Odometer (km) *",
"liters": "Litres *",
"cost": "Total cost",
"pricePerLiter": "Price per litre: {price}",
"tank": "Tank",
"fullTank": "Filled to full",
"missedFill": "I missed logging a refill before this one",
"tankHint": "Consumption is measured between full tanks, so partial fills count towards the next full one. Flagging a missed refill leaves that stretch out of the figures instead of reporting it as unrealistically economical.",
"station": "Station",
"notes": "Notes",
"attachmentLegend": "Receipt",
"submit": "Log refill"
},
"maintenance": {
"addTitle": "Log workshop visit",
"editTitle": "Edit workshop visit",
"date": "Date *",
"odometer": "Odometer (km)",
"type": "Type",
"status": "Status",
"description": "What was done *",
"descriptionPlaceholder": "Replaced alternator and drive belt",
"workshop": "Workshop",
"location": "Location",
"partsUsed": "Parts replaced",
"partsUsedPlaceholder": "Alternator 27060-0T010, belt 90916-02660",
"laborCost": "Labour cost",
"partsCost": "Parts cost",
"total": "Total: {total}",
"invoiceNumber": "Invoice number",
"warrantyUntil": "Warranty until",
"attachmentLegend": "Invoice",
"notes": "Notes",
"submit": "Log visit"
},
"document": {
"addTitle": "Add document",
"editTitle": "Edit document",
"type": "Type",
"title": "Title *",
"titlePlaceholder": "Third-party liability 2026",
"provider": "Provider",
"reference": "Policy / certificate no.",
"issued": "Issued",
"renewalDate": "Renewal date",
"renewalHint": "Leave the renewal date blank for a document that never expires. Setting it adds a reminder automatically.",
"cost": "Cost",
"attachmentLegend": "Scan or photo",
"notes": "Notes",
"submit": "Add document"
},
"reminder": {
"addTitle": "Add reminder",
"editTitle": "Edit reminder",
"title": "Title *",
"titlePlaceholder": "Swap to winter tyres",
"type": "Type",
"remindMe": "Remind me",
"onDate": "On date",
"atOdometer": "At odometer (km)",
"triggerHint": "Set either or both — with both, whichever comes first wins.",
"currentKm": "The car is at {km} now.",
"repeat": "Repeat (optional)",
"everyDays": "Every … days",
"everyKm": "Every … km",
"recurringHint": "Marking this done will roll it forward instead of closing it.",
"oneOffHint": "Leave blank for a one-off reminder that closes when you mark it done.",
"notes": "Notes",
"noTrigger": "Set a due date, a due odometer reading, or both.",
"submit": "Add reminder"
},
"share": {
"title": "Share {name}",
"body": "Give another user access to this car. Read-only lets them view; read & write also lets them edit the car and its service records and parts.",
"userEmail": "User email",
"read": "Read-only",
"write": "Read & write",
"submit": "Share",
"peopleWithAccess": "People with access",
"notShared": "Not shared with anyone yet."
}
},
"enums": {
"fuelType": {
"petrol": "Petrol (gasoline)",
"petrol_lpg": "Petrol (gasoline) + LPG",
"diesel": "Diesel",
"diesel_lpg": "Diesel + LPG",
"hybrid": "Hybrid",
"electric": "Electric",
"hydrogen": "Hydrogen"
},
"maintenanceType": {
"repair": "Repair",
"inspection": "Inspection",
"bodywork": "Bodywork",
"tyres": "Tyres",
"diagnostics": "Diagnostics",
"recall": "Recall",
"warranty": "Warranty work",
"other": "Other"
},
"maintenanceStatus": {
"scheduled": "Scheduled",
"in_progress": "In progress",
"completed": "Completed"
},
"documentType": {
"insurance": "Insurance",
"pollution": "Pollution certificate",
"registration": "Registration",
"inspection": "Inspection",
"roadTax": "Road tax",
"warranty": "Warranty",
"other": "Other"
},
"reminderTypeShort": {
"maintenance": "Maintenance",
"document": "Document",
"service": "Service",
"inspection": "Inspection",
"other": "Other"
},
"reminderType": {
"maintenance": "Maintenance",
"document": "Document renewal",
"service": "Service",
"inspection": "Inspection",
"other": "Other"
}
},
"status": {
"noData": "No data",
"serviceOverdueDays": "Service Overdue {days}d",
"dueInDays": "Due in {days}d",
"okDays": "OK · {days}d",
"noKm": "No km",
"serviceOverdueKm": "Service Overdue {km} km",
"inKm": "In {km} km",
"kmLeft": "{km} km left",
"expiredAgo": "Expired {days}d ago",
"expiresToday": "Expires today",
"renewInDays": "Renew in {days}d",
"validDays": "Valid · {days}d",
"noExpiry": "No expiry",
"done": "Done",
"noTrigger": "No trigger",
"overdue": "Overdue",
"overdueBy": "Overdue {parts}",
"dueIn": "Due in {parts}",
"upcoming": "Upcoming",
"today": "today",
"days": "{days}d",
"km": "{km} km"
}
}