You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
459 lines
16 KiB
459 lines
16 KiB
<script lang="ts">
|
|
import { Trans } from '$uix/eidos/components/trans';
|
|
import { compileMorfo } from '$uix/morfo';
|
|
import { transMorfo } from '@/uix/morfo/components/trans';
|
|
import { getActiveUix } from '$active-uix';
|
|
|
|
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
|
|
let tab = $state<Tab>('live');
|
|
|
|
const uix = getActiveUix();
|
|
const compiled = compileMorfo(transMorfo);
|
|
const partsList = $derived([...compiled.parts.byKebab.values()]);
|
|
|
|
// ── Live state ────────────────────────────────────────────────────────
|
|
let valueProp = $state('#?components.badge.remove|Remove');
|
|
let pathProp = $state('components.badge.remove');
|
|
let asProp = $state<keyof HTMLElementTagNameMap>('span');
|
|
let trim = $state(true);
|
|
|
|
const eidosSnippet = $derived(
|
|
[
|
|
"<script lang='ts'>",
|
|
" import { Trans } from '$uix/eidos/components/trans';",
|
|
'</' + 'script>',
|
|
'',
|
|
'<!-- mode 1 — idlangref via children -->',
|
|
'<Trans>#?common.save|Save</Trans>',
|
|
'',
|
|
'<!-- mode 2 — idlangref via value prop -->',
|
|
`<Trans value="${valueProp}" />`,
|
|
'',
|
|
'<!-- mode 3 — path + fallback children -->',
|
|
`<Trans path="${pathProp}">Fallback</Trans>`,
|
|
'',
|
|
'<!-- mode 4 — path with interpolation params -->',
|
|
`<Trans path="common.greet" params={{ name: 'Juan' }} />`,
|
|
'',
|
|
'<!-- mode 5 — literal pass-through (no #? prefix) -->',
|
|
'<Trans>Just a literal string</Trans>',
|
|
'',
|
|
'<!-- tag override -->',
|
|
`<Trans as="${asProp}">#?ui.required|Required</Trans>`
|
|
].join('\n')
|
|
);
|
|
</script>
|
|
|
|
<div data-uix-canvas-inner>
|
|
<header>
|
|
<div data-uix-eyebrow>Typography · Trans</div>
|
|
<h1 data-uix-page-title>Trans</h1>
|
|
<p data-uix-page-lede>
|
|
i18n primitive that resolves an idlangref or path through the active langs catalog and
|
|
renders the translated string. Wraps the
|
|
<code>getActiveUix().langs.ts/t</code> API in a greppable component — searching
|
|
<code><Trans</code> finds every site that touches translations, much easier to audit
|
|
than inline <code>langs.ts(...)</code> calls. Reactive to locale changes: when the active
|
|
locale flips, every <code><Trans></code> on screen updates without consumer
|
|
plumbing.
|
|
</p>
|
|
<div data-uix-page-meta>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>parts</span>{compiled.parts.order.length}
|
|
</span>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>events</span>0
|
|
</span>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>modes</span>5
|
|
</span>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>current locale</span>
|
|
<code>{uix.langs.getLocale()}</code>
|
|
</span>
|
|
<span data-uix-meta-pill>
|
|
<span data-uix-meta-key>scope</span>eidos
|
|
</span>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Live preview -->
|
|
<div data-uix-stage>
|
|
<div data-uix-stage-area>
|
|
<div style="display: flex; flex-direction: column; gap: var(--uix-space-3); inline-size: 100%;">
|
|
<div data-uix-trace-row>
|
|
<code><Trans>{valueProp}</Trans></code>
|
|
<span style="margin-inline-start: var(--uix-space-3); font-weight: 600;"
|
|
>→ <Trans>{valueProp}</Trans></span
|
|
>
|
|
</div>
|
|
<div data-uix-trace-row>
|
|
<code><Trans value="{valueProp}" /></code>
|
|
<span style="margin-inline-start: var(--uix-space-3); font-weight: 600;"
|
|
>→ <Trans value={valueProp} /></span
|
|
>
|
|
</div>
|
|
<div data-uix-trace-row>
|
|
<code><Trans path="{pathProp}">Fallback</Trans></code>
|
|
<span style="margin-inline-start: var(--uix-space-3); font-weight: 600;"
|
|
>→ <Trans path={pathProp}>Fallback</Trans></span
|
|
>
|
|
</div>
|
|
<div data-uix-trace-row>
|
|
<code><Trans as="{asProp}">#?ui.required|Required</Trans></code>
|
|
<span style="margin-inline-start: var(--uix-space-3); font-weight: 600;"
|
|
>→ <Trans as={asProp}>#?ui.required|Required</Trans></span
|
|
>
|
|
</div>
|
|
<div data-uix-trace-row>
|
|
<code><Trans>Literal pass-through (no #?)</Trans></code>
|
|
<span style="margin-inline-start: var(--uix-space-3); font-weight: 600;"
|
|
>→ <Trans>Literal pass-through (no #?)</Trans></span
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div data-uix-stage-trace>
|
|
<span data-uix-stage-trace-key>locale</span>
|
|
<code>{uix.langs.getLocale()}</code>
|
|
<span style="color: var(--uix-text-faint)">·</span>
|
|
<span data-uix-stage-trace-key>cambia el locale en el header</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div data-uix-tabs role="tablist">
|
|
<button data-uix-tab data-active={tab === 'live'} onclick={() => (tab = 'live')}>Live</button>
|
|
<button data-uix-tab data-active={tab === 'api'} onclick={() => (tab = 'api')}>
|
|
API <span data-uix-tab-count>6</span>
|
|
</button>
|
|
<button data-uix-tab data-active={tab === 'morfo'} onclick={() => (tab = 'morfo')}>
|
|
<span data-uix-layer-badge="morfo">morfo</span>
|
|
<span data-uix-tab-count>{partsList.length}p · 0e</span>
|
|
</button>
|
|
<button data-uix-tab data-active={tab === 'sema'} onclick={() => (tab = 'sema')}>
|
|
<span data-uix-layer-badge="sema">sema</span>
|
|
<span data-uix-tab-count>0</span>
|
|
</button>
|
|
<button data-uix-tab data-active={tab === 'recipe'} onclick={() => (tab = 'recipe')}>Recipe</button
|
|
>
|
|
<button data-uix-tab data-active={tab === 'a11y'} onclick={() => (tab = 'a11y')}>A11y</button>
|
|
</div>
|
|
|
|
{#if tab === 'live'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Controls</h2>
|
|
<div data-uix-controls>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>value (idlangref)</span>
|
|
<input
|
|
type="text"
|
|
bind:value={valueProp}
|
|
style="inline-size: 28rem; font-family: var(--uix-font-mono); font-size: var(--uix-font-size-sm);"
|
|
/>
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>path</span>
|
|
<input
|
|
type="text"
|
|
bind:value={pathProp}
|
|
style="inline-size: 22rem; font-family: var(--uix-font-mono); font-size: var(--uix-font-size-sm);"
|
|
/>
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>as</span>
|
|
<span data-uix-chips role="radiogroup">
|
|
{#each ['span', 'div', 'p', 'strong', 'em', 'small', 'h2', 'h3'] as t}
|
|
<button
|
|
data-uix-chip
|
|
data-active={asProp === t}
|
|
onclick={() => (asProp = t as keyof HTMLElementTagNameMap)}>{t}</button
|
|
>
|
|
{/each}
|
|
</span>
|
|
</label>
|
|
<label data-uix-control>
|
|
<span data-uix-control-label>trim</span>
|
|
<span data-uix-switch>
|
|
<input type="checkbox" bind:checked={trim} />
|
|
<span data-uix-switch-label>{trim ? 'on' : 'off'}</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
|
|
<div data-uix-code style="margin-top: var(--uix-space-4);">
|
|
<div data-uix-code-head>
|
|
<span data-uix-layer-badge="eidos">eidos</span>
|
|
<span>composition · 5 resolution modes</span>
|
|
<span data-uix-code-lang>svelte</span>
|
|
</div>
|
|
<pre><code>{eidosSnippet}</code></pre>
|
|
</div>
|
|
|
|
<div data-uix-subsection-head>Patterns · common shapes</div>
|
|
<div data-uix-pattern-stack>
|
|
<div data-uix-pattern>
|
|
<h3 data-uix-pattern-title>Resolución por modo — comparativa con idlangref existente</h3>
|
|
<div data-uix-pattern-row>
|
|
<table data-uix-table>
|
|
<thead>
|
|
<tr><th>Forma</th><th>Output (current locale)</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code><Trans>#?components.badge.remove|Remove</Trans></code></td>
|
|
<td><strong><Trans>#?components.badge.remove|Remove</Trans></strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code><Trans value="#?components.image.loading|Loading image" /></code></td>
|
|
<td><strong><Trans value="#?components.image.loading|Loading image" /></strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code><Trans path="components.image.error">Failed</Trans></code></td>
|
|
<td><strong><Trans path="components.image.error">Failed</Trans></strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td><code><Trans>Literal text</Trans></code></td>
|
|
<td><strong><Trans>Literal text</Trans></strong></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<div data-uix-pattern>
|
|
<h3 data-uix-pattern-title>Tag override — Trans dentro de cualquier elemento HTML</h3>
|
|
<div data-uix-pattern-row>
|
|
<div style="display: flex; flex-direction: column; gap: var(--uix-space-2);">
|
|
<div>span: <Trans>#?components.badge.remove|Remove</Trans></div>
|
|
<div>strong: <Trans as="strong">#?components.badge.remove|Remove</Trans></div>
|
|
<div>em: <Trans as="em">#?components.badge.remove|Remove</Trans></div>
|
|
<div>
|
|
<Trans as="h3" style="margin: 0;">#?components.image.loading|Loading image</Trans>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div data-uix-pattern>
|
|
<h3 data-uix-pattern-title>
|
|
Reactividad al locale — cambia el idioma en el header de la página y los Trans se
|
|
re-renderizan automáticamente
|
|
</h3>
|
|
<div data-uix-pattern-row>
|
|
<p style="margin: 0;">
|
|
Locale activo: <code style="font-weight: 600;">{uix.langs.getLocale()}</code>
|
|
</p>
|
|
<p style="margin: 0;">
|
|
Remove: <Trans>#?components.badge.remove|Remove</Trans>
|
|
</p>
|
|
<p style="margin: 0;">
|
|
Loading image: <Trans>#?components.image.loading|Loading image</Trans>
|
|
</p>
|
|
<p style="margin: 0;">
|
|
Image failed: <Trans>#?components.image.error|Image failed to load</Trans>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'api'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>API reference</h2>
|
|
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Prop</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="name">value</td>
|
|
<td class="type">string</td>
|
|
<td class="default empty">—</td>
|
|
<td>
|
|
Idlangref (<code>'#?path|fallback'</code>) or literal string. Wins over
|
|
<code>path</code> and over captured children.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">path</td>
|
|
<td class="type">string</td>
|
|
<td class="default empty">—</td>
|
|
<td>
|
|
Dotted path (no <code>#?</code> prefix). Uses <code>langs.t()</code> for
|
|
interpolation. Children become the fallback when the path is missing from
|
|
the catalog.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">params</td>
|
|
<td class="type">LangParams</td>
|
|
<td class="default empty">—</td>
|
|
<td>Interpolation params for <code>path</code>-based translations.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">locale</td>
|
|
<td class="type">SupportedLocale</td>
|
|
<td class="default empty">— uix.langs.getLocale()</td>
|
|
<td>Optional locale override.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">as</td>
|
|
<td class="type">keyof HTMLElementTagNameMap</td>
|
|
<td class="default">'span'</td>
|
|
<td>HTML tag for the rendered element.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">trim</td>
|
|
<td class="type">boolean</td>
|
|
<td class="default">true</td>
|
|
<td>Trim whitespace from captured children text.</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">children</td>
|
|
<td class="type">Snippet</td>
|
|
<td class="default empty">—</td>
|
|
<td>
|
|
Source idlangref / literal text when neither <code>value</code> nor
|
|
<code>path</code> is set; fallback text when <code>path</code> is set and
|
|
missing.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<div data-uix-subsection-head>Resolution precedence</div>
|
|
<ol style="line-height: var(--uix-leading-prose);">
|
|
<li><code>value</code> prop (if set) → resolved via <code>langs.ts()</code></li>
|
|
<li>
|
|
<code>path</code> prop (if set) → resolved via <code>langs.t(path, params, locale)</code>
|
|
</li>
|
|
<li>Captured children text → resolved via <code>langs.ts()</code></li>
|
|
<li>If everything is empty, renders the fallback snippet</li>
|
|
</ol>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'morfo'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>
|
|
<span data-uix-layer-badge="morfo">morfo</span> · declarative contract
|
|
</h2>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Field</th><th>Value</th></tr></thead>
|
|
<tbody>
|
|
<tr><td class="name">name</td><td>{transMorfo.name}</td></tr>
|
|
<tr><td class="name">kebab</td><td><code>{transMorfo.kebab}</code></td></tr>
|
|
<tr><td class="name">scope</td><td>{transMorfo.scope.join(', ')}</td></tr>
|
|
<tr><td class="name">parts</td><td>{partsList.length}</td></tr>
|
|
<tr><td class="name">events</td><td>0</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'sema'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>
|
|
<span data-uix-layer-badge="sema">sema</span> · events
|
|
</h2>
|
|
<p data-uix-section-desc>
|
|
Trans declares <strong>no semantic events</strong>. Translation is a passive
|
|
transformation, not an evaluative act.
|
|
</p>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'recipe'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Eidos recipe</h2>
|
|
<p data-uix-section-desc>
|
|
Trans deliberately ships <strong>no CSS recipe</strong>. The rendered element is a
|
|
plain `<span>` (or whatever the consumer sets via <code>as</code>) and inherits its
|
|
styling from the surrounding context. The <code>[data-trans]</code> marker exists
|
|
only for tooling — i18n audits can target it without affecting visual output.
|
|
</p>
|
|
</section>
|
|
{/if}
|
|
|
|
{#if tab === 'a11y'}
|
|
<section data-uix-section>
|
|
<h2 data-uix-section-title>Accessibility</h2>
|
|
<div data-uix-table-wrap>
|
|
<table data-uix-table>
|
|
<thead><tr><th>Concern</th><th>Contract</th></tr></thead>
|
|
<tbody>
|
|
<tr>
|
|
<td class="name">Element semantics</td>
|
|
<td>
|
|
Inherits the semantics of the chosen <code>as</code> tag. Default
|
|
<code><span></code> is inline and announces with the surrounding
|
|
context. For headings, set <code>as="h2"</code> (etc.); for emphasis, use
|
|
<code>as="strong"</code> / <code>as="em"</code>.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">Live updates</td>
|
|
<td>
|
|
When the locale flips, the text content inside <code><Trans></code>
|
|
updates. Screen readers reading the page at that moment may or may not
|
|
re-announce — wrap critical announcements in
|
|
<a href="/uix/components/announce"><Announce></a> for explicit
|
|
<code>aria-live</code> coverage.
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="name">Fallback strings</td>
|
|
<td>
|
|
Every idlangref carries its English fallback (<code>#?path|Fallback</code>).
|
|
If the catalog lookup fails the fallback renders — never an empty string.
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
[data-uix-trace-row] {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--uix-space-2);
|
|
padding: var(--uix-space-2) var(--uix-space-3);
|
|
border-radius: var(--uix-radius-sm);
|
|
background: var(--uix-bg-subtle, var(--color-surface-raised));
|
|
font-family: var(--uix-font-mono);
|
|
font-size: var(--uix-font-size-sm);
|
|
}
|
|
[data-uix-trace-row] > code {
|
|
color: var(--uix-text-faint);
|
|
}
|
|
[data-uix-pattern-stack] {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--uix-space-5);
|
|
}
|
|
[data-uix-pattern-row] {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--uix-space-3);
|
|
padding: var(--uix-space-4);
|
|
border: 1px solid var(--uix-border);
|
|
border-radius: var(--uix-radius-md);
|
|
}
|
|
h3[data-uix-pattern-title] {
|
|
margin: 0 0 var(--uix-space-3);
|
|
font-family: var(--uix-font-ui);
|
|
font-size: var(--uix-font-size-sm);
|
|
font-weight: 600;
|
|
color: var(--uix-text-faint);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
</style>
|