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/arts/sium/engine-resolver.ts

99 lines
3.4 KiB

eidos: pilot wrapper pattern + doctrinal API conventions Toggle as the eidos pilot: subdirectory layout (recipe + Svelte wrapper + types + index + README) replacing the flat CSS-only form. Pattern is documented in eidos/components/README.md and the toggle README. Shared types live in eidos/lib/types.ts. First export is `Size` (8 values xxs..xxl + full); components narrow with `Extract<Size, ...>` per the per-component-subset doctrine. No `Eidos` prefix on types — module path already conveys the layer. API doctrine: - soma stays compound (Toggle.Provider) for symmetry with multi-part - eidos exports both default + Provider so single-part components accept both `<Toggle>` (ergonomic) and `<Toggle.Provider>` (compound-style consumers) SoundChannel eager-init fixes the autoplay race: AudioContext is created + resumed synchronously on the first user gesture (capture-phase listener registered in the constructor), avoiding the previous race where the first emit() scheduled the resume in a microtask outside the gesture window. Demo page (web/routes/toggle/+page.svelte) restructured so the live preview renders ALWAYS above the tablist — Sema-tab Play buttons can fire on the real toggle. Motion preview amplifies scale ×8 visually only; doctrinal values stay in the <dl>. Conventions 7-13 added to src/docs/sema-implementation-guide.md covering: directory structure, wrapper composition, no Eidos prefix, soma compound vs eidos flat, iconOnly sr-only body, sound eager-init, docs-preview amplification. CLAUDE.md gets a session hand-off block listing where things stand and next concrete steps (migrate switch/collapsible/dialog/drawer/popover/ toast/avatar; wire topbar sound mute to masterGain; rename theme tokens to drop the success/warning/danger fallback aliases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import {
LANG_ID_FALLBACK_SEPARATOR,
parseLangRef,
type LangParams,
type SupportedLocale
} from '$libs/lang';
import type { EngineLang } from '$lang';
import { SIUM_DIAGNOSTIC_EVENTS } from './consts';
import { emitSiumDiagnostic, type SiumDiagnostics } from './diagnostics';
import { resolve as resolveFallbackTemplate } from './langs';
import type { Issue } from './core';
export interface SiumResolver {
readonly resolve: (message: string, params?: LangParams, locale?: SupportedLocale) => string;
readonly resolveIssue: (issue: Issue, locale?: SupportedLocale) => string;
readonly resolveIssues: (issues: ReadonlyArray<Issue>, locale?: SupportedLocale) => string[];
}
/**
* Duck-typed shape: when Sium is wired to `ActiveLang` (which exposes a
* runtime `getLocale()`), the resolver follows the active locale lazily.
* `EngineLang` does not expose this getter, so consumers wired to the
* pure engine still use the captured construction-time default.
*/
interface ActiveLocaleSource {
readonly getLocale: () => SupportedLocale;
}
interface SiumResolverOptions {
readonly lang?: EngineLang;
/**
* Locale used when neither the caller passes one nor `lang.getLocale`
* exists. When `lang` is wired through `ActiveLang`, the resolver
* reads `lang.getLocale()` on every call so a runtime
* `Lang.setLocale(...)` takes effect immediately.
*/
readonly defaultLocale: SupportedLocale;
readonly diagnostics: SiumDiagnostics;
}
function hasActiveLocaleSource(lang: EngineLang | undefined): lang is EngineLang & ActiveLocaleSource {
return (
lang !== undefined &&
typeof (lang as Partial<ActiveLocaleSource>).getLocale === 'function'
);
}
function composeLangPath(message: string): string | undefined {
const parsed = parseLangRef(message);
if (parsed === null) return undefined;
if (parsed.fallback === undefined) return parsed.path;
return `${parsed.path}${LANG_ID_FALLBACK_SEPARATOR}${parsed.fallback}`;
}
function fallbackMessage(message: string, params?: LangParams): string {
const parsed = parseLangRef(message);
return resolveFallbackTemplate(parsed?.fallback ?? parsed?.path ?? message, params);
}
export function createSiumResolver(options: SiumResolverOptions): SiumResolver {
const { lang, defaultLocale, diagnostics } = options;
function effectiveLocale(explicit: SupportedLocale | undefined): SupportedLocale {
if (explicit !== undefined) return explicit;
// Read lang's current locale lazily when available so a runtime
// `setLocale()` is honored. EngineLang has no `getLocale()`; for
// that path we fall back to the construction-time default.
if (hasActiveLocaleSource(lang)) return lang.getLocale();
return defaultLocale;
}
const resolve: SiumResolver['resolve'] = (message, params, locale) => {
const langPath = composeLangPath(message);
if (lang === undefined || langPath === undefined) {
return fallbackMessage(message, params);
}
try {
return lang.t(langPath, params, effectiveLocale(locale));
} catch (error) {
emitSiumDiagnostic(diagnostics, SIUM_DIAGNOSTIC_EVENTS.RESOLVE_FALLBACK, {
message,
error
});
return fallbackMessage(message, params);
}
};
const resolveIssue: SiumResolver['resolveIssue'] = (entry, locale) => {
return resolve(entry.message, entry.params, locale);
};
const resolveIssues: SiumResolver['resolveIssues'] = (issues, locale) => {
return issues.map((entry) => resolveIssue(entry, locale));
};
return { resolve, resolveIssue, resolveIssues };
}

Powered by TurnKey Linux.