5697137c52
The "permit/lejet" feature is really a subscription. Full rename of the mutable master data, plus a recurring monthly price. - DB (migration 0004, data-preserving ALTER RENAME): permits→subscriptions, permit_credentials/_plates→subscription_*, sessions.permit_id→subscription_id. - Pricing: per-subscription priceMinor + period(monthly) + currency, with a site default (site_config.subscription_monthly_price_minor) pre-filling the form. - Server: subscription-flow.ts (SubscriptionFlow), routes/subscriptions.ts (/api/subscriptions). Web: SubscriptionManager, route, i18n (sq Abonimet/en). - The signed ledger `permitId` payload is intentionally kept — immutable hash-chained history; renaming it would break verification of past events. Deferred (wiki notes): fee collection into the ledger/shift (a shift-attributed payment), LPR/ANPR plate source, time-of-day access windows (overnight subscriber). Also carries the device-footer UI surface (api DeviceStatus, router mount, i18n devices) due to shared-file overlap with the preceding footer commit. Verified end-to-end on a fresh DB and migration on a live-DB copy (sessions preserved). Live DB migrated. Full monorepo builds clean. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
264 lines
8.5 KiB
TypeScript
264 lines
8.5 KiB
TypeScript
// English (en). Mirrors the key structure of sq.ts (the default/fallback). Any key
|
||
// missing here falls back to Albanian. See wiki/concepts/i18n.md.
|
||
|
||
import type { Catalog } from "./sq.js";
|
||
|
||
export const en: Catalog = {
|
||
common: {
|
||
loading: "Loading…",
|
||
logout: "Log out",
|
||
cancel: "Cancel",
|
||
close: "Close",
|
||
save: "Save",
|
||
none: "—",
|
||
},
|
||
auth: {
|
||
title: "Parking System",
|
||
username: "Username",
|
||
password: "Password",
|
||
signIn: "Sign in",
|
||
signingIn: "Signing in…",
|
||
},
|
||
nav: {
|
||
booth: "Booth",
|
||
shift: "Shift",
|
||
setup: "Setup",
|
||
tariff: "Tariff",
|
||
subscriptions: "Subscriptions",
|
||
site: "Site",
|
||
},
|
||
status: {
|
||
live: "LIVE",
|
||
connecting: "CONNECTING",
|
||
offline: "OFFLINE",
|
||
},
|
||
devices: {
|
||
footerTitle: "Devices",
|
||
none: "No devices configured.",
|
||
catAccess: "Barrier",
|
||
catReader: "Reader",
|
||
catCamera: "Camera",
|
||
catPrinter: "Printer",
|
||
// Role/direction suffixes for the chip label (e.g. "Reader entry").
|
||
role: {
|
||
entry: "entry",
|
||
exit: "exit",
|
||
both: "entry/exit",
|
||
mixed: "mixed",
|
||
lane: "lane",
|
||
booth: "booth",
|
||
},
|
||
state: {
|
||
ready: "ready",
|
||
degraded: "degraded",
|
||
offline: "offline",
|
||
},
|
||
allOk: "all ready",
|
||
issuesCount: "{{count}} with issues",
|
||
issuesTitle: "Device issues",
|
||
clickForIssues: "Click for details",
|
||
checkedAt: "checked {{time}}",
|
||
},
|
||
booth: {
|
||
processTicket: "Process ticket",
|
||
scanPlaceholder: "Scan or type ticket number…",
|
||
open: "Open",
|
||
occupancy: "Occupancy",
|
||
occUnavailable: "occupancy unavailable",
|
||
inside: "inside",
|
||
of: "of",
|
||
uncapped: "uncapped",
|
||
free: "free",
|
||
lotFull: "● lot full",
|
||
liveFeed: "Live feed",
|
||
events: "events",
|
||
noEventsYet: "No events yet — entries and exits will stream here.",
|
||
activeSessions: "Active sessions",
|
||
insideCount: "inside",
|
||
noActiveSessions: "No active sessions.",
|
||
inAt: "in",
|
||
openPayExit: "Open pay / exit",
|
||
openBarrier: "Open barrier",
|
||
openBarrierTitle: "Human-intervention barrier open (audited)",
|
||
barrierOpened: "barrier opened",
|
||
openManually: "open manually",
|
||
badgeExiting: "exiting",
|
||
badgePaid: "paid",
|
||
badgeUnpaid: "unpaid",
|
||
evtEntry: "ENTRY",
|
||
evtExit: "EXIT",
|
||
evtPay: "PAY",
|
||
evtVoid: "VOID",
|
||
evtOpenCmd: "OPEN→",
|
||
evtOpenObserved: "OPEN✓",
|
||
evtShiftOpen: "SHIFT+",
|
||
evtShiftZ: "SHIFT Z",
|
||
evtCashMovement: "CASH",
|
||
evtAnomaly: "ANOMALY",
|
||
},
|
||
tariff: {
|
||
title: "Tariff",
|
||
noRateCard: "No rate card published yet — the pay station can't charge until you publish one.",
|
||
activeSince: "Active since {{date}} · {{count}} version(s) in history. Publishing creates a new version; past sessions keep their original pricing.",
|
||
currency: "Currency",
|
||
freeEntryGrace: "Free entry grace (min)",
|
||
billingIncrement: "Billing increment (min)",
|
||
dailyCap: "Daily cap (blank = none)",
|
||
dailyCapPh: "e.g. 12.00",
|
||
lostTicketFee: "Lost-ticket fee",
|
||
exitGrace: "Exit walk-back grace (min)",
|
||
rateBlocks: "Rate blocks",
|
||
rateBlocksHint: "Consumed in order as time accrues. \"Up to (min)\" is the block's upper bound; leave the last block's bound blank for \"thereafter\". Price is per billing increment.",
|
||
upToMin: "Up to (min)",
|
||
pricePerIncrement: "Price / increment",
|
||
thereafter: "thereafter",
|
||
egExample: "e.g. 60",
|
||
remove: "Remove",
|
||
addBlock: "+ Add block",
|
||
publishNewVersion: "Publish new version",
|
||
publishing: "Publishing…",
|
||
publishedOk: "New tariff version published — it's now the active rate card.",
|
||
},
|
||
subs: {
|
||
title: "Subscriptions",
|
||
unnamed: "(unnamed)",
|
||
unbound: "unbound",
|
||
car_one: "{{count}} car",
|
||
car_other: "{{count}} cars",
|
||
cred: "cred",
|
||
plates: "{{count}} plate(s)",
|
||
noPrice: "no price",
|
||
perMonth: "month",
|
||
monthlyPrice: "Monthly price",
|
||
pricePlaceholder: "e.g. 10000",
|
||
edit: "Edit",
|
||
revoke: "Revoke",
|
||
delete: "Delete",
|
||
noneYet: "No subscriptions yet.",
|
||
add: "+ Add subscription",
|
||
new: "New subscription",
|
||
editTitle: "Edit subscription",
|
||
holderName: "Holder name",
|
||
contact: "Contact",
|
||
carLimit: "Car limit",
|
||
limitCarsInAtOnce: "limit cars in at once",
|
||
validFrom: "Valid from",
|
||
validTo: "Valid to",
|
||
isoDateOptional: "ISO date (optional)",
|
||
boundPlates: "Bound plates",
|
||
commaSeparatedOptional: "comma-separated (optional)",
|
||
credentialsCardQr: "Credentials (card / QR)",
|
||
rfCardTag: "RF card/tag",
|
||
qr: "QR",
|
||
credentialValue: "credential value",
|
||
addCredential: "+ credential",
|
||
needCredentialOrPlate: "A subscription needs at least one credential OR one bound plate.",
|
||
save: "Save",
|
||
cancel: "Cancel",
|
||
saved: "Subscription saved.",
|
||
confirmRevoke: "Revoke subscription for {{name}}? It will be refused at the barrier.",
|
||
confirmDelete: "Delete subscription for {{name}}? (Past events are kept.)",
|
||
statusActive: "active",
|
||
statusSuspended: "suspended",
|
||
statusRevoked: "revoked",
|
||
},
|
||
site: {
|
||
occupancy: "Occupancy:",
|
||
noCapacitySet: "(no capacity set)",
|
||
free: "free",
|
||
full: "FULL",
|
||
capacityLabel: "Capacity (blank = no limit):",
|
||
capacityPlaceholder: "e.g. 120",
|
||
printExitDefault: "Print exit ticket by default",
|
||
printExitHint: "(booth far from exit → customer self-exits with a voucher)",
|
||
parkDetails: "Park details (optional — shown on tickets/receipts)",
|
||
save: "Save",
|
||
saved: "Saved.",
|
||
fieldParkName: "Park name",
|
||
fieldParkNamePh: "e.g. Acme Parking",
|
||
fieldOperator: "Operator (legal name)",
|
||
fieldOperatorPh: "operating company",
|
||
fieldNius: "NIUS",
|
||
fieldNiusPh: "e.g. L01234567A",
|
||
fieldAddress: "Address",
|
||
fieldPhone: "Phone",
|
||
fieldEmail: "Email",
|
||
},
|
||
shift: {
|
||
label: "Shift:",
|
||
open: "open",
|
||
notStarted: "not started",
|
||
since: "since",
|
||
startShift: "Start shift",
|
||
starting: "Starting…",
|
||
endShift: "End shift",
|
||
ending: "Ending…",
|
||
drawer: "Drawer:",
|
||
openingFloatInherited: "(opening float inherited from the prior shift)",
|
||
drawerCashAdmin: "Drawer cash (admin) — load or remove the float",
|
||
amount: "amount",
|
||
reasonPlaceholder: "reason (e.g. opening float)",
|
||
load: "Load +",
|
||
remove: "Remove −",
|
||
enterPositive: "Enter a positive amount.",
|
||
drawerNow: "Drawer now {{amount}}.",
|
||
zReport: "Z-REPORT",
|
||
payments: "Payments:",
|
||
cash: "Cash:",
|
||
card: "Card:",
|
||
drawerSection: "— Drawer —",
|
||
openingFloat: "Opening float:",
|
||
cashTaken: "Cash taken:",
|
||
cashAdded: "Cash added:",
|
||
cashRemoved: "Cash removed:",
|
||
expectedDrawer: "Expected drawer:",
|
||
printedToReceipt: "Printed to booth receipt.",
|
||
recordedNoPrinter: "Recorded (no printer to print to).",
|
||
// Header shift control + the booth shift gate.
|
||
headerNoShift: "No shift",
|
||
headerOpen: "Open shift",
|
||
headerClose: "Close shift",
|
||
headerHeldBy: "Shift open — {{operator}}",
|
||
headerHeldByShort: "Shift: {{operator}}",
|
||
gateTitle: "Open a shift to process tickets",
|
||
gateBody:
|
||
"No shift is open. Open your shift so payments and exits are recorded against it.",
|
||
gateOtherTitle: "The open shift belongs to another operator",
|
||
gateOtherBody:
|
||
"{{operator}} has an open shift. Only one shift may be open at a time — they must close theirs before you can open yours.",
|
||
openNow: "Open shift now",
|
||
opening: "Opening…",
|
||
},
|
||
pay: {
|
||
ticket: "Ticket",
|
||
entry: "Entry",
|
||
now: "Now",
|
||
duration: "Duration",
|
||
statusLabel: "Status",
|
||
paid: "PAID",
|
||
unpaid: "UNPAID",
|
||
total: "Total",
|
||
noTariff: "no tariff",
|
||
tender: "Tender",
|
||
cash: "Cash",
|
||
card: "Card",
|
||
printExitVoucher: "Print exit ticket",
|
||
selfExitHint: "(customer self-exits at the exit)",
|
||
payAndOpen: "Pay + open barrier",
|
||
payAndVoucher: "Pay + print voucher",
|
||
openBarrier: "Open barrier",
|
||
printVoucher: "Print voucher",
|
||
takingPayment: "taking payment…",
|
||
printingVoucher: "printing voucher…",
|
||
opening: "opening…",
|
||
noSessionFound: "No session found for this ticket.",
|
||
alreadyClosed: "This session is already closed (exited {{time}}).",
|
||
lookingUp: "looking up…",
|
||
paidBarrierOpened: "Paid — barrier opened. Car may exit.",
|
||
paidExitRecorded: "Paid and exit recorded, but the barrier did not open: {{reason}}.",
|
||
voucherPrinted: "Exit voucher printed on {{printer}}. Customer self-exits at the exit.",
|
||
noSnapshots: "no snapshots",
|
||
loadingSnapshots: "loading snapshots…",
|
||
},
|
||
};
|