Files
Cibello-app/apps/mobile/src/lib/units.ts
T
2026-08-05 19:21:11 +07:00

346 lines
7.7 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.
/**
* 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<string, Record<string, string>> = {
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<string, string> = {
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 §910).
* 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 ?? {}));