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.
svelte-kit-vice/src/uix/eidos/components/trans/trans.svelte

85 lines
3.1 KiB

<script lang="ts">
/**
* Eidos `<Trans>` — i18n primitive. Resolves an idlangref or path
* through the active langs catalog and renders the translated text
* in a configurable element (default `<span>`).
*
* <Trans>#?common.save|Save</Trans> <!-- children → 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>
*
* Reactive to locale changes — when the active locale flips, the
* rendered text updates without consumer plumbing.
*
* Designed as a greppable i18n marker — searching `<Trans` finds
* every site that touches the langs catalog, which is much harder
* to audit when consumers call `langs.ts(...)` inline.
*/
import { ActiveEidos } from '$uix/eidos';
import type { LangString } from '$libs/langs';
import type { TransProps } from './types';
let {
value,
path,
params,
locale,
as = 'span',
trim = true,
children,
...rest
}: TransProps = $props();
const eidos = ActiveEidos.require();
// When neither `value` nor `path` is set, capture the children DOM
// text content and treat it as the source. This lets consumers
// write `<Trans>#?common.save|Save</Trans>` and we read the literal
// from the rendered output.
let captureNode = $state<HTMLElement | null>(null);
let capturedText = $state<string | undefined>(undefined);
$effect(() => {
if (value !== undefined || path !== undefined) return;
if (!captureNode) return;
const raw = captureNode.textContent ?? '';
capturedText = trim ? raw.trim() : raw;
});
const resolved = $derived.by(() => {
// path-based: use t() for interpolation + fallback children.
// Cast: langs.t() types its first overload with a strict template
// literal (`${string}.${string}` ...); a free `string` doesn't
// match. The runtime accepts arbitrary dotted paths just fine,
// so the cast is safe — we route through the second overload.
if (path !== undefined) {
const result = eidos.langs.t(path as Parameters<typeof eidos.langs.t>[0], params, locale);
// langs.t returns the path itself when missing — fall back to
// the captured children text if available.
if (result === path && capturedText) return capturedText;
return result;
}
// value-based or captured: pass through ts() which handles
// idlangrefs (`#?path|fallback`) and literal strings alike.
const source = value ?? capturedText;
if (source === undefined) return '';
return eidos.langs.ts(source as LangString, locale);
});
// `hasSource` is true once we have any source string to render.
// While the children-capture path runs $effect on mount, we render
// the children initially so the captureNode has text to read.
const hasSource = $derived(value !== undefined || path !== undefined || capturedText !== undefined);
</script>
<svelte:element this={as} bind:this={captureNode} data-trans="" {...rest}>
{#if hasSource}
{resolved}
{:else}
{@render children?.()}
{/if}
</svelte:element>

Powered by TurnKey Linux.