refactor(soma/eidos): migrate 4 service components from Eidos to Soma

Architectural cleanup: Trans, FormatDate, FormatNumber, RelativeTime no
son visuales — son behavior alrededor del runtime (`uix.langs.t/ts`,
`uix.format.*`). Estaban en `eidos/components/` solo porque ahi se
inventariaban los wrappers. Naturaleza real: Soma.

Beneficio principal: el invariante "Eidos no importa `getActiveUix()`"
recupera su pureza — los 4 service components ya no contaban como
violación del audit Codex (P1 #1 reducido en 4 archivos +
dependencias).

Movimientos (4 componentes, 31 archivos staged):

- src/uix/eidos/components/{kebab}/ → src/uix/soma/components/{kebab}/
  - {kebab}.svelte → components/{kebab}.svelte (subdir convencion Soma)
  - types.ts → types.ts
  - README.md → README.md
  - index.ts → exports.ts (Soma pattern: index re-exports from exports)
  - + nuevo index.ts: `export * from './exports';`
  - Eidos dirs eliminados (4 directories).

Morfos: scope `['eidos']` → scope `['soma']` (4 archivos):
- morfo/components/trans.ts
- morfo/components/format-date.ts
- morfo/components/format-number.ts
- morfo/components/relative-time.ts

Demos (4 +page.svelte) updated:
- import paths `$uix/eidos/components/{kebab}` → `$soma/components/{kebab}`
- meta-pill `scope: eidos` → `scope: soma`

Headers de los .svelte updated: "Eidos `<X>`" → "Soma `<X>` — service
component" + parrafo explicando por que vive en Soma (behavior, no
visual primitive).

Verificación:
- `npm run check`: 0 errors (mantenido).
- HTTP probes /uix/components/{trans,format-date,format-number,relative-time}:
  todos 200.

Sin barrel re-exports a limpiar (no existían). Sin consumers fuera de
los 4 demos.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 546aa8d0ee
commit 540abee026

@ -1,20 +0,0 @@
// FormatDate — service component wrapping uix.format.dates.
//
// import { FormatDate } from '$uix/eidos/components/format-date';
//
// <FormatDate value={post.publishedAt} dateStyle="medium" />
// <FormatDate value={meeting.startsAt} mode="dateTime" />
// <FormatDate value={now} mode="time" timeZoneName="short" />
//
// Renders as <time datetime="..."> for semantic ISO 8601 access.
import FormatDate from './format-date.svelte';
export { FormatDate };
export default FormatDate;
export type {
FormatDateProps,
FormatDateInput,
FormatDateMode,
FormatDateStyle,
FormatTimeStyle
} from './types';

@ -1,19 +0,0 @@
// RelativeTime — service component rendering Intl.RelativeTimeFormat
// output ("hace 2 horas" / "in 3 days") with an auto-refresh tick.
//
// import { RelativeTime } from '$uix/eidos/components/relative-time';
//
// <RelativeTime value={post.publishedAt} />
// <RelativeTime value={createdAt} style="short" />
// <RelativeTime value={dueDate} unit="day" updateInterval={0} />
import RelativeTime from './relative-time.svelte';
export { RelativeTime };
export default RelativeTime;
export type {
RelativeTimeProps,
RelativeTimeInput,
RelativeTimeUnit,
RelativeTimeNumeric,
RelativeTimeStyle
} from './types';

