/** * Enhetsvisning och -tolkning i appen (i18n-spec §9, §11). * Canonical koder (GRAM, TABLESPOON …) kommer från API:t; här översätts de till * lokala förkortningar och tolkas tillbaka från användarens fritext. * Måttsystem-konvertering (M5) sker via displayQuantity i shared-types. * DB-tabellen unit_translations är källan för fler språk (API: /v1/i18n/units). */ import { displayQuantity, type MeasurementSystem, type Unit } from "@app/shared-types"; const LABELS: Record> = { sv: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "tsk", TABLESPOON: "msk", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "st", PORTION: "portion", PINCH: "krm", SLICE: "skiva", CLOVE: "klyfta", CAN: "burk", PACKAGE: "paket", }, en: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "tsp", TABLESPOON: "tbsp", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "pcs", PORTION: "serving", PINCH: "pinch", SLICE: "slice", CLOVE: "clove", CAN: "can", PACKAGE: "pack", }, es: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "cdta", TABLESPOON: "cda", CUP_US: "taza", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "ud", PORTION: "ración", PINCH: "pizca", SLICE: "rebanada", CLOVE: "diente", CAN: "lata", PACKAGE: "paquete", }, it: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "cucchiaino", TABLESPOON: "cucchiaio", CUP_US: "tazza", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "pz", PORTION: "porzione", PINCH: "pizzico", SLICE: "fetta", CLOVE: "spicchio", CAN: "lattina", PACKAGE: "confezione", }, de: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "TL", TABLESPOON: "EL", CUP_US: "Cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "Stk", PORTION: "Portion", PINCH: "Prise", SLICE: "Scheibe", CLOVE: "Zehe", CAN: "Dose", PACKAGE: "Packung", }, fr: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "c. à c.", TABLESPOON: "c. à s.", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "pcs", PORTION: "portion", PINCH: "pincée", SLICE: "tranche", CLOVE: "gousse", CAN: "boîte", PACKAGE: "paquet", }, da: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "tsk", TABLESPOON: "spsk", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "stk", PORTION: "portion", PINCH: "knsp", SLICE: "skive", CLOVE: "fed", CAN: "dåse", PACKAGE: "pakke", }, nb: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "ts", TABLESPOON: "ss", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "stk", PORTION: "porsjon", PINCH: "knivsodd", SLICE: "skive", CLOVE: "båt", CAN: "boks", PACKAGE: "pakke", }, fi: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "tl", TABLESPOON: "rkl", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "kpl", PORTION: "annos", PINCH: "hyppysellinen", SLICE: "viipale", CLOVE: "kynsi", CAN: "tölkki", PACKAGE: "paketti", }, nl: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "tl", TABLESPOON: "el", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "st", PORTION: "portie", PINCH: "snufje", SLICE: "plak", CLOVE: "teentje", CAN: "blik", PACKAGE: "pak", }, pl: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "łyżeczka", TABLESPOON: "łyżka", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "szt.", PORTION: "porcja", PINCH: "szczypta", SLICE: "plaster", CLOVE: "ząbek", CAN: "puszka", PACKAGE: "opakowanie", }, pt: { GRAM: "g", KILOGRAM: "kg", MILLILITER: "ml", DECILITER: "dl", LITER: "l", TEASPOON: "c. chá", TABLESPOON: "c. sopa", CUP_US: "cup", FLUID_OUNCE_US: "fl oz", OUNCE: "oz", POUND: "lb", COUNT: "un", PORTION: "dose", PINCH: "pitada", SLICE: "fatia", CLOVE: "dente", CAN: "lata", PACKAGE: "embalagem", }, }; let currentLanguage = "sv"; export function setUnitLanguage(language: string): void { currentLanguage = LABELS[language] ? language : "sv"; } /** Kod → lokal etikett ("GRAM" → "g", "TABLESPOON" → "msk"). */ export function unitLabel(unitCode: string): string { return LABELS[currentLanguage]?.[unitCode] ?? LABELS.sv?.[unitCode] ?? unitCode.toLowerCase(); } /** Fritext → kod ("msk" → TABLESPOON, "tbsp" → TABLESPOON, "st" → COUNT …). */ const PARSE: Record = { g: "GRAM", gram: "GRAM", grams: "GRAM", kg: "KILOGRAM", kilo: "KILOGRAM", ml: "MILLILITER", milliliter: "MILLILITER", dl: "DECILITER", deciliter: "DECILITER", l: "LITER", liter: "LITER", litre: "LITER", tsk: "TEASPOON", tsp: "TEASPOON", teaspoon: "TEASPOON", msk: "TABLESPOON", tbsp: "TABLESPOON", tablespoon: "TABLESPOON", krm: "MILLILITER", // 1 krm = 1 ml (dokumenterad standard) cup: "CUP_US", cups: "CUP_US", "fl oz": "FLUID_OUNCE_US", floz: "FLUID_OUNCE_US", oz: "OUNCE", ounce: "OUNCE", lb: "POUND", lbs: "POUND", pound: "POUND", st: "COUNT", styck: "COUNT", pcs: "COUNT", pc: "COUNT", stk: "COUNT", portion: "PORTION", portioner: "PORTION", serving: "PORTION", nypa: "PINCH", pinch: "PINCH", skiva: "SLICE", skivor: "SLICE", slice: "SLICE", klyfta: "CLOVE", klyftor: "CLOVE", clove: "CLOVE", burk: "CAN", can: "CAN", paket: "PACKAGE", pkt: "PACKAGE", pack: "PACKAGE", package: "PACKAGE", }; export function parseUnitInput(text: string): string | null { const key = text.trim().toLowerCase(); if (!key) return null; if (LABELS.sv?.[text.trim().toUpperCase()]) return text.trim().toUpperCase(); return PARSE[key] ?? null; } /** Användarens måttsystem (i18n M5) – sätts från locale-preferenserna. */ let currentSystem: MeasurementSystem = "METRIC"; export function setMeasurementSystem(system: MeasurementSystem): void { currentSystem = system; } /** * Kanonisk mängd -> visningssträng i användarens måttsystem (i18n-spec §9–10). * Lagringen är alltid kanonisk; endast visningen konverterar. "≈" markerar * konverterade (approximativa) värden – ärlighet före skenprecision. */ export function formatQuantity(quantity: number, unitCode: string): string { const display = KNOWN_UNITS.has(unitCode) ? displayQuantity(quantity, unitCode as Unit, currentSystem) : { value: quantity, unit: unitCode, approximate: false }; const v = display.value; const rounded = Number.isInteger(v) ? String(v) : String(Math.round(v * 100) / 100); const prefix = display.approximate ? "≈" : ""; return `${prefix}${rounded} ${unitLabel(String(display.unit))}`; } const KNOWN_UNITS = new Set(Object.keys(LABELS.sv ?? {}));