11 KiB
glob — Módulo de globalización
glob es la capa de globalización del proyecto. Agrupa cuatro módulos de formateo y conversión —número, moneda, fecha/hora y unidades— bajo un único punto de entrada con locale compartido.
Índice
- Diseño
- LocaleResolver
- createGlob
- numr — Números
- curr — Moneda
- dati — Fecha y hora
- unit — Unidades
- Referencia de tipos
Diseño
Principios
- Un locale, todos los módulos. El locale vive en la instancia
ling. Cuando se llama aglob.setLocale(), todos los módulos formatean con el nuevo locale de forma automática, sin necesidad de recrearlos. - Módulos independientes. Cada módulo (
numr,curr,dati,unit) puede instanciarse de forma autónoma concreateNumr,createCurr, etc., sin necesitarcreateGlob. - Locale reactivo. Cada función
f()acepta unLocaleResolveropcional. Si se omite, usa el default configurado al crear la instancia (que puede ser una función que lee el locale en tiempo de ejecución). - Fábricas puras. No hay clases ni singletons. Cada llamada a
createX()devuelve un objeto cerrado con su estado interno.
Estructura de archivos
src/glob/
├── comn_types.ts — GlobLocale, LocaleResolver
├── comn_engine.ts — resolveLocale()
├── glob_types.ts — GlobConfig, GlobInstance
├── glob_engine.ts — createGlob()
├── numr_types.ts — NumrConfig, NumrInstance
├── numr_engine.ts — createNumr()
├── curr_types.ts — CurrDefinition, CurrConfig, CurrInstance
├── curr_engine.ts — createCurr()
├── dati_types.ts — DatiConfig, DatiInstance, DatiOptions
├── dati_engine.ts — createDati()
├── unit_types.ts — UnitCategory, UnitConfig, UnitInstance
├── unit_engine.ts — createUnit()
└── unit_langs.ts — defaultCategories (definiciones por defecto)
Flujo de locale en createGlob
glob.setLocale('en')
└─▶ ling.setLocale('en') // fuente de verdad
└─▶ listeners.forEach(fn => fn()) // notifica suscriptores externos
glob.numr.f(1234)
└─▶ resolveLocale(getLocale) // llama a ling.getLocale() en cada f()
└─▶ Intl.NumberFormat('en').format(1234)
Los módulos reciben getLocale (una función () => GlobLocale) como defaultLocale, por lo que leen el locale actual en cada invocación sin necesidad de reconectarse.
LocaleResolver
type LocaleResolver = GlobLocale | (() => GlobLocale);
Todos los métodos f() aceptan un LocaleResolver como último parámetro. Puede ser:
| Forma | Cuándo usarla |
|---|---|
'es' |
Locale fijo en esa llamada concreta |
() => user.locale |
Locale dinámico que se lee en cada llamada |
| (omitido) | Usa el defaultLocale configurado al crear la instancia |
resolveLocale(l) en comn_engine.ts normaliza ambas formas:
resolveLocale('es') // → 'es'
resolveLocale(() => 'en') // → 'en'
createGlob
Punto de entrada principal. Crea una instancia integrada con locale compartido.
import { createGlob } from '@/glob/glob_engine';
const glob = createGlob({
locale: 'es',
ling, // instancia de LingInstance
numr: { maxDecimals: 2 },
curr: {
definitions: { EUR: eurDef, USD: usdDef },
selectedCurrency: 'EUR',
},
dati: { selectedTimeFormat: '24h' },
unit: { selectedSystem: 'metric' },
});
API de GlobInstance
| Método / propiedad | Descripción |
|---|---|
getLocale() |
Locale activo (delegado a ling) |
setLocale(locale) |
Cambia el locale en ling y notifica suscriptores |
onLocaleChange(fn) |
Suscribe un listener; devuelve función de desuscripción |
t(key, params?) |
Traducción via ling.t |
ts(key, params?) |
Traducción con soporte plural via ling.ts |
ling |
La instancia LingInstance completa |
numr |
Módulo de números |
curr |
Módulo de moneda |
dati |
Módulo de fecha/hora |
unit |
Módulo de unidades |
Cambio reactivo de locale
glob.setLocale('en');
glob.numr.f(1234.5); // '1,234.5'
glob.curr.f(99); // '99.00 €'
glob.dati.f(new Date()); // 'March 2, 2026'
Suscriptor de cambios
const unsub = glob.onLocaleChange(locale => {
console.log('Nuevo locale:', locale);
});
glob.setLocale('fr'); // → 'Nuevo locale: fr'
unsub(); // elimina el listener
numr
Formateo de números usando Intl.NumberFormat.
Creación independiente
import { createNumr } from '@/glob/numr_engine';
const numr = createNumr({ minDecimals: 0, maxDecimals: 2 }, 'es');
f(n, opts?, locale?)
numr.f(1234567.89) // '1.234.567,89' (es)
numr.f(1234567.89, {}, 'en') // '1,234,567.89'
numr.f(1234.5, { maxDecimals: 0 }) // '1.235'
numr.f(0.1234, { minDecimals: 2, maxDecimals: 4 }) // '0,1234'
| Parámetro | Tipo | Default |
|---|---|---|
n |
number |
— |
opts.minDecimals |
number |
0 |
opts.maxDecimals |
number |
2 |
locale |
LocaleResolver |
defaultLocale de instancia |
curr
Formateo y conversión de moneda.
Definir monedas
import type { CurrDefinition } from '@/glob/curr_types';
import { p } from '@/ling/engine';
const EUR: CurrDefinition = {
symbol: '€',
code: 'EUR',
name: p({
es: { one: 'euro', other: 'euros' },
en: { one: 'euro', other: 'euros' },
}),
conversionValue: 1, // relativo a moneda base
};
Creación independiente
import { createCurr } from '@/glob/curr_engine';
const curr = createCurr(
{ EUR, USD, MXN }, // definiciones
ling, // instancia LingInstance
{ selectedCurrency: 'EUR', decimals: 2 },
'es' // defaultLocale
);
f(amount, display?, locale?)
curr.f(99.5) // '99,50 €' (symbol, es)
curr.f(99.5, 'symbol', 'en') // '99.50 €'
curr.f(99.5, 'code') // '99,50 EUR'
curr.f(1, 'name') // '1,00 euro'
curr.f(2, 'name') // '2,00 euros'
display |
Resultado ejemplo |
|---|---|
'symbol' (default) |
99,50 € |
'code' |
99,50 EUR |
'name' |
99,50 euros |
Conversión
curr.convert(100, 'EUR', 'USD') // → 108 (si USD.conversionValue = 1.08)
curr.convertTo(100, 'USD') // convierte desde selectedCurrency
conversionValue puede ser un número estático o una función () => number para tasas dinámicas:
const USD: CurrDefinition = {
// ...
conversionValue: () => fetchLiveRate('USD'),
};
Gestión de moneda activa
curr.getCurrency() // 'EUR'
curr.setCurrency('USD') // cambia la moneda activa
curr.get('EUR') // → CurrDefinition | undefined
dati
Formateo de fechas y horas usando Intl.DateTimeFormat.
Creación independiente
import { createDati } from '@/glob/dati_engine';
const dati = createDati({ selectedTimeFormat: '24h' }, 'es');
f(date, opts?, locale?)
const d = new Date('2026-03-02T15:30:00');
dati.f(d) // '2 de marzo de 2026' (long, es)
dati.f(d, 'short') // '2/3/26'
dati.f(d, { date: 'long', time: 'short' }) // '2 de marzo de 2026, 15:30'
dati.f(d, { time: 'short' }, 'en') // '3:30 PM' (si timeFormat = '12h')
DatiOptions
| Valor | Resultado |
|---|---|
'long' (default) |
Solo fecha en formato largo |
'short' |
Solo fecha en formato corto |
{ date: 'long' } |
Solo fecha larga |
{ time: 'short' } |
Solo hora corta |
{ date: 'long', time: 'short' } |
Fecha y hora |
Preferencias
dati.setTimeFormat('12h') // '12h' | '24h'
dati.getTimeFormat() // '12h'
dati.setDateOrder('MDY') // 'DMY' | 'MDY' | 'YMD'
dati.getDateOrder() // 'MDY'
dateOrderestá almacenado en el estado pero el orden real lo gestionaIntl.DateTimeFormatsegún el locale. Se expone para que la UI pueda reflejarlo.
unit
Conversión y formateo de unidades de medida. Soporta dos sistemas: metric e imperial.
Categorías disponibles
| Categoría | Base métrica | Base imperial | Unidades |
|---|---|---|---|
weight |
kg |
lb |
kg, g, lb, oz, t |
length |
m |
mi |
km, m, cm, mm, mi, ft, in, yd |
temp |
c |
f |
c, f, k |
volume |
l |
gal |
l, ml, m³, gal, fl oz, pt |
area |
m² |
ft² |
m², km², ha, ft², mi², ac |
Creación independiente
import { createUnit } from '@/glob/unit_engine';
const unit = createUnit({}, { selectedSystem: 'metric' }, 'es');
Sistema activo
unit.setSystem('imperial')
unit.getSystem() // 'imperial'
category.f(value, display?, locale?)
Formatea el valor en la unidad base del sistema activo:
unit.weight.f(70) // '70 kg' (metric, symbol)
unit.weight.f(70, 'name') // '70 kilogramos'
unit.weight.f(70, 'symbol', 'en') // '70 kg'
unit.setSystem('imperial');
unit.weight.f(70) // '154,32 lb'
category.fTo(value, targetUnit?, display?, locale?)
Convierte desde la base métrica y formatea en la unidad destino:
unit.length.fTo(1000, 'km') // '1 km'
unit.temp.fTo(100, 'f') // '212 °F' (100°C → °F)
unit.area.fTo(10000, 'ha') // '1 ha'
Si se omite targetUnit, usa la base del sistema activo.
category.convert(value, from, to)
Conversión numérica pura, sin formateo:
unit.weight.convert(1, 'kg', 'lb') // 2.204...
unit.temp.convert(100, 'c', 'f') // 212
unit.length.convert(1, 'mi', 'km') // 1.609...
La temperatura usa conversión directa (no por factor lineal), manejada internamente con convertTemp().
Personalizar categorías
Se pueden sobreescribir las categorías por defecto o añadir nuevas unidades:
const unit = createUnit(
{
weight: {
base: 'kg',
baseImperial: 'lb',
units: {
// solo las unidades que necesitas
kg: { factor: 1, system: 'metric', symbol: { es: 'kg', en: 'kg' }, name: ... },
},
},
},
{ selectedSystem: 'metric' },
'es'
);
Referencia de tipos
GlobLocale
type GlobLocale = SupportedLocale | (string & {});
Acepta cualquier código BCP 47 ('es', 'en', 'fr', 'es-MX', etc.).
LocaleResolver
type LocaleResolver = GlobLocale | (() => GlobLocale);
CurrDefinition
interface CurrDefinition {
symbol : string;
code : string;
name : LingPluralFn<Record<never, never>>;
conversionValue?: number | (() => number | null) | null;
}
UnitDefinition
interface UnitDefinition {
symbol : LingRecord; // { es: 'kg', en: 'kg' }
name : LingPluralFn<...>; // plural bilingüe
factor : number; // relativo a la base de la categoría
system : 'metric' | 'imperial';
}
La conversión entre unidades (excepto temperatura) se calcula como:
resultado = (valor × factor_origen) / factor_destino
donde factor es la equivalencia de cada unidad respecto a la base de la categoría (ej. kg tiene factor 1, g tiene factor 0.001).