@ -1,82 +0,0 @@
# Eidos Trans
`<Trans>` es un **service component** — un wrapper greppable de
`getActiveUix().langs.t / ts`. Resuelve un `idlangref` o un `path` a
traves del catalogo activo de langs y renderiza el texto traducido.
Familia con `<FormatDate>`, `<FormatNumber>`, `<RelativeTime>`: todos
son wrappers tipados del runtime para que un audit i18n encuentre cada
sitio que toca el catalogo con `grep "<Trans"`.
## Superficie
```svelte
<!-- 5 modos de resolucion -->
<Trans>#?common.save|Save</Trans> <!-- children como idlangref -->
<Trans value="#?common.save|Save" /> <!-- value prop -->
<Trans path="common.save">Save</Trans> <!-- path + children=fallback -->
<Trans path="common.greet" params={{name: 'Juan'}} />
<Trans as="strong">#?ui.required|Required</Trans>
<Trans path="common.save" locale="en-US">Save</Trans>
```
## Props clave
| Prop | Tipo | Default | Notas |
| --- | --- | --- | --- |
| `value` | `LangString` | — | `idlangref` (`#?path\|fallback`) o literal. |
| `path` | `string` | — | Dotted path al catalogo (ej. `'common.save'`). |
| `params` | `Record<string, unknown>` | — | Interpolacion para `path`. |
| `locale` | `SupportedLocale` | runtime | Override por instancia. |
| `trim` | `boolean` | `true` | Trim del texto capturado de children. |
| `as` | `keyof HTMLElementTagNameMap` | `'span'` | Tag HTML. |
| `children` | snippet | — | Fallback / source si no hay `value` / `path`. |
## Modos de resolucion
| Modo | Forma | Source |
| --- | --- | --- |
| 1. Children-only | `<Trans>#?common.save\|Save</Trans>` | DOM `textContent` parseado como `idlangref`. |
| 2. Value prop | `<Trans value="#?common.save\|Save" />` | `value` parseado. |
| 3. Path + children fallback | `<Trans path="common.save">Save</Trans>` | `t(path)` con `children` como fallback si la key no existe. |
| 4. Path + params | `<Trans path="common.greet" params={{name}} />` | `t(path, params)`. |
| 5. Pass-through | `<Trans value="literal text" />` | Texto literal sin resolver. |
## Decisiones
- **Reactivo al locale**: el `$derived` re-corre cuando el locale activo
cambia. La lectura va a traves de `uix.langs.t / ts` que estan
acopladas al sistema reactivo de runes.
- **Captura de children**: cuando ni `value` ni `path` estan
presentes, el componente captura `textContent` del nodo via
`bind:this={captureNode}` + `$effect`. La primera render emite el
children original (`#?path|fallback` sin resolver); en `$effect`
intercepta y resuelve.
- **Cast del path**: `langs.t()` tipa su primer overload con
`${string}.${string}` (template literal estricto). Como el runtime
acepta paths dinamicos en runtime, se castea via
`Parameters<typeof t>[0]`.
- **Fallback a children**: cuando `t(path)` devuelve el path original
(signal de "key no encontrada"), el componente cae al `capturedText`
si existe.
- **No CSS recipe**: marker `[data-trans]` solo para tooling.
- **No emite eventos sema**.
## Comparativa
| Capacidad | Eidos | i18next `Trans` | formatjs `FormattedMessage` | svelte-i18n `_` |
| --- | --- | --- | --- | --- |
| Greppable site marker | Si: `<Trans` | Si | Si | No: `$_('key')` inline |
| Children-as-idlangref | Si: `#?path\|fallback` | Si | No | No |
| Path + params | Si | Si | Si | Si |
| Locale override per-instance | Si | Limitado | Si | No |
| Reactivo al cambio de locale | Si | Si | Si | Si |
| Tag HTML configurable | Si: `as` | Si: `as` | Si: `tagName` | No |
| Pluralizacion | Via params del catalogo | Si | Si | Si |
## Referencias
- i18next Trans: https://react.i18next.com/latest/trans-component
- formatjs: https://formatjs.io/docs/react-intl/components#formattedmessage
- Active langs: `src/arts/langs/`
- Sistema idlangref: `src/uix/langs/`

@ -1,17 +0,0 @@
// Trans — i18n primitive that resolves an idlangref or path through
// the active langs catalog and renders the translated string.
//
// import { Trans } from '$uix/eidos/components/trans';
//
// <Trans>#?common.save|Save</Trans>
// <Trans value="#?common.save|Save" />
// <Trans path="common.save">Save</Trans>
// <Trans path="common.greet" params={{ name: 'Juan' }} />
// <Trans as="strong">#?ui.required|Required</Trans>
//
// Reactive to locale changes; greppable as the canonical i18n marker.
import Trans from './trans.svelte';
export { Trans };
export default Trans;
export type { TransProps } from './types';

@ -12,7 +12,7 @@ import type { Morfo } from '../types';
export const formatDateMorfo = { export const formatDateMorfo = {
name: 'FormatDate', name: 'FormatDate',
kebab: 'format-date', kebab: 'format-date',
scope: ['eidos'], scope: ['soma'],
parts: [ parts: [
{ {
name: 'Provider', name: 'Provider',

@ -16,7 +16,7 @@ import type { Morfo } from '../types';
export const formatNumberMorfo = { export const formatNumberMorfo = {
name: 'FormatNumber', name: 'FormatNumber',
kebab: 'format-number', kebab: 'format-number',
scope: ['eidos'], scope: ['soma'],
parts: [ parts: [
{ {
name: 'Provider', name: 'Provider',

@ -12,7 +12,7 @@ import type { Morfo } from '../types';
export const relativeTimeMorfo = { export const relativeTimeMorfo = {
name: 'RelativeTime', name: 'RelativeTime',
kebab: 'relative-time', kebab: 'relative-time',
scope: ['eidos'], scope: ['soma'],
parts: [ parts: [
{ {
name: 'Provider', name: 'Provider',

@ -20,7 +20,7 @@ import type { Morfo } from '../types';
export const transMorfo = { export const transMorfo = {
name: 'Trans', name: 'Trans',
kebab: 'trans', kebab: 'trans',
scope: ['eidos'], scope: ['soma'],
parts: [ parts: [
{ {
name: 'Provider', name: 'Provider',

@ -1,4 +1,4 @@
# Eidos FormatDate # Soma FormatDate
`<FormatDate>` es un **service component** — un wrapper greppable de `<FormatDate>` es un **service component** — un wrapper greppable de
`getActiveUix().format.dates.formatDate / formatTime / formatDateTime`. `getActiveUix().format.dates.formatDate / formatTime / formatDateTime`.
@ -10,6 +10,10 @@ Familia con `<Trans>`, `<FormatNumber>`, `<RelativeTime>`: todos son
wrappers tipados del runtime para que un audit i18n/l10n encuentre cada wrappers tipados del runtime para que un audit i18n/l10n encuentre cada
sitio de formateo con `grep "<FormatDate"`. sitio de formateo con `grep "<FormatDate"`.
Vive en `soma/components/` (no `eidos/components/`) porque es una pieza
de comportamiento alrededor de `getActiveUix()`. La invariante "Eidos no
importa runtime" se preserva sacando los 4 service components a Soma.
## Superficie ## Superficie
```svelte ```svelte
@ -57,7 +61,7 @@ sitio de formateo con `grep "<FormatDate"`.
`as="span"`, el `datetime` se aplica al span (validado por Svelte) pero `as="span"`, el `datetime` se aplica al span (validado por Svelte) pero
pierde la semantica nativa. pierde la semantica nativa.
- **No CSS recipe**: el marker `[data-format-date]` existe solo para - **No CSS recipe**: el marker `[data-format-date]` existe solo para
tooling — no hay estilos a nivel de eidos porque el output es texto tooling — no hay estilos a nivel de soma porque el output es texto
puro. puro.
- **No emite eventos sema**: no es una acto perceptivo del usuario. - **No emite eventos sema**: no es una acto perceptivo del usuario.
@ -83,7 +87,7 @@ Si quieres mostrar timezone label (`timeZoneName`), usa per-axis:
## Comparativa ## Comparativa
| Capacidad | Eidos | date-fns | Day.js | luxon | Intl.DateTimeFormat | | Capacidad | Soma | date-fns | Day.js | luxon | Intl.DateTimeFormat |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| Modo `date`/`time`/`dateTime` | Si | Manual via format strings | Manual | Si (`toLocaleString`) | Manual | | Modo `date`/`time`/`dateTime` | Si | Manual via format strings | Manual | Si (`toLocaleString`) | Manual |
| Locale reactivo desde runtime | Si | No | No | No | No | | Locale reactivo desde runtime | Si | No | No | No | No |

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* Eidos `<FormatDate>` — service component wrapping the active * Soma `<FormatDate>` — service component wrapping the active
* dates runtime (`uix.format.dates.formatDate / formatTime / * dates runtime (`uix.format.dates.formatDate / formatTime /
* formatDateTime`). Renders as `<time datetime="...">` by default * formatDateTime`). Renders as `<time datetime="...">` by default
* so screen readers and search engines can parse the original * so screen readers and search engines can parse the original
@ -9,9 +9,14 @@
* <FormatDate value={post.publishedAt} dateStyle="medium" /> * <FormatDate value={post.publishedAt} dateStyle="medium" />
* <FormatDate value={meeting.startsAt} mode="dateTime" dateStyle="short" timeStyle="short" /> * <FormatDate value={meeting.startsAt} mode="dateTime" dateStyle="short" timeStyle="short" />
* <FormatDate value={createdAt} mode="time" timeZone="UTC" timeZoneName="short" /> * <FormatDate value={createdAt} mode="time" timeZone="UTC" timeZoneName="short" />
*
* Lives in Soma (not Eidos) because it is a behavior component
* around `getActiveUix().format`, not a visual primitive. Eidos
* remains a pure visual layer; service components like this one
* own the runtime access.
*/ */
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import type { FormatDateInput, FormatDateProps } from './types'; import type { FormatDateInput, FormatDateProps } from '../types';
let { let {
value = null, value = null,
@ -90,7 +95,7 @@
return dates.formatDate(parsed, options, locale); return dates.formatDate(parsed, options, locale);
} }
// No runtime available (e.g. eidos used outside `<UixApp>`). // No runtime available (e.g. soma used outside `<UixApp>`).
// Replicate the engine's default-preset logic locally so `mode` // Replicate the engine's default-preset logic locally so `mode`
// is still honoured. Defaults are skipped when the caller // is still honoured. Defaults are skipped when the caller
// already passed a conflicting per-axis field or `timeZoneName` // already passed a conflicting per-axis field or `timeZoneName`

@ -0,0 +1,24 @@
// FormatDate — service component wrapping uix.format.dates.
//
// import { FormatDate } from '$soma/components/format-date';
//
// <FormatDate value={post.publishedAt} dateStyle="medium" />
// <FormatDate value={meeting.startsAt} mode="dateTime" />
// <FormatDate value={now} mode="time" timeZoneName="short" />
//
// Renders as <time datetime="..."> for semantic ISO 8601 access.
//
// Lives under `soma/` (not `eidos/`) because the component owns runtime
// access. Eidos invariant — "no `getActiveUix()` imports" — is enforced
// across the visual layer; service components like `Trans` /
// `FormatDate` / `FormatNumber` / `RelativeTime` are their Soma-side peers.
export { default as FormatDate } from './components/format-date.svelte';
export { default as Provider } from './components/format-date.svelte';
export type {
FormatDateProps,
FormatDateInput,
FormatDateMode,
FormatDateStyle,
FormatTimeStyle
} from './types';

@ -0,0 +1 @@
export * from './exports';

@ -1,4 +1,4 @@
# Eidos FormatNumber # Soma FormatNumber
`<FormatNumber>` es un **service component** — un wrapper greppable de `<FormatNumber>` es un **service component** — un wrapper greppable de
`getActiveUix().format.numbers`. Renderiza un numero a traves del `getActiveUix().format.numbers`. Renderiza un numero a traves del
@ -7,6 +7,10 @@ preferencias de moneda / sistema de unidades).
Familia con `<Trans>`, `<FormatDate>`, `<RelativeTime>`. Familia con `<Trans>`, `<FormatDate>`, `<RelativeTime>`.
Vive en `soma/components/` (no `eidos/components/`) porque es una pieza
de comportamiento alrededor de `getActiveUix()`. La invariante "Eidos no
importa runtime" se preserva sacando los 4 service components a Soma.
## Superficie ## Superficie
```svelte ```svelte
@ -62,7 +66,7 @@ Familia con `<Trans>`, `<FormatDate>`, `<RelativeTime>`.
## Comparativa ## Comparativa
| Capacidad | Eidos | numbro | accounting.js | Intl.NumberFormat | | Capacidad | Soma | numbro | accounting.js | Intl.NumberFormat |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Locale reactivo desde runtime | Si | No | No | No | | Locale reactivo desde runtime | Si | No | No | No |
| Cache de instancias por options | Si | No | No | No | | Cache de instancias por options | Si | No | No | No |

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* Eidos `<FormatNumber>` — service component wrapping * Soma `<FormatNumber>` — service component wrapping
* `getActiveUix().format.numbers.format(...)` so consumers can * `getActiveUix().format.numbers.format(...)` so consumers can
* type-safe their locale-aware number rendering without inlining * type-safe their locale-aware number rendering without inlining
* the runtime call on every site. * the runtime call on every site.
@ -16,9 +16,14 @@
* Reactive to locale + currency + preference changes — the runtime * Reactive to locale + currency + preference changes — the runtime
* already exposes those signals, this component just observes them * already exposes those signals, this component just observes them
* via `$derived`. * via `$derived`.
*
* Lives in Soma (not Eidos) because it is a behavior component
* around `getActiveUix().format`, not a visual primitive. Eidos
* remains a pure visual layer; service components like this one
* own the runtime access.
*/ */
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import type { FormatNumberProps } from './types'; import type { FormatNumberProps } from '../types';
let { let {
value = null, value = null,
@ -90,7 +95,7 @@
return numbers.format(inputValue, options, locale); return numbers.format(inputValue, options, locale);
} }
// No runtime available (e.g. eidos used outside `<UixApp>`). // No runtime available (e.g. soma used outside `<UixApp>`).
return new Intl.NumberFormat(locale ?? undefined, options).format(inputValue); return new Intl.NumberFormat(locale ?? undefined, options).format(inputValue);
}); });
</script> </script>

@ -1,6 +1,6 @@
// FormatNumber — service component wrapping uix.format.numbers. // FormatNumber — service component wrapping uix.format.numbers.
// //
// import { FormatNumber } from '$uix/eidos/components/format-number'; // import { FormatNumber } from '$soma/components/format-number';
// //
// <FormatNumber value={1234567} /> // <FormatNumber value={1234567} />
// <FormatNumber value={42.5} formatStyle="percent" valueAsPercent /> // <FormatNumber value={42.5} formatStyle="percent" valueAsPercent />
@ -10,10 +10,14 @@
// Same family as <Trans>: no CSS recipe, no behaviour, just a typed // Same family as <Trans>: no CSS recipe, no behaviour, just a typed
// wrapper around the active runtime API. Reactive to locale and // wrapper around the active runtime API. Reactive to locale and
// currency preference changes. // currency preference changes.
import FormatNumber from './format-number.svelte'; //
// Lives under `soma/` (not `eidos/`) because the component owns runtime
// access. Eidos invariant — "no `getActiveUix()` imports" — is enforced
// across the visual layer; service components like `Trans` /
// `FormatDate` / `FormatNumber` / `RelativeTime` are their Soma-side peers.
export { default as FormatNumber } from './components/format-number.svelte';
export { default as Provider } from './components/format-number.svelte';
export { FormatNumber };
export default FormatNumber;
export type { export type {
FormatNumberProps, FormatNumberProps,
FormatNumberStyle, FormatNumberStyle,

@ -1,4 +1,4 @@
# Eidos RelativeTime # Soma RelativeTime
`<RelativeTime>` es un **service component** que envuelve `<RelativeTime>` es un **service component** que envuelve
`Intl.RelativeTimeFormat` con auto-refresh tick. Renderiza frases `Intl.RelativeTimeFormat` con auto-refresh tick. Renderiza frases
@ -7,6 +7,10 @@ cuando pasa el tiempo.
Familia con `<Trans>`, `<FormatDate>`, `<FormatNumber>`. Familia con `<Trans>`, `<FormatDate>`, `<FormatNumber>`.
Vive en `soma/components/` (no `eidos/components/`) porque es una pieza
de comportamiento alrededor de `getActiveUix()`. La invariante "Eidos no
importa runtime" se preserva sacando los 4 service components a Soma.
## Superficie ## Superficie
```svelte ```svelte
@ -58,7 +62,7 @@ Familia con `<Trans>`, `<FormatDate>`, `<FormatNumber>`.
## Comparativa ## Comparativa
| Capacidad | Eidos | date-fns `formatDistance` | Day.js `from` | luxon `toRelative` | Intl.RelativeTimeFormat | | Capacidad | Soma | date-fns `formatDistance` | Day.js `from` | luxon `toRelative` | Intl.RelativeTimeFormat |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| Auto-unit | Si | Si | Si | Si | No (manual) | | Auto-unit | Si | Si | Si | Si | No (manual) |
| Auto-tick | Si | No | No | No | No | | Auto-tick | Si | No | No | No | No |

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
/** /**
* Eidos `<RelativeTime>` — service component wrapping * Soma `<RelativeTime>` — service component wrapping
* `Intl.RelativeTimeFormat` with an auto-refresh tick. Renders * `Intl.RelativeTimeFormat` with an auto-refresh tick. Renders
* relative phrases like "hace 2 horas" / "in 3 days" and updates * relative phrases like "hace 2 horas" / "in 3 days" and updates
* itself as time passes so the displayed text stays fresh. * itself as time passes so the displayed text stays fresh.
@ -13,6 +13,11 @@
* Auto-tick cadence is derived from the resolved unit (1s for * Auto-tick cadence is derived from the resolved unit (1s for
* second, 30s for minute, 5min for hour, 1h for day, none above). * second, 30s for minute, 5min for hour, 1h for day, none above).
* Pass `updateInterval={0}` to render once and stop. * Pass `updateInterval={0}` to render once and stop.
*
* Lives in Soma (not Eidos) because it is a behavior component
* around `getActiveUix().format`, not a visual primitive. Eidos
* remains a pure visual layer; service components like this one
* own the runtime access.
*/ */
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
@ -20,7 +25,7 @@
RelativeTimeInput, RelativeTimeInput,
RelativeTimeProps, RelativeTimeProps,
RelativeTimeUnit RelativeTimeUnit
} from './types'; } from '../types';
let { let {
value = null, value = null,

@ -0,0 +1,23 @@
// RelativeTime — service component rendering Intl.RelativeTimeFormat
// output ("hace 2 horas" / "in 3 days") with an auto-refresh tick.
//
// import { RelativeTime } from '$soma/components/relative-time';
//
// <RelativeTime value={post.publishedAt} />
// <RelativeTime value={createdAt} style="short" />
// <RelativeTime value={dueDate} unit="day" updateInterval={0} />
//
// Lives under `soma/` (not `eidos/`) because the component owns runtime
// access. Eidos invariant — "no `getActiveUix()` imports" — is enforced
// across the visual layer; service components like `Trans` /
// `FormatDate` / `FormatNumber` / `RelativeTime` are their Soma-side peers.
export { default as RelativeTime } from './components/relative-time.svelte';
export { default as Provider } from './components/relative-time.svelte';
export type {
RelativeTimeProps,
RelativeTimeInput,
RelativeTimeUnit,
RelativeTimeNumeric,
RelativeTimeStyle
} from './types';

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
/** /**
* Eidos `<Trans>` — i18n primitive. Resolves an idlangref or path * Soma `<Trans>` — i18n service component. Resolves an idlangref or
* through the active langs catalog and renders the translated text * path through the active langs catalog and renders the translated
* in a configurable element (default `<span>`). * text in a configurable element (default `<span>`).
* *
* <Trans>#?common.save|Save</Trans> <!-- children → idlangref --> * <Trans>#?common.save|Save</Trans> <!-- children → idlangref -->
* <Trans value="#?common.save|Save" /> <!-- value prop --> * <Trans value="#?common.save|Save" /> <!-- value prop -->
@ -13,13 +13,14 @@
* Reactive to locale changes — when the active locale flips, the * Reactive to locale changes — when the active locale flips, the
* rendered text updates without consumer plumbing. * rendered text updates without consumer plumbing.
* *
* Designed as a greppable i18n marker — searching `<Trans` finds * Lives in Soma (not Eidos) because it is a behavior component
* every site that touches the langs catalog, which is much harder * around `getActiveUix().langs`, not a visual primitive. Eidos
* to audit when consumers call `langs.ts(...)` inline. * remains a pure visual layer; service components like this one
* own the runtime access.
*/ */
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
import type { LangString } from '$libs/langs'; import type { LangString } from '$libs/langs';
import type { TransProps } from './types'; import type { TransProps } from '../types';
let { let {
value, value,
@ -72,7 +73,9 @@
// `hasSource` is true once we have any source string to render. // `hasSource` is true once we have any source string to render.
// While the children-capture path runs $effect on mount, we render // While the children-capture path runs $effect on mount, we render
// the children initially so the captureNode has text to read. // the children initially so the captureNode has text to read.
const hasSource = $derived(value !== undefined || path !== undefined || capturedText !== undefined); const hasSource = $derived(
value !== undefined || path !== undefined || capturedText !== undefined
);
</script> </script>
<svelte:element this={as} bind:this={captureNode} data-trans="" {...rest}> <svelte:element this={as} bind:this={captureNode} data-trans="" {...rest}>

@ -0,0 +1,22 @@
// Trans — i18n service component. Renders translated text through the
// active langs catalog (`uix.langs.t / ts`).
//
// import { Trans } from '$soma/components/trans';
//
// <Trans>#?common.save|Save</Trans>
// <Trans value="#?common.save|Save" />
// <Trans path="common.save">Save</Trans>
// <Trans path="common.greet" params={{ name: 'Juan' }} />
// <Trans as="strong">#?ui.required|Required</Trans>
//
// Reactive to locale changes; greppable as the canonical i18n marker.
//
// Lives under `soma/` (not `eidos/`) because the component owns runtime
// access. Eidos invariant — "no `getActiveUix()` imports" — is enforced
// across the visual layer; service components like `Trans` /
// `FormatDate` / `FormatNumber` / `RelativeTime` are their Soma-side
// peers.
export { default as Trans } from './components/trans.svelte';
export { default as Provider } from './components/trans.svelte';
export type { TransProps } from './types';

@ -0,0 +1 @@
export * from './exports';

@ -3,29 +3,25 @@ import type { HTMLAttributes } from 'svelte/elements';
import type { LangParams, SupportedLocale } from '$libs/langs'; import type { LangParams, SupportedLocale } from '$libs/langs';
/** /**
* Props for `<Trans>`. * Props for `<Trans>` — i18n service component.
* *
* Three resolution modes (any one of them activates translation): * Three resolution modes (any one activates translation):
* *
* 1. **idlangref string in children** * 1. **idlangref in children**
* `<Trans>#?common.save|Save</Trans>` * `<Trans>#?common.save|Save</Trans>`
* The component reads the children DOM text content. If it * Reads the children DOM text. `#?…` → idlangref;
* starts with `#?` it's treated as an idlangref; otherwise it * otherwise literal string.
* passes through as a literal string.
* *
* 2. **`value` prop** * 2. **`value` prop**
* `<Trans value="#?common.save|Save" />` * `<Trans value="#?common.save|Save" />`
* Same idlangref / literal split, but supplied via prop — * Same split via prop — cleaner for SSR and dynamic values.
* cleaner for SSR and dynamic values.
* *
* 3. **`path` prop** * 3. **`path` prop**
* `<Trans path="common.save">Save</Trans>` * `<Trans path="common.save">Save</Trans>`
* Strips the `#?` prefix — pass the dotted path directly. The * Dotted path (no `#?` prefix). Supports `params`. Children act as
* children act as the fallback text. Use this when the consumer * the fallback when the path is missing from the catalog.
* wants type-safety on the path (autocomplete from the langs
* schema).
* *
* `path` + `params` enable interpolation: * `path` + `params` interpolate:
* `<Trans path="common.greet" params={{ name: 'Juan' }} />` * `<Trans path="common.greet" params={{ name: 'Juan' }} />`
* *
* `as` swaps the rendered tag (default `<span>`). * `as` swaps the rendered tag (default `<span>`).
@ -45,9 +41,9 @@ export type TransProps = Omit<HTMLAttributes<HTMLElement>, 'children'> & {
*/ */
path?: string; path?: string;
/** /**
* Interpolation params for `path`-based translations. Ignored * Interpolation params for `path`-based translations. Ignored when
* when `value` (idlangref) is the source — interpolation in * `value` is the source — idlangref interpolation lives in the
* idlangrefs lives in the inline syntax. * inline syntax.
*/ */
params?: LangParams; params?: LangParams;
/** /**

@ -4,7 +4,7 @@
type FormatDateMode, type FormatDateMode,
type FormatDateStyle, type FormatDateStyle,
type FormatTimeStyle type FormatTimeStyle
} from '$uix/eidos/components/format-date'; } from '$soma/components/format-date';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { formatDateMorfo } from '@/uix/morfo/components/format-date'; import { formatDateMorfo } from '@/uix/morfo/components/format-date';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
@ -80,7 +80,7 @@
<code>{demoLocale}</code> <code>{demoLocale}</code>
</span> </span>
<span data-uix-meta-pill> <span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos <span data-uix-meta-key>scope</span>soma
</span> </span>
</div> </div>
</header> </header>

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { FormatNumber } from '$uix/eidos/components/format-number'; import { FormatNumber } from '$soma/components/format-number';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { formatNumberMorfo } from '@/uix/morfo/components/format-number'; import { formatNumberMorfo } from '@/uix/morfo/components/format-number';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
@ -41,7 +41,7 @@
const eidosSnippet = $derived( const eidosSnippet = $derived(
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { FormatNumber } from '$uix/eidos/components/format-number';", " import { FormatNumber } from '$soma/components/format-number';",
'</' + 'script>', '</' + 'script>',
'', '',
'<FormatNumber', '<FormatNumber',
@ -88,7 +88,7 @@
<code>{uix.langs.getLocale()}</code> <code>{uix.langs.getLocale()}</code>
</span> </span>
<span data-uix-meta-pill> <span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos <span data-uix-meta-key>scope</span>soma
</span> </span>
</div> </div>
</header> </header>

@ -4,7 +4,7 @@
type RelativeTimeUnit, type RelativeTimeUnit,
type RelativeTimeStyle, type RelativeTimeStyle,
type RelativeTimeNumeric type RelativeTimeNumeric
} from '$uix/eidos/components/relative-time'; } from '$soma/components/relative-time';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { relativeTimeMorfo } from '@/uix/morfo/components/relative-time'; import { relativeTimeMorfo } from '@/uix/morfo/components/relative-time';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
@ -95,7 +95,7 @@
<code>{demoLocale}</code> <code>{demoLocale}</code>
</span> </span>
<span data-uix-meta-pill> <span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos <span data-uix-meta-key>scope</span>soma
</span> </span>
</div> </div>
</header> </header>

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { Trans } from '$uix/eidos/components/trans'; import { Trans } from '$soma/components/trans';
import { compileMorfo } from '$uix/morfo'; import { compileMorfo } from '$uix/morfo';
import { transMorfo } from '@/uix/morfo/components/trans'; import { transMorfo } from '@/uix/morfo/components/trans';
import { getActiveUix } from '$active-uix'; import { getActiveUix } from '$active-uix';
@ -20,7 +20,7 @@
const eidosSnippet = $derived( const eidosSnippet = $derived(
[ [
"<script lang='ts'>", "<script lang='ts'>",
" import { Trans } from '$uix/eidos/components/trans';", " import { Trans } from '$soma/components/trans';",
'</' + 'script>', '</' + 'script>',
'', '',
'<!-- mode 1 — idlangref via children -->', '<!-- mode 1 — idlangref via children -->',
@ -72,7 +72,7 @@
<code>{uix.langs.getLocale()}</code> <code>{uix.langs.getLocale()}</code>
</span> </span>
<span data-uix-meta-pill> <span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos <span data-uix-meta-key>scope</span>soma
</span> </span>
</div> </div>
</header> </header>

Loading…
Cancel
Save

Powered by TurnKey Linux.