Bloque F5 — sium follows lang's active locale lazily

`createSiumResolver` now reads `lang.getLocale()` on every `resolve()`
when the wired lang exposes that getter (i.e. `ActiveLang`). Pure
`EngineLang` consumers fall back to the captured construction-time
default — same behavior as before, no breakage.

Closes the audit's P2: "Sium captures `defaultLocale` at construction;
a later `Lang.setLocale(...)` was ignored unless the caller passed an
explicit `locale` to every resolve()/resolveIssue() call".

Test covers the lazy follow-through with a duck-typed `getLocale` shim
over an `EngineLang`, so the test does not need to spin up the full
`ActiveLang` Svelte runtime.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
master
dev 5 months ago
parent 529a93b813
commit 5c92a5cc7a

@ -16,12 +16,35 @@ export interface SiumResolver {
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;
@ -37,14 +60,23 @@ function fallbackMessage(message: string, params?: LangParams): string {
export function createSiumResolver(options: SiumResolverOptions): SiumResolver {
const { lang, defaultLocale, diagnostics } = options;
const resolve: SiumResolver['resolve'] = (message, params, locale = defaultLocale) => {
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, locale);
return lang.t(langPath, params, effectiveLocale(locale));
} catch (error) {
emitSiumDiagnostic(diagnostics, SIUM_DIAGNOSTIC_EVENTS.RESOLVE_FALLBACK, {
message,

@ -58,6 +58,29 @@ describe('createEngineSium()', () => {
expect(sium.resolveIssue(validationIssue, 'en')).toBe('Expected string but received number');
});
it('follows the active lang locale lazily on every resolve', () => {
const engineLang = createEngineLang({ sium: siumLangs }, 'es');
// Wrap the engine in a minimal duck-typed `getLocale()` so the
// resolver picks up the runtime locale (the production path is
// `ActiveLang`, which exposes the same getter).
let currentLocale: 'es' | 'en' = 'es';
const lang = Object.assign(Object.create(engineLang) as typeof engineLang, {
getLocale: () => currentLocale
});
const sium = createEngineSium({ lang });
const validationIssue: Issue = {
path: [],
code: 'type',
message: '#?sium.errors.type|Expected {{expected}} but received {{actual}}',
params: { expected: 'string', actual: 'number' }
};
expect(sium.resolveIssue(validationIssue)).toBe('Esperaba string pero se recibió number');
currentLocale = 'en';
expect(sium.resolveIssue(validationIssue)).toBe('Expected string but received number');
});
it('uses the local resolver fallback when EngineLang is not provided', () => {
const sium = createEngineSium();
const validationIssue: Issue = {

Loading…
Cancel
Save

Powered by TurnKey Linux.