Files
parking_solution/apps/web/src/lib/i18n/sq.ts
T
julian 00f3d141b6 feat: human + relative dates; fix language/theme toggle stale-context
Dates were raw ISO on printed slips and time-only in the UI (a session from
two days ago showed just "10:48"). Make them human and day-relative. Also fix
a latent toggle bug surfaced while testing.

Dates:
- Printed tickets/receipts/subscription cards now show "19 Qershor 2026
  10:48:25" (Albanian month, 24h with seconds) instead of YYYY-MM-DD HH:MM.
  stamp() exported as formatStampSq so the shift Z-report shares it.
- Shift Z-report is now Albanian (Operatori/Nga/Deri/Para në dorë/Arka…),
  was English-only with ISO dates.
- Web sessions/logs/history show relative days: "Sot 10:48" / "Dje 17:33" /
  "17 Qershor 10:48" via formatRelativeDateTime(). Month names come from the
  i18n catalog (common.months), NOT Intl — the appliance browser's ICU lacks
  Albanian locale data and Intl silently falls back to English month names.

Toggle fix:
- The language + theme toggles read the active value from the TanStack Router
  context `user`, which is captured at route-resolution time and does not
  re-render on setUser. After one switch the highlight froze and the equality
  guard blocked switching back until a page refresh. Drive them off live state
  instead: language from i18n.language (useTranslation subscribes to
  languageChanged), theme from local useState. (Bug dated to 040c0ff.)

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
2026-06-19 11:14:06 +02:00

550 lines
20 KiB
TypeScript
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.
// Albanian (sq) — the DEFAULT and fallback language. Customer/operator-facing copy.
// Keys are dot-namespaced by area (common, nav, booth, …). When adding a string,
// add it here first (the fallback), then mirror the key in en.ts.
// See wiki/concepts/i18n.md.
export const sq = {
common: {
loading: "Duke u ngarkuar…",
logout: "Dil",
cancel: "Anulo",
close: "Mbyll",
save: "Ruaj",
none: "—",
themeDark: "errët",
themeLight: "çelët",
theme: "Tema",
today: "Sot",
yesterday: "Dje",
// Month names (index 0 = January) — kept in the catalog because the appliance's
// browser ICU may lack Albanian locale data (Intl falls back to English).
months: [
"Janar",
"Shkurt",
"Mars",
"Prill",
"Maj",
"Qershor",
"Korrik",
"Gusht",
"Shtator",
"Tetor",
"Nëntor",
"Dhjetor",
],
},
auth: {
title: "Sistemi i Parkimit",
username: "Përdoruesi",
password: "Fjalëkalimi",
signIn: "Hyr",
signingIn: "Duke hyrë…",
},
nav: {
booth: "Kabina",
shift: "Turni",
setup: "Konfigurimi",
devices: "Pajisjet",
tariff: "Tarifa",
subscriptions: "Abonimet",
site: "Park",
users: "Përdoruesit",
roles: "Rolet",
shifts: "Turnet",
},
status: {
live: "LIVE",
connecting: "DUKE U LIDHUR",
offline: "JASHTË LINJE",
},
devices: {
footerTitle: "Pajisjet",
none: "Asnjë pajisje e konfiguruar.",
catAccess: "Barriera",
catReader: "Lexuesi",
catCamera: "Kamera",
catPrinter: "Printer",
// Role/direction suffixes for the chip label (e.g. "Lexuesi hyrje").
role: {
entry: "hyrje",
exit: "dalje",
both: "hyrje/dalje",
mixed: "hyrje/dalje",
lane: "në korsi",
booth: "në kabinë",
},
state: {
ready: "gati",
degraded: "i dëmtuar",
offline: "jashtë linje",
},
allOk: "të gjitha gati",
issuesCount: "{{count}} me probleme",
issuesTitle: "Problemet e pajisjeve",
clickForIssues: "Kliko për detajet",
checkedAt: "kontrolluar {{time}}",
},
booth: {
processTicket: "Proceso biletën",
scanPlaceholder: "Skano ose shkruaj numrin e biletës…",
open: "Hap",
occupancy: "Prania",
occUnavailable: "zënia e padisponueshme",
inside: "brenda",
of: "nga",
uncapped: "pa kufi",
free: "Vende të lira",
lotFull: "● parkimi plot",
liveFeed: "Aktiviteti live",
events: "Evente",
noEventsYet: "Asnjë event ende — hyrjet dhe daljet do të shfaqen këtu.",
activeSessions: "Sesionet aktive",
insideCount: "brenda",
noActiveSessions: "Asnjë sesion aktiv.",
openPayExit: "Hap pagesën / daljen",
openBarrier: "Hap barrierën",
openBarrierTitle: "Hap barrierën manualisht",
barrierOpened: "barriera u hap",
openManually: "hape me dorë",
// session row badges
badgeExiting: "duke dalë",
badgePaid: "paguar",
badgeUnpaid: "papaguar",
badgeSubscription: "abonim",
// event types (live feed labels)
evtEntry: "HYRJE",
evtExit: "DALJE",
evtPay: "PAGESË",
evtVoid: "ANULIM",
evtOpenCmd: "HAP→",
evtOpenObserved: "HAP✓",
evtShiftOpen: "TURN+",
evtShiftZ: "TURN Z",
evtCashMovement: "ARKË",
evtAnomaly: "ANOMALI",
// rreshti i detajeve të eventit live + etiketat e klasifikimit (nga payload)
evtNoReason: "pa arsye të regjistruar",
badgeEntryRefused: "hyrje e refuzuar",
badgeExitRefused: "dalje e refuzuar",
badgeLotFull: "parkimi plot",
badgeBarrierFailed: "barriera nuk u hap",
badgeManualOpen: "hapje manuale",
badgeSubRefused: "abonimi u refuzua",
badgeNoTicket: "bileta nuk u printua",
feedSourceBooth: "kabinë",
feedSourceReader: "lexues",
// dritarja e detajeve të eventit
eventDetail: "Detajet e eventit",
edType: "Lloji",
edTime: "Ora",
edIndex: "Indeksi në regjistër",
edDirection: "Drejtimi",
edSource: "Burimi",
edIdentity: "Identiteti",
edReason: "Arsyeja",
edSnapshots: "Fotot",
edPayload: "Të dhënat e nënshkruara",
edChain: "Zinxhiri",
edSignature: "Nënshkrimi",
edKeyId: "Çelësi",
edPrevHash: "Hash-i i mëparshëm",
edNoPayload: "Ky event nuk ka të dhëna shtesë.",
edCopy: "Kopjo",
edCopied: "U kopjua",
edDetails: "Detajet",
edAuditData: "Të dhënat e auditimit (nënshkrimi & zinxhiri)",
edAmount: "Shuma",
edTender: "Mënyra e pagesës",
edSession: "Sesioni",
edPlate: "Targa",
edCategory: "Kategoria",
edOperator: "Operatori",
edTariffVersion: "Versioni i tarifës",
edRawPayload: "Të dhënat e papërpunuara të nënshkruara",
edOccurrence: "ID e hyrjes",
subscriber: "Abonent",
},
// Mesazhet e përkthyera për REASON_CODES e nënshkruara (shih @parking/shared).
// Çelësat përputhen 1:1 me kodet; {{param}} mbushet nga reasonParams i eventit.
reason: {
"entry.refused.full": "Hyrja u refuzua — parkimi plot ({{count}}/{{capacity}})",
"entry.held.noTicket": "Hyrja u mbajt — bileta nuk u printua: {{detail}}",
"exit.refused.closed": "Dalja u refuzua — sesioni është mbyllur tashmë",
"exit.refused.noSession": "Dalja u refuzua — biletë e panjohur",
"exit.refused.unpaid": "Dalja u refuzua — e papaguar (bëj pagesën në fillim)",
"exit.refused.graceExpired": "Dalja u refuzua — afati i daljes skadoi (kërkohet pagesë shtesë)",
"exit.open.noBarrier": "Dalja u regjistrua, por nuk ka barrierë daljeje të konfiguruar — hape me dorë",
"exit.open.unavailable": "Dalja u regjistrua, por barriera është e padisponueshme — hape me dorë",
"exit.open.failed": "Dalja u regjistrua, por barriera nuk u hap — hape me dorë",
"exit.freeGrace": "Periudhë pa pagesë në hyrje (pa tarifë)",
"exit.manualOpen": "Hapje manuale e barrierës (ndërhyrje njerëzore)",
"sub.refused.notFound": "Abonimi u refuzua — nuk u gjet",
"sub.refused.outOfWindow": "Abonimi u refuzua — {{status}}/jashtë afatit",
"sub.refused.noSession": "Dalje me abonim pa sesion të hapur (tashmë jashtë / nuk ka hyrë kurrë)",
"sub.refused.atCapacity": "Abonimi u refuzua — në kapacitet ({{inUse}}/{{max}} makina brenda)",
},
tariff: {
title: "Tarifa",
noRateCard: "Asnjë tarifë e publikuar — arka nuk mund të faturojë derisa të publikoni një.",
activeSince: "Aktive që nga {{date}} · {{count}} version(e) në histori. Publikimi krijon një version të ri; sesionet e kaluara ruajnë çmimin origjinal.",
currency: "Monedha",
freeEntryGrace: "Periudha pa pagesë në hyrje (min)",
billingIncrement: "Intervali i faturimit (min)",
dailyCap: "Kufiri ditor (bosh = pa kufi)",
dailyCapPh: "p.sh. 12.00",
lostTicketFee: "Tarifa për biletë të humbur",
exitGrace: "Periudha e kthimit në dalje (min)",
rateBlocks: "Blloqet tarifore",
rateBlocksHint: "Çdo brez zgjat një numër orësh dhe faturohet me çmimin e tij; brezat konsumohen me radhë (orët e para, pastaj orët në vijim). Brezi i fundit është \"më pas\" (i hapur) — çmimi i tij zbatohet pas mbarimit të shkallës. Çmimi është për interval faturimi.",
bandDuration: "Kohëzgjatja e brezit",
hoursUnit: "orë",
egHours: "p.sh. 2",
pricePerIncrement: "Çmimi / interval",
thereafter: "më pas (i hapur)",
remove: "Hiq",
addBlock: "+ Shto bllok",
publishNewVersion: "Publiko version të ri",
publishing: "Duke publikuar…",
publishedOk: "U publikua versioni i ri i tarifës — tani është tarifa aktive.",
defaultCard: "Tarifa bazë (gjithmonë aktive)",
defaultCardHint: "Çmimi bazë i zbatuar kur asnjë nivel kohor/sezonal nuk vlen. Kjo e vetme është mjaftueshëm për shumicën e parkimeve.",
modeLadder: "Shkallë orësh",
modeFlat: "Çmim fiks",
tiersAdvanced: "Të avancuara: nivele kohore & sezonale",
tiersHint: "Opsionale. Shto nivele tarifore që vlejnë vetëm në orë/ditë/data ose kategori të caktuara (p.sh. orë e lirë, tarifë nate, fundjavë, autobus). Pa nivele, publikohet vetëm tarifa bazë.",
tierName: "Emri",
tierPriority: "Përparësia",
tierCategory: "Kategoria",
tierCategoryPh: "p.sh. autobus",
tierDays: "Ditët",
tierHours: "Orët",
tierDates: "Datat",
tierOvernight: "(kalon mesnatën)",
addTier: "+ Shto nivel",
dow1: "Hën",
dow2: "Mar",
dow3: "Mër",
dow4: "Enj",
dow5: "Pre",
dow6: "Sht",
dow0: "Die",
},
setup: {
title: "Konfigurimi",
intro:
"Shto fillimisht kontrolluesit e barrierave — cakto cili rele është hyrje/dalje dhe në cilin terminal është lidhur butoni i hyrjes. Pastaj shto lexues, kamera dhe printera dhe drejto secilin te barriera që shërben.",
// Category titles + the singular noun used in buttons/modal titles.
catControllers: "Kontrolluesit (barrierat + butoni i hyrjes)",
catReaders: "Lexuesit (QR / RFID)",
catCameras: "Kamerat (foto + targë)",
catPrinters: "Printerat (bileta / vouchera)",
nounController: "kontrollues",
nounReader: "lexues",
nounCamera: "kamerë",
nounPrinter: "printer",
add: "+ Shto {{noun}}",
addAnother: "+ Shto edhe një {{noun}}",
addTitle: "Shto {{noun}}",
editTitle: "Ndrysho {{noun}}",
needControllerFirst: "Shto fillimisht një kontrollues — {{noun}} drejtohet te një prej releve të tij.",
failedToLoad: "Ngarkimi i konfigurimit dështoi: {{error}}",
loadingCatalog: "Duke ngarkuar katalogun e pajisjeve…",
// Direction labels (relay direction + inherited binding).
dirEntry: "Hyrje",
dirExit: "Dalje",
dirBoth: "Hyrje + dalje",
inherits: "trashëgon {{direction}}",
// Warnings panel.
warnTitle: "⚠ U ruajt, por nevojitet veprim:",
dismiss: "Mbyll",
// Assignment row.
disabled: "(çaktivizuar)",
edit: "Ndrysho",
remove: "Hiq",
removing: "Duke hequr…",
confirmRemove: "Të hiqet kjo pajisje {{driver}}?",
noRelaysSet: "asnjë rele e caktuar",
unbound: "e palidhur",
// Device form.
noDrivers: "Asnjë drejtues i regjistruar.",
chooseDevice: "Zgjidh një pajisje…",
scan: "Skano për kontrollues",
scanning: "Duke skanuar…",
noControllersFound: "Asnjë kontrollues në LAN.",
use: "Përdor",
test: "Testo lidhjen",
testing: "Duke testuar…",
saveConfigure: "Ruaj & konfiguro",
saveChanges: "Ruaj ndryshimet",
saving: "Duke ruajtur…",
cancel: "Anulo",
testFailed: "Testi dështoi: {{error}}",
saveFailed: "Ruajtja dështoi: {{error}}",
deviceLabel: "Pajisja:",
preconditionsOk: "● parakushtet OK",
autoFixedOnSave: "(rregullohet vetë në ruajtje)",
backendPushIp: "IP-ja e backend-it",
chooseAddress: "Zgjidh një adresë…",
onDeviceSubnet: "— në subnetin e pajisjes",
noNicOnSubnet: "⚠ asnjë NIC në subnetin e pajisjes — pajisja mund të mos arrijë backend-in",
backendIpHint: "Adresa te e cila kjo pajisje do të dërgojë eventet e hyrjes.",
// Relay editor.
relaysTitle: "Relet në këtë kontrollues",
relaysHint:
"Çdo rele hap një barrierë. Cakto drejtimin e saj; për hyrje kalimtare, cakto në cilin terminal hyrës është lidhur butoni i hyrjes.",
relay: "Rele",
entryButtonTerminal: "Butoni i hyrjes në terminalin",
addRelay: "+ Shto rele",
// Binding picker.
whichBarrier: "Cilën barrierë shërben kjo pajisje?",
controller: "Kontrolluesi",
choose: "Zgjidh…",
relayLabel: "Rele {{relay}} ({{direction}})",
noRelaysConfigured: "Ky kontrollues nuk ka rele të konfiguruar.",
},
subs: {
title: "Abonimet",
unnamed: "(pa emër)",
unbound: "pa kufizim",
car_one: "{{count}} makinë",
car_other: "{{count}} makina",
cred: "kredencial",
plates: "{{count}} targë(a)",
noPrice: "pa çmim",
perMonth: "muaj",
monthlyPrice: "Çmimi mujor",
pricePlaceholder: "p.sh. 10000",
edit: "Ndrysho",
revoke: "Anulo",
delete: "Fshij",
noneYet: "Asnjë abonim ende.",
add: "+ Shto abonim",
new: "Abonim i ri",
editTitle: "Ndrysho abonimin",
holderName: "Emri i mbajtësit",
contact: "Kontakti",
carLimit: "Kufiri i makinave",
limitCarsInAtOnce: "kufizo makinat brenda njëkohësisht",
validFrom: "Vlen nga",
validTo: "Vlen deri",
months: "Muaj",
monthsHint: "muaj të paguar",
coverageHint: "deri më {{end}}",
totalDue: "gjithsej {{total}}",
validToOverride: "Vlen deri (manual)",
isoDateOptional: "Datë ISO (opsionale)",
boundPlates: "Targat e lidhura",
commaSeparatedOptional: "të ndara me presje (opsionale)",
credentials: "Kredencialet",
credentialsCardQr: "Kredencialet (kartë / QR)",
rfCardTag: "Kartë/Tag RF",
rfCardTagSoon: "Kartë/Tag RF (së shpejti)",
rfPlaceholder: "numri i kartës (ose lexo kartën)",
readCard: "Lexo kartën",
captureChooseReader: "Zgjidh lexuesin, pastaj afro kartën:",
captureNoReaders: "Asnjë lexues i konfiguruar.",
captureWaiting: "Afro kartën te lexuesi…",
captureTimeout: "Skadoi pa lexuar kartë. Provo sërish.",
captured: "Karta u lexua: {{value}}",
qr: "QR",
qrAutoGen: "kodi QR gjenerohet automatikisht në ruajtje",
credentialValue: "vlera e kredencialit",
addCredential: "+ kredencial",
needCredentialOrPlate: "Një abonim kërkon të paktën një kredencial OSE një targë të lidhur.",
save: "Ruaj",
cancel: "Anulo",
saved: "Abonimi u ruajt.",
savedPrinted: "Abonimi u ruajt — kodi QR u printua.",
savedPrintFailed: "Abonimi u ruajt, por printimi dështoi ({{error}}). Përdor \"Printo kodin\".",
printCode: "Printo kodin",
printedOn: "Kodi u printua te {{printer}}.",
confirmRevoke: "Të anulohet abonimi për {{name}}? Do të refuzohet te barriera.",
confirmDelete: "Të fshihet abonimi për {{name}}? (t e kaluara ruhen.)",
statusActive: "aktiv",
statusSuspended: "pezulluar",
statusRevoked: "anuluar",
},
site: {
occupancy: "Prania:",
noCapacitySet: "(pa kapacitet të caktuar)",
free: "lirë",
full: "PLOT",
capacityLabel: "Kapaciteti (bosh = pa kufi):",
capacityPlaceholder: "p.sh. 120",
printExitDefault: "Printo biletën e daljes si parazgjedhje",
printExitHint: "(kabina larg daljes → klienti del vetë me biletë)",
parkDetails: "Të dhënat e parkimit (opsionale — shfaqen në bileta/fatura)",
save: "Ruaj",
saved: "U ruajt.",
fieldParkName: "Emri i parkimit",
fieldParkNamePh: "p.sh. Acme Parking",
fieldOperator: "Operatori (emri ligjor)",
fieldOperatorPh: "kompania operuese",
fieldNius: "NIUS",
fieldNiusPh: "p.sh. L01234567A",
fieldAddress: "Adresa",
fieldPhone: "Telefoni",
fieldEmail: "Email",
},
users: {
title: "Përdoruesit",
add: "+ Shto përdorues",
new: "Përdorues i ri",
none: "Asnjë përdorues.",
username: "Përdoruesi",
password: "Fjalëkalimi",
passwordHint: "Të paktën 8 karaktere.",
newPassword: "fjalëkalim i ri",
role: "Roli",
resetPassword: "Rivendos fjalëkalimin",
edit: "Ndrysho",
editTitle: "Ndrysho përdoruesin",
save: "Ruaj",
delete: "Fshi",
confirmDelete: "Të fshihet përdoruesi \"{{name}}\"?",
// Optional profile metadata.
detailsSection: "Të dhënat (opsionale)",
fullName: "Emri i plotë",
phone: "Telefoni",
email: "Email",
address: "Adresa",
},
roles: {
title: "Rolet",
add: "+ Shto rol",
new: "Rol i ri",
editTitle: "Ndrysho rolin",
name: "Emri",
permissions: "Lejet",
builtin: "i integruar",
edit: "Ndrysho",
delete: "Fshi",
confirmDelete: "Të fshihet roli \"{{name}}\"?",
permCount_one: "{{count}} leje",
permCount_other: "{{count}} leje",
userCount_one: "{{count}} përdorues",
userCount_other: "{{count}} përdorues",
},
shift: {
label: "Turni:",
open: "hapur",
notStarted: "i panisur",
since: "që nga",
startShift: "Fillo turnin",
starting: "Duke filluar…",
endShift: "Mbyll turnin",
ending: "Duke mbyllur…",
drawer: "Arka:",
openingFloatInherited: "(bilanci fillestar i trashëguar nga turni i mëparshëm)",
drawerCashAdmin: "Para në arkë (admin) — shto ose hiq bilancin",
amount: "shuma",
reasonPlaceholder: "arsyeja (p.sh. bilanci fillestar)",
load: "Shto +",
remove: "Hiq −",
enterPositive: "Shkruaj një shumë pozitive.",
drawerNow: "Arka tani {{amount}}.",
zReport: "RAPORT Z",
payments: "Pagesa:",
cash: "Para:",
card: "Kartë:",
drawerSection: "— Arka —",
openingFloat: "Bilanci fillestar:",
cashTaken: "Para të marra:",
cashAdded: "Para të shtuara:",
cashRemoved: "Para të hequra:",
expectedDrawer: "Arka e pritshme:",
printedToReceipt: "Printuar te printeri i kabinës.",
recordedNoPrinter: "Regjistruar (pa printer për të printuar).",
// Header shift control + the booth shift gate.
headerNoShift: "Asnjë turn",
headerOpen: "Hap turnin",
headerClose: "Mbyll turnin",
headerHeldBy: "Turn i hapur nga {{operator}}",
headerHeldByShort: "Turni: {{operator}}",
gateTitle: "Hap një turn për të proceduar biletat",
gateBody:
"Asnjë turn nuk është i hapur. Hap turnin tënd që pagesat dhe daljet të regjistrohen te ky turn.",
gateOtherTitle: "Turni i hapur i përket një operatori tjetër",
gateOtherBody:
"{{operator}} ka një turn të hapur. Vetëm një turn mund të jetë i hapur njëkohësisht — ai duhet të mbyllë turnin para se ti të hapësh tëndin.",
openNow: "Hap turnin tani",
opening: "Duke hapur…",
},
shifts: {
title: "Historiku i turneve",
myTitle: "Turnet e mia",
none: "Asnjë turn i mbyllur.",
operator: "Operatori",
started: "Filloi",
ended: "Mbaroi",
payments: "Pagesa",
cash: "Para",
card: "Kartë",
expectedDrawer: "Arka e pritshme",
// Filter (admin only).
filterFrom: "Nga",
filterTo: "Deri",
allOperators: "Të gjithë operatorët",
apply: "Apliko",
clear: "Pastro",
// Expanded drawer detail.
drawerSection: "Arka",
openingFloat: "Bilanci fillestar",
cashTaken: "Para të marra",
cashAdded: "Para të shtuara",
cashRemoved: "Para të hequra",
loadFailed: "Ngarkimi i turneve dështoi.",
},
pay: {
ticket: "Bileta",
entry: "Hyrja",
now: "Tani",
duration: "Kohëzgjatja",
statusLabel: "Statusi",
paid: "PAGUAR",
unpaid: "PAPAGUAR",
total: "Totali",
noTariff: "pa tarifë",
tender: "Mënyra",
cash: "Para",
card: "Kartë",
printExitVoucher: "Printo biletë dalje",
selfExitHint: "(klienti del vetë te dalja)",
payAndOpen: "Paguaj + hap barrierën",
payAndVoucher: "Paguaj + printo biletën",
openBarrier: "Hap barrierën",
printVoucher: "Printo biletën",
takingPayment: "Duke marrë pagesën…",
printingVoucher: "Duke printuar biletën…",
opening: "Duke hapur…",
noSessionFound: "Nuk u gjet asnjë sesion për këtë biletë.",
alreadyClosed: "Ky sesion është mbyllur tashmë (doli {{time}}).",
lookingUp: "Duke kërkuar…",
paidBarrierOpened: "Paguar — barriera u hap. Automjeti mund të dalë.",
paidExitRecorded: "Paguar dhe dalja u regjistrua, por barriera nuk u hap: {{reason}}.",
subscription: "ABONIM",
plan: "Plani",
prepaid: "I PARAPAGUAR",
subAssistHint: "Abonim i parapaguar. Hap barrierën për të ndihmuar daljen (lexues me defekt / kartë e munguar). S'ka pagesë.",
subBarrierOpened: "Barriera u hap për abonentin (ndërhyrje e regjistruar).",
voucherPrinted: "Bileta e daljes u printua në {{printer}}. Klienti del vetë te dalja.",
// payment receipt (transparency slip)
receiptPrintFailed: "(fatura nuk u printua — provoni \"Riprinto faturën\".)",
receiptReprinted: "Fatura u riprintua në {{printer}}.",
reprintReceipt: "Riprinto faturën",
reprinting: "duke printuar…",
// snapshots
noSnapshots: "asnjë foto",
loadingSnapshots: "duke ngarkuar fotot…",
snapEntry: "hyrje",
snapExit: "dalje",
snapFailed: "kamera e paarritshme",
},
};
// The catalog SHAPE (keys + nesting), with string-typed values — so en.ts must
// supply every key but may differ in value. (Not `typeof sq` with `as const`, which
// would pin en.ts to the Albanian literals.)
type Stringify<T> = { [K in keyof T]: T[K] extends object ? Stringify<T[K]> : string };
export type Catalog = Stringify<typeof sq>;