dev 7 months ago
parent 8f422e2b4f
commit 133699153c

@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(npx vitest run src/glob/tests/unit.test.ts)"
]
}
}

@ -25,7 +25,7 @@ export function createGlob<S extends LingNode>(config: GlobConfig<S>): GlobInsta
const numr = createNumr(config.numr ?? {}, getLocale);
const curr = createCurr(config.curr?.definitions ?? {}, ling, config.curr ?? {}, getLocale);
const dati = createDati(config.dati ?? {}, getLocale);
const unit = createUnit(config.unit?.categories ?? {}, config.unit ?? {}, getLocale);
const unit = createUnit(ling,config.unit?.categories ?? {}, config.unit ?? {}, getLocale);
function setLocale(locale: GlobLocale): void {
ling.setLocale(locale as any);

@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest';
import { createGlob } from '@/glob/glob_engine';
import { createLing, p } from '@/ling/engine';
import type { CurrDefinition } from '../curr_types';
import { unitsLingNode } from '../unit_langs';
// =============================================================================
// FIXTURES

@ -1,28 +1,32 @@
import { describe, it, expect } from 'vitest';
import { createUnit } from '../unit_engine';
import { createUnit } from '@/glob/unit_engine';
import { createLing } from '@/ling/engine';
function makeLing(locale = 'es') {
return createLing({} as any, locale as any);
}
describe('createUnit', () => {
describe('selectedSystem', () => {
it('usa metric por defecto', () => {
const unit = createUnit({}, {}, 'es');
const unit = createUnit(makeLing(), {}, {}, 'es');
expect(unit.getSystem()).toBe('metric');
});
it('acepta imperial en la configuración', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.getSystem()).toBe('imperial');
});
it('setSystem cambia el sistema activo', () => {
const unit = createUnit({}, {}, 'es');
const unit = createUnit(makeLing(), {}, {}, 'es');
unit.setSystem('imperial');
expect(unit.getSystem()).toBe('imperial');
});
it('el sistema reactivo afecta a f()', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.f(33)).toContain('kg');
unit.setSystem('imperial');
@ -34,11 +38,13 @@ describe('createUnit', () => {
describe('locale reactivo via función', () => {
it('responde a cambios de locale sin recrear el módulo', () => {
const ling = makeLing('es');
let locale = 'es';
const unit = createUnit({}, { selectedSystem: 'metric' }, () => locale);
const unit = createUnit(ling, {}, { selectedSystem: 'metric' }, () => locale);
expect(unit.weight.f(1, 'name')).toBe('1 kilogramo');
locale = 'en';
ling.setLocale('en' as any);
expect(unit.weight.f(1, 'name')).toBe('1 kilogram');
});
@ -51,64 +57,59 @@ describe('createUnit', () => {
describe('weight', () => {
it('f() metric — unidad base kg', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.f(33)).toContain('kg');
});
it('f() imperial — unidad base lb', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.weight.f(33)).toContain('lb');
});
it('f() display name singular en es', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing('es'), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.f(1, 'name')).toBe('1 kilogramo');
});
it('f() display name plural en es', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing('es'), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.f(5, 'name')).toBe('5 kilogramos');
});
it('f() display name en en', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'en');
const unit = createUnit(makeLing('en'), {}, { selectedSystem: 'metric' }, 'en');
expect(unit.weight.f(1, 'name')).toBe('1 kilogram');
});
it('f() display name imperial en en', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'en');
const unit = createUnit(makeLing('en'), {}, { selectedSystem: 'imperial' }, 'en');
expect(unit.weight.f(1, 'name')).toBe('1 pound');
});
it('fTo() convierte kg a lb', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'en');
const unit = createUnit(makeLing('en'), {}, { selectedSystem: 'imperial' }, 'en');
const result = unit.weight.fTo(1);
expect(result).toContain('lb');
expect(result).toContain('2.2');
});
it('fTo() con unidad destino explícita', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.fTo(1, 'oz')).toContain('oz');
});
it('fTo() sin conversión en metric devuelve kg', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
expect(unit.weight.fTo(33)).toContain('kg');
});
it('convert() kg a lb', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.weight.convert(1, 'kg', 'lb')).toBeCloseTo(2.2046, 2);
});
it('convert() kg a g', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.weight.convert(1, 'kg', 'g')).toBeCloseTo(1000, 0);
});
it('convert() misma unidad', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.weight.convert(33, 'kg', 'kg')).toBe(33);
});
@ -121,22 +122,22 @@ describe('createUnit', () => {
describe('length', () => {
it('f() metric — unidad base m', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.length.f(100)).toContain('m');
});
it('f() imperial — unidad base mi', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.length.f(1609)).toContain('mi');
});
it('convert() m a km', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.length.convert(1000, 'm', 'km')).toBeCloseTo(1, 5);
});
it('convert() km a mi', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.length.convert(1.609344, 'km', 'mi')).toBeCloseTo(1, 3);
});
@ -149,34 +150,34 @@ describe('createUnit', () => {
describe('temp', () => {
it('f() metric — °C', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.temp.f(100)).toContain('°C');
});
it('f() imperial — °F', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.temp.f(212)).toContain('°F');
});
it('fTo() convierte °C a °F', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
const result = unit.temp.fTo(100);
expect(result).toContain('°F');
expect(result).toContain('212');
});
it('convert() °C a °F', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.temp.convert(100, 'c', 'f')).toBeCloseTo(212, 0);
});
it('convert() °C a K', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.temp.convert(0, 'c', 'k')).toBeCloseTo(273.15, 1);
});
it('convert() °F a °C', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.temp.convert(32, 'f', 'c')).toBeCloseTo(0, 1);
});
@ -189,22 +190,22 @@ describe('createUnit', () => {
describe('volume', () => {
it('f() metric — l', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.volume.f(2)).toContain('l');
});
it('f() imperial — gal', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.volume.f(2)).toContain('gal');
});
it('convert() l a ml', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.volume.convert(1, 'l', 'ml')).toBeCloseTo(1000, 0);
});
it('convert() l a gal', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.volume.convert(3.78541, 'l', 'gal')).toBeCloseTo(1, 2);
});
@ -217,25 +218,25 @@ describe('createUnit', () => {
describe('area', () => {
it('f() metric — m²', () => {
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'metric' }, 'es');
expect(unit.area.f(100)).toContain('m²');
});
it('f() imperial — ft²', () => {
const unit = createUnit({}, { selectedSystem: 'imperial' }, 'es');
const unit = createUnit(makeLing(), {}, { selectedSystem: 'imperial' }, 'es');
expect(unit.area.f(100)).toContain('ft²');
});
it('convert() m² a ft²', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.area.convert(1, 'm2', 'ft2')).toBeCloseTo(10.7639, 2);
});
it('convert() ha a m²', () => {
const unit = createUnit();
const unit = createUnit(makeLing());
expect(unit.area.convert(1, 'ha', 'm2')).toBeCloseTo(10000, 0);
});
});
});
});

@ -0,0 +1,71 @@
import type { UnitCategoryKey, UnitCategoryData } from './unit_types';
/**
* Datos numéricos de las unidades — factores de conversión y sistemas.
* Las traducciones (symbol, name) viven en units_lang.ts → ling.
*/
export const unitData: Record<UnitCategoryKey, UnitCategoryData> = {
weight: {
base : 'kg',
baseImperial: 'lb',
units: {
kg: { factor: 1, system: 'metric' },
g: { factor: 0.001, system: 'metric' },
lb: { factor: 0.453592, system: 'imperial' },
oz: { factor: 0.0283495, system: 'imperial' },
t: { factor: 1000, system: 'metric' },
},
},
length: {
base : 'm',
baseImperial: 'mi',
units: {
km: { factor: 1000, system: 'metric' },
m: { factor: 1, system: 'metric' },
cm: { factor: 0.01, system: 'metric' },
mm: { factor: 0.001, system: 'metric' },
mi: { factor: 1609.344, system: 'imperial' },
ft: { factor: 0.3048, system: 'imperial' },
in: { factor: 0.0254, system: 'imperial' },
yd: { factor: 0.9144, system: 'imperial' },
},
},
temp: {
base : 'c',
baseImperial: 'f',
units: {
c: { factor: 1, system: 'metric' },
f: { factor: 1, system: 'imperial' },
k: { factor: 1, system: 'metric' },
},
},
volume: {
base : 'l',
baseImperial: 'gal',
units: {
l: { factor: 1, system: 'metric' },
ml: { factor: 0.001, system: 'metric' },
m3: { factor: 1000, system: 'metric' },
gal: { factor: 3.78541, system: 'imperial' },
floz: { factor: 0.029574, system: 'imperial' },
pt: { factor: 0.473176, system: 'imperial' },
},
},
area: {
base : 'm2',
baseImperial: 'ft2',
units: {
m2: { factor: 1, system: 'metric' },
km2: { factor: 1000000, system: 'metric' },
ha: { factor: 10000, system: 'metric' },
ft2: { factor: 0.092903, system: 'imperial' },
mi2: { factor: 2589988.11, system: 'imperial' },
ac: { factor: 4046.856, system: 'imperial' },
},
},
};

@ -1,33 +1,34 @@
import type { LocaleResolver, GlobLocale } from "./comn_types";
import type { UnitCategory, UnitCategoryInstance, UnitCategoryKey, UnitDisplay, UnitInstance, UnitSystem } from "./unit_types";
import type { UnitCategoryData, UnitCategoryInstance, UnitCategoryKey, UnitDisplay, UnitInstance, UnitSystem } from "./unit_types";
import { resolveLocale } from "./comn_engine";
import { defaultCategories } from "./unit_langs";
import { defaultISOLocale } from "@/ling/consts";
import { type LingNode, type LingInstance, ling } from "../ling";
import { unitData } from "./unit_data";
import { unitsLingNode } from "./unit_langs";
// ==============================
// CONVERSIÓN DE TEMPERATURA
// ==============================
/**
* Convierte temperatura entre Celsius, Fahrenheit y Kelvin.
* Usa Celsius como pivote intermedio para todas las conversiones.
*/
function convertTemp(value: number, from: string, to: string): number {
if (from === to) return value;
let celsius: number;
let c: number;
switch (from) {
case 'c': celsius = value; break;
case 'f': celsius = (value - 32) * 5 / 9; break;
case 'k': celsius = value - 273.15; break;
default: celsius = value;
case 'c': c = value; break;
case 'f': c = (value - 32) * 5 / 9; break;
case 'k': c = value - 273.15; break;
default: c = value;
}
switch (to) {
case 'c': return celsius;
case 'f': return celsius * 9 / 5 + 32;
case 'k': return celsius + 273.15;
default: return celsius;
case 'c': return c;
case 'f': return c * 9 / 5 + 32;
case 'k': return c + 273.15;
default: return c;
}
}
@ -35,68 +36,48 @@ function convertTemp(value: number, from: string, to: string): number {
// CATEGORY ENGINE
// ==============================
/**
* Crea las funciones de formato y conversión para una categoría de unidades.
* El sistema activo se lee en cada llamada via `getSystem()`, por lo que
* responde a `setSystem()` sin necesidad de recrear la categoría.
*/
function createCategory(
function createCategory<S extends LingNode>(
categoryKey : UnitCategoryKey,
category : UnitCategory,
category : UnitCategoryData,
getSystem : () => UnitSystem,
defaultLocale: LocaleResolver,
ling : LingInstance<S>,
): UnitCategoryInstance {
/** Devuelve la unidad base según el sistema activo en este momento. */
function resolveTargetUnit(): string {
return getSystem() === 'imperial' ? category.baseImperial : category.base;
}
/** Formatea `value` en la unidad `unitKey` con símbolo o nombre localizado. */
function formatValue(value: number, unitKey: string, display: UnitDisplay, locale: GlobLocale): string {
const def = category.units[unitKey];
if (!def) return `${value} ${unitKey}`;
const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);
const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }).format(value);
// Extrae solo el código de idioma (sin región) para buscar en symbol/name.
const baseLocale = locale.split('-')[0] as keyof typeof def.symbol;
if (display === 'symbol') {
const symbol = def.symbol[baseLocale] ?? def.symbol['es'];
return `${formatted} ${symbol}`;
if (display === 'abbr') {
// Lee símbolo de ling: units.weight.kg.abbr
const abbr = ling.t(`units.${categoryKey}.${unitKey}.abbr` as any);
return `${formatted} ${abbr}`;
}
const record = def.name({ count: value });
const name = record[baseLocale as keyof typeof record] ?? record['es'];
// Lee nombre pluralizado de ling: units.weight.kg.name
const name = ling.t(`units.${categoryKey}.${unitKey}.name` as any, { count: value });
return `${formatted} ${name}`;
}
/**
* Conversión numérica entre dos unidades de la categoría.
* Para temperatura delega en `convertTemp` (conversión no lineal).
* Para el resto: `resultado = (valor × factorOrigen) / factorDestino`
*/
function convert(value: number, from: string, to: string): number {
if (from === to) return value;
if (categoryKey === 'temp') return convertTemp(value, from, to);
const fromDef = category.units[from];
const toDef = category.units[to];
if (!fromDef || !toDef) return value;
return (value * fromDef.factor) / toDef.factor;
const fromData = category.units[from];
const toData = category.units[to];
if (!fromData || !toData) return value;
return (value * fromData.factor) / toData.factor;
}
/** Formatea `value` en la unidad base del sistema activo. */
function f(value: number, display: UnitDisplay = 'symbol', locale: LocaleResolver = defaultLocale): string {
function f(value: number, display: UnitDisplay = 'abbr', locale: LocaleResolver = defaultLocale): string {
const loc = resolveLocale(locale);
const unit = resolveTargetUnit();
return formatValue(value, unit, display, loc);
}
/**
* Convierte `value` desde la base métrica de la categoría hacia `targetUnit`
* y lo formatea. Si se omite `targetUnit`, usa la base del sistema activo.
*/
function fTo(value: number, targetUnit?: string, display: UnitDisplay = 'symbol', locale: LocaleResolver = defaultLocale): string {
function fTo(value: number, targetUnit?: string, display: UnitDisplay = 'abbr', locale: LocaleResolver = defaultLocale): string {
const loc = resolveLocale(locale);
const to = targetUnit ?? resolveTargetUnit();
const converted = convert(value, category.base, to);
@ -110,35 +91,28 @@ function createCategory(
// UNIT ENGINE
// ==============================
/**
* Crea un módulo de unidades de medida con conversión y formateo.
*
* Las categorías pasadas en `categories` sobreescriben las de `defaultCategories`,
* lo que permite reemplazar categorías completas sin tocar el resto.
*
* @param categories - Categorías personalizadas (parcial).
* @param opts - Sistema activo inicial.
* @param defaultLocale - Locale o resolver para todas las categorías.
*/
export function createUnit(
categories : Partial<Record<UnitCategoryKey, UnitCategory>> = {},
export function createUnit<S extends LingNode>(
ling : LingInstance<S>,
categories : Partial<Record<UnitCategoryKey, UnitCategoryData>> = {},
opts : { selectedSystem?: UnitSystem } = {},
defaultLocale: LocaleResolver = defaultISOLocale
defaultLocale: LocaleResolver = 'es'
): UnitInstance {
// El módulo registra sus propias traducciones en ling
ling.extend('units', unitsLingNode);
let currentSystem: UnitSystem = opts.selectedSystem ?? 'metric';
function getSystem(): UnitSystem { return currentSystem; }
function setSystem(s: UnitSystem) { currentSystem = s; }
// Las categorías personalizadas sobreescriben las definiciones por defecto.
const merged: Record<UnitCategoryKey, UnitCategory> = {
...defaultCategories,
const merged: Record<UnitCategoryKey, UnitCategoryData> = {
...unitData,
...categories,
};
const makeCategory = (key: UnitCategoryKey) =>
createCategory(key, merged[key], getSystem, defaultLocale);
createCategory(key, merged[key], getSystem, defaultLocale, ling);
return {
getSystem,

@ -6,45 +6,45 @@ import type { LingNode } from '@/ling/types';
* Se registra en ling bajo el nodo 'units' via glob_engine.
* Los datos numéricos (factor, system, base) viven en units_data.ts.
*/
export const unitsSchema = {
export const unitsLingNode = {
weight: {
kg: { symbol: { es: 'kg', en: 'kg' }, name: p({ es: { one: 'kilogramo', other: 'kilogramos' }, en: { one: 'kilogram', other: 'kilograms' } }) },
g: { symbol: { es: 'g', en: 'g' }, name: p({ es: { one: 'gramo', other: 'gramos' }, en: { one: 'gram', other: 'grams' } }) },
lb: { symbol: { es: 'lb', en: 'lb' }, name: p({ es: { one: 'libra', other: 'libras' }, en: { one: 'pound', other: 'pounds' } }) },
oz: { symbol: { es: 'oz', en: 'oz' }, name: p({ es: { one: 'onza', other: 'onzas' }, en: { one: 'ounce', other: 'ounces' } }) },
t: { symbol: { es: 't', en: 't' }, name: p({ es: { one: 'tonelada', other: 'toneladas' }, en: { one: 'tonne', other: 'tonnes' } }) },
kg: { abbr: { es: 'kg', en: 'kg' }, name: p({ es: { one: 'kilogramo', other: 'kilogramos' }, en: { one: 'kilogram', other: 'kilograms' } }) },
g: { abbr: { es: 'g', en: 'g' }, name: p({ es: { one: 'gramo', other: 'gramos' }, en: { one: 'gram', other: 'grams' } }) },
lb: { abbr: { es: 'lb', en: 'lb' }, name: p({ es: { one: 'libra', other: 'libras' }, en: { one: 'pound', other: 'pounds' } }) },
oz: { abbr: { es: 'oz', en: 'oz' }, name: p({ es: { one: 'onza', other: 'onzas' }, en: { one: 'ounce', other: 'ounces' } }) },
t: { abbr: { es: 't', en: 't' }, name: p({ es: { one: 'tonelada', other: 'toneladas' }, en: { one: 'tonne', other: 'tonnes' } }) },
},
length: {
km: { symbol: { es: 'km', en: 'km' }, name: p({ es: { one: 'kilómetro', other: 'kilómetros' }, en: { one: 'kilometer', other: 'kilometers' } }) },
m: { symbol: { es: 'm', en: 'm' }, name: p({ es: { one: 'metro', other: 'metros' }, en: { one: 'meter', other: 'meters' } }) },
cm: { symbol: { es: 'cm', en: 'cm' }, name: p({ es: { one: 'centímetro', other: 'centímetros' }, en: { one: 'centimeter', other: 'centimeters' } }) },
mm: { symbol: { es: 'mm', en: 'mm' }, name: p({ es: { one: 'milímetro', other: 'milímetros' }, en: { one: 'millimeter', other: 'millimeters' } }) },
mi: { symbol: { es: 'mi', en: 'mi' }, name: p({ es: { one: 'milla', other: 'millas' }, en: { one: 'mile', other: 'miles' } }) },
ft: { symbol: { es: 'ft', en: 'ft' }, name: p({ es: { one: 'pie', other: 'pies' }, en: { one: 'foot', other: 'feet' } }) },
in: { symbol: { es: 'in', en: 'in' }, name: p({ es: { one: 'pulgada', other: 'pulgadas' }, en: { one: 'inch', other: 'inches' } }) },
yd: { symbol: { es: 'yd', en: 'yd' }, name: p({ es: { one: 'yarda', other: 'yardas' }, en: { one: 'yard', other: 'yards' } }) },
km: { abbr: { es: 'km', en: 'km' }, name: p({ es: { one: 'kilómetro', other: 'kilómetros' }, en: { one: 'kilometer', other: 'kilometers' } }) },
m: { abbr: { es: 'm', en: 'm' }, name: p({ es: { one: 'metro', other: 'metros' }, en: { one: 'meter', other: 'meters' } }) },
cm: { abbr: { es: 'cm', en: 'cm' }, name: p({ es: { one: 'centímetro', other: 'centímetros' }, en: { one: 'centimeter', other: 'centimeters' } }) },
mm: { abbr: { es: 'mm', en: 'mm' }, name: p({ es: { one: 'milímetro', other: 'milímetros' }, en: { one: 'millimeter', other: 'millimeters' } }) },
mi: { abbr: { es: 'mi', en: 'mi' }, name: p({ es: { one: 'milla', other: 'millas' }, en: { one: 'mile', other: 'miles' } }) },
ft: { abbr: { es: 'ft', en: 'ft' }, name: p({ es: { one: 'pie', other: 'pies' }, en: { one: 'foot', other: 'feet' } }) },
in: { abbr: { es: 'in', en: 'in' }, name: p({ es: { one: 'pulgada', other: 'pulgadas' }, en: { one: 'inch', other: 'inches' } }) },
yd: { abbr: { es: 'yd', en: 'yd' }, name: p({ es: { one: 'yarda', other: 'yardas' }, en: { one: 'yard', other: 'yards' } }) },
},
temp: {
c: { symbol: { es: '°C', en: '°C' }, name: p({ es: { one: 'grado Celsius', other: 'grados Celsius' }, en: { one: 'degree Celsius', other: 'degrees Celsius' } }) },
f: { symbol: { es: '°F', en: '°F' }, name: p({ es: { one: 'grado Fahrenheit', other: 'grados Fahrenheit' }, en: { one: 'degree Fahrenheit', other: 'degrees Fahrenheit' } }) },
k: { symbol: { es: 'K', en: 'K' }, name: p({ es: { one: 'kelvin', other: 'kelvins' }, en: { one: 'kelvin', other: 'kelvins' } }) },
c: { abbr: { es: '°C', en: '°C' }, name: p({ es: { one: 'grado Celsius', other: 'grados Celsius' }, en: { one: 'degree Celsius', other: 'degrees Celsius' } }) },
f: { abbr: { es: '°F', en: '°F' }, name: p({ es: { one: 'grado Fahrenheit', other: 'grados Fahrenheit' }, en: { one: 'degree Fahrenheit', other: 'degrees Fahrenheit' } }) },
k: { abbr: { es: 'K', en: 'K' }, name: p({ es: { one: 'kelvin', other: 'kelvins' }, en: { one: 'kelvin', other: 'kelvins' } }) },
},
volume: {
l: { symbol: { es: 'l', en: 'l' }, name: p({ es: { one: 'litro', other: 'litros' }, en: { one: 'liter', other: 'liters' } }) },
ml: { symbol: { es: 'ml', en: 'ml' }, name: p({ es: { one: 'mililitro', other: 'mililitros' }, en: { one: 'milliliter', other: 'milliliters' } }) },
m3: { symbol: { es: 'm³', en: 'm³' }, name: p({ es: { one: 'metro cúbico', other: 'metros cúbicos'}, en: { one: 'cubic meter', other: 'cubic meters' } }) },
gal: { symbol: { es: 'gal', en: 'gal' }, name: p({ es: { one: 'galón', other: 'galones' }, en: { one: 'gallon', other: 'gallons' } }) },
floz: { symbol: { es: 'fl oz', en: 'fl oz' }, name: p({ es: { one: 'onza líquida', other: 'onzas líquidas'}, en: { one: 'fluid ounce', other: 'fluid ounces' } }) },
pt: { symbol: { es: 'pt', en: 'pt' }, name: p({ es: { one: 'pinta', other: 'pintas' }, en: { one: 'pint', other: 'pints' } }) },
l: { abbr: { es: 'l', en: 'l' }, name: p({ es: { one: 'litro', other: 'litros' }, en: { one: 'liter', other: 'liters' } }) },
ml: { abbr: { es: 'ml', en: 'ml' }, name: p({ es: { one: 'mililitro', other: 'mililitros' }, en: { one: 'milliliter', other: 'milliliters' } }) },
m3: { abbr: { es: 'm³', en: 'm³' }, name: p({ es: { one: 'metro cúbico', other: 'metros cúbicos'}, en: { one: 'cubic meter', other: 'cubic meters' } }) },
gal: { abbr: { es: 'gal', en: 'gal' }, name: p({ es: { one: 'galón', other: 'galones' }, en: { one: 'gallon', other: 'gallons' } }) },
floz: { abbr: { es: 'fl oz', en: 'fl oz' }, name: p({ es: { one: 'onza líquida', other: 'onzas líquidas'}, en: { one: 'fluid ounce', other: 'fluid ounces' } }) },
pt: { abbr: { es: 'pt', en: 'pt' }, name: p({ es: { one: 'pinta', other: 'pintas' }, en: { one: 'pint', other: 'pints' } }) },
},
area: {
m2: { symbol: { es: 'm²', en: 'm²' }, name: p({ es: { one: 'metro cuadrado', other: 'metros cuadrados' }, en: { one: 'square meter', other: 'square meters' } }) },
km2: { symbol: { es: 'km²', en: 'km²' }, name: p({ es: { one: 'kilómetro cuadrado', other: 'kilómetros cuadrados' }, en: { one: 'square kilometer', other: 'square kilometers' } }) },
ha: { symbol: { es: 'ha', en: 'ha' }, name: p({ es: { one: 'hectárea', other: 'hectáreas' }, en: { one: 'hectare', other: 'hectares' } }) },
ft2: { symbol: { es: 'ft²', en: 'ft²' }, name: p({ es: { one: 'pie cuadrado', other: 'pies cuadrados' }, en: { one: 'square foot', other: 'square feet' } }) },
mi2: { symbol: { es: 'mi²', en: 'mi²' }, name: p({ es: { one: 'milla cuadrada', other: 'millas cuadradas' }, en: { one: 'square mile', other: 'square miles' } }) },
ac: { symbol: { es: 'ac', en: 'ac' }, name: p({ es: { one: 'acre', other: 'acres' }, en: { one: 'acre', other: 'acres' } }) },
m2: { abbr: { es: 'm²', en: 'm²' }, name: p({ es: { one: 'metro cuadrado', other: 'metros cuadrados' }, en: { one: 'square meter', other: 'square meters' } }) },
km2: { abbr: { es: 'km²', en: 'km²' }, name: p({ es: { one: 'kilómetro cuadrado', other: 'kilómetros cuadrados' }, en: { one: 'square kilometer', other: 'square kilometers' } }) },
ha: { abbr: { es: 'ha', en: 'ha' }, name: p({ es: { one: 'hectárea', other: 'hectáreas' }, en: { one: 'hectare', other: 'hectares' } }) },
ft2: { abbr: { es: 'ft²', en: 'ft²' }, name: p({ es: { one: 'pie cuadrado', other: 'pies cuadrados' }, en: { one: 'square foot', other: 'square feet' } }) },
mi2: { abbr: { es: 'mi²', en: 'mi²' }, name: p({ es: { one: 'milla cuadrada', other: 'millas cuadradas' }, en: { one: 'square mile', other: 'square miles' } }) },
ac: { abbr: { es: 'ac', en: 'ac' }, name: p({ es: { one: 'acre', other: 'acres' }, en: { one: 'acre', other: 'acres' } }) },
},
} satisfies LingNode;
export type UnitsSchema = typeof unitsSchema;
export type UnitsLingNode = typeof unitsLingNode;

@ -5,57 +5,29 @@
import type { LingPluralFn, LingRecord } from "../ling";
import type { LocaleResolver } from "./comn_types";
/** Cómo se muestra la unidad: símbolo abreviado o nombre completo. */
export type UnitDisplay = 'symbol' | 'name';
/** Sistema de medida activo. Determina la unidad base usada en `f()`. */
export type UnitDisplay = 'abbr' | 'name';
export type UnitSystem = 'metric' | 'imperial';
/** Definición de una unidad dentro de una categoría. */
export interface UnitDefinition {
/** Símbolo localizado, ej. `{ es: 'kg', en: 'kg' }`. */
symbol : LingRecord;
/** Función plural bilingüe para el nombre de la unidad. */
name : LingPluralFn<Record<never, never>>;
/**
* Factor de conversión respecto a la base de la categoría.
* Fórmula: `resultado = (valor × factorOrigen) / factorDestino`
* Excepción: `temp` no usa factores (conversión no lineal).
*/
export type UnitCategoryKey = 'weight' | 'length' | 'temp' | 'volume' | 'area';
/** Solo datos numéricos — sin texto, sin traducciones */
export interface UnitData {
factor : number;
system : UnitSystem;
}
/** Configuración de `createUnit`. */
export interface UnitConfig {
/** Sistema activo al iniciar. Default: `'metric'`. */
selectedSystem?: UnitSystem;
/** Categorías personalizadas que sobreescriben las de `defaultCategories`. */
categories?: Partial<Record<UnitCategoryKey, UnitCategory>>;
}
/** Definición de una categoría de unidades (peso, longitud, etc.). */
export interface UnitCategory {
/** Unidad base del sistema métrico (ej. `'kg'`, `'m'`). */
/** Datos de categoría — base y factores de conversión */
export interface UnitCategoryData {
base : string;
/** Unidad base del sistema imperial (ej. `'lb'`, `'mi'`). */
baseImperial : string;
units : Record<string, UnitDefinition>;
units : Record<string, UnitData>;
}
/** Categorías de unidades disponibles. */
export type UnitCategoryKey = 'weight' | 'length' | 'temp' | 'volume' | 'area';
export interface UnitCategoryInstance {
/** Formatea `value` en la unidad base del sistema activo. */
f : (value: number, display?: UnitDisplay, locale?: LocaleResolver) => string;
/**
* Convierte `value` desde la base métrica hacia `targetUnit` y lo formatea.
* Si se omite `targetUnit`, usa la base del sistema activo.
*/
fTo : (value: number, targetUnit?: string, display?: UnitDisplay, locale?: LocaleResolver) => string;
/** Conversión numérica pura entre dos unidades de la categoría. Sin formateo. */
convert: (value: number, from: string, to: string) => number;
}

Loading…
Cancel
Save

Powered by TurnKey Linux.