Use plural naming in langs docs

active-uix
dev 5 months ago
parent 77c37cd940
commit e117141c8f

@ -1,4 +1,4 @@
# lang
# langs
Type-safe i18n library for SvelteKit. **Zero external dependencies.** Reactive
translation resolution with fallback chain, pluralization via
@ -6,7 +6,7 @@ translation resolution with fallback chain, pluralization via
## Handoff 2026-05-12
`Lang` no es `locale` y no debe decidir formatos. En la arquitectura a cerrar,
`Langs` no es `locale` y no debe decidir formatos. En la arquitectura a cerrar,
`ActiveLangs` consume la preferencia de idioma (`prefs.language`) que le pasa la
composicion (`ActiveApp` o `ActiveUix` standalone). El locale de formato vive
en `prefs.locale` y pertenece a `Format`.
@ -16,7 +16,7 @@ en `prefs.locale` y pertenece a `Format`.
## Architecture
```
lang/
langs/
├── index.ts Barrel exports
├── types.ts LangBase, SupportedLocale, LangRecord, LangNode, ...
├── engine-langs.ts Pure factory: createEngineLangs()
@ -172,15 +172,15 @@ export type TranslationSchema = typeof translations;
import { createEngineLangs } from '$langs';
import { translations } from './translations';
const lang = createEngineLangs(translations, 'es');
const langs = createEngineLangs(translations, 'es');
// `t()` and `ts()` require an explicit locale on the pure engine.
lang.t('common.ok', undefined, 'es'); // 'Aceptar'
lang.t('greet', { name: 'Ana' }, 'en'); // 'Hello, Ana'
lang.t('messages.unread', { count: 5 }, 'en'); // '5 unread messages'
langs.t('common.ok', undefined, 'es'); // 'Aceptar'
langs.t('greet', { name: 'Ana' }, 'en'); // 'Hello, Ana'
langs.t('messages.unread', { count: 5 }, 'en'); // '5 unread messages'
lang.ts({ es: 'Hola', en: 'Hello' }, 'en'); // 'Hello'
lang.ts('#?common.ok', 'es'); // 'Aceptar'
langs.ts({ es: 'Hola', en: 'Hello' }, 'en'); // 'Hello'
langs.ts('#?common.ok', 'es'); // 'Aceptar'
```
### `ts()` and the `LangString` type
@ -195,19 +195,19 @@ type LangString = string | LangRecord | LangRef;
This is the idiomatic way to accept "maybe translatable" text through
component boundaries: a prop typed `message: LangString` accepts all three
shapes transparently, and components call `lang.ts(message)` to render.
shapes transparently, and components call `langs.ts(message)` to render.
```ts
// 1) Plain string — passthrough, no lookup
lang.ts('Static text', 'en'); // 'Static text'
langs.ts('Static text', 'en'); // 'Static text'
// 2) LangRecord — inline per-locale values
lang.ts({ es: 'Guardar', en: 'Save', ar: 'حفظ' }, 'en'); // 'Save'
lang.ts({ es: 'Solo español' }, 'en'); // 'Solo español' (single-locale fallback)
langs.ts({ es: 'Guardar', en: 'Save', ar: 'حفظ' }, 'en'); // 'Save'
langs.ts({ es: 'Solo español' }, 'en'); // 'Solo español' (single-locale fallback)
// 3) LangRef — alias to a schema key (optional literal fallback)
lang.ts('#?common.ok', 'es'); // resolves to 'Aceptar'
lang.ts('#?missing.key|Default', 'es'); // fallback literal wins
langs.ts('#?common.ok', 'es'); // resolves to 'Aceptar'
langs.ts('#?missing.key|Default', 'es'); // fallback literal wins
```
Practical pattern — a generic button component that accepts a translatable label:
@ -220,10 +220,10 @@ Practical pattern — a generic button component that accepts a translatable lab
import type { ActiveLangs } from '$langs';
let { label, onclick }: { label: LangString; onclick: () => void } = $props();
const lang = getContext<ActiveLangs>('lang');
const langs = getContext<ActiveLangs>('langs');
</script>
<button {onclick}>{lang.ts(label)}</button>
<button {onclick}>{langs.ts(label)}</button>
```
Callers pass whichever form fits:
@ -251,12 +251,12 @@ if (isLangString(value)) { /* value is one of the three */ }
import { createActiveLangs } from '$langs/active-langs.svelte';
import { translations } from './translations';
const lang = createActiveLangs(translations, 'es');
const langs = createActiveLangs(translations, 'es');
</script>
<h1>{lang.t('common.ok')}</h1>
<button onclick={() => lang.setLocale('en')}>EN</button>
<button onclick={() => lang.setLocale('ar')}>AR</button>
<h1>{langs.t('common.ok')}</h1>
<button onclick={() => langs.setLocale('en')}>EN</button>
<button onclick={() => langs.setLocale('ar')}>AR</button>
```
`createActiveLangs` holds `locale` in `$state`. When you call
@ -267,18 +267,18 @@ Signatures on the reactive wrapper make `locale` **optional** — it defaults
to the active reactive locale:
```ts
lang.t('common.ok'); // uses active locale
lang.t('common.ok', undefined, 'en'); // explicit locale override
langs.t('common.ok'); // uses active locale
langs.t('common.ok', undefined, 'en'); // explicit locale override
```
### Fallback chain
```ts
// Resolution order: currentLocale → en → es (default)
const lang = createActiveLangs(translations, 'es', ['en']);
const langs = createActiveLangs(translations, 'es', ['en']);
lang.setLocale('fr');
lang.t('common.ok'); // fr missing → tries en → 'OK'
langs.setLocale('fr');
langs.t('common.ok'); // fr missing -> tries en -> 'OK'
```
### Dynamic modules — `extend()`
@ -287,19 +287,19 @@ Adds translations to the schema at runtime. Supports dotted namespaces and
deep-merges existing keys:
```ts
lang.extend('shop', {
langs.extend('shop', {
product: { es: 'Producto', en: 'Product' }
});
lang.t('shop.product'); // 'Producto'
langs.t('shop.product'); // 'Producto'
// Dotted namespace — creates nested structure
lang.extend('app.settings', {
langs.extend('app.settings', {
title: { es: 'Ajustes', en: 'Settings' }
});
// Subscribe to schema changes (triggers re-render in Svelte automatically)
const unsub = lang.onSchemaChange(() => {
const unsub = langs.onSchemaChange(() => {
// schema changed
});
```
@ -319,7 +319,7 @@ const unsub = lang.onSchemaChange(() => {
registering the same key by mistake.
```ts
lang.extend('common', { ok: { es: 'Sí', en: 'Yes' } });
langs.extend('common', { ok: { es: 'Sí', en: 'Yes' } });
// DEV: [lang] extend(): leaf at "common.ok" was overwritten silently.
// Last write wins — register namespaces uniquely or check for
// collisions before extending.
@ -335,24 +335,24 @@ Creates a new instance with the module merged. In the reactive wrapper the
child shares locale state with the parent:
```ts
const child = lang.register('admin', {
const child = langs.register('admin', {
dashboard: { es: 'Panel', en: 'Dashboard' }
});
child.t('admin.dashboard'); // 'Panel'
child.t('common.ok'); // 'Aceptar' (inherits parent schema)
lang.setLocale('en');
langs.setLocale('en');
child.getLocale(); // 'en' (synced)
child.dispose(); // detach from parent
lang.setLocale('es');
langs.setLocale('es');
child.getLocale(); // 'en' (stopped syncing)
```
---
## Mono lang — single-language passthrough
## Mono langs — single-language passthrough
When the app does not need translation, use `createActiveMonoLangs()` to get an
`ActiveLangs`-shaped instance whose `t()` / `ts()` methods are passthrough:
@ -360,48 +360,48 @@ When the app does not need translation, use `createActiveMonoLangs()` to get an
```ts
import { createActiveMonoLangs } from '$langs/mono-langs.svelte';
const lang = createActiveMonoLangs();
const langs = createActiveMonoLangs();
lang.t('common.ok'); // 'common.ok'
lang.t('common.ok|Aceptar'); // 'Aceptar'
lang.t('greet|Hello {{name}}', { name: 'Ada' }); // 'Hello Ada'
lang.ts('plain text'); // 'plain text'
lang.ts({ es: 'Hola', en: 'Hello' }); // 'Hola' (first non-empty entry)
langs.t('common.ok'); // 'common.ok'
langs.t('common.ok|Aceptar'); // 'Aceptar'
langs.t('greet|Hello {{name}}', { name: 'Ada' }); // 'Hello Ada'
langs.ts('plain text'); // 'plain text'
langs.ts({ es: 'Hola', en: 'Hello' }); // 'Hola' (first non-empty entry)
```
The locale is held as `$state` so consumers that subscribe via
`onLocaleChange` still react to `setLocale`. In `ActiveApp`, the lang service
`onLocaleChange` still react to `setLocale`. In `ActiveApp`, the langs service
is normally synchronized from `prefs.language`; `Format` uses `prefs.locale`
instead.
In DEV the mono lang warns once per unresolved path via the supplied logger
In DEV the mono langs adapter warns once per unresolved path via the supplied logger
under category `lang.mono` — strong signal that the caller forgot to
configure a real lang or to add `|fallback` text. Calls that include
configure real translations or to add `|fallback` text. Calls that include
`|fallback` are silent.
```ts
const lang = createActiveMonoLangs({ logger: app.Logger });
lang.t('users.profile.name'); // warns once
lang.t('users.profile.name'); // dedup, no second warn
lang.t('users.profile.email'); // different path → warns once
const langs = createActiveMonoLangs({ logger: app.logger });
langs.t('users.profile.name'); // warns once
langs.t('users.profile.name'); // dedup, no second warn
langs.t('users.profile.email'); // different path -> warns once
```
Mono lang is consumed automatically by `createActiveApp({ /* no lang */ })`
`createActiveMonoLangs()` is used automatically by `createActiveApp({ /* no langs */ })`
so monolingual apps never have to wire it themselves.
### Mono lang type contract
### Mono langs type contract
`createActiveMonoLangs()` is public, but intentionally **type-loose**: there is
no schema, so it cannot provide typed path autocomplete or static missing-key
checks. `createActiveApp<S>()` casts the mono implementation to
`ActiveLangs<S>` when the app is created without `lang`, only to keep the root
`ActiveLangs<S>` when the app is created without `langs`, only to keep the root
surface uniform (`App.langs.t(...)` always exists).
Treat that cast as a null-object convenience, not as a type guarantee:
- Use `createActiveApp({ langs: { schema, defaultLocale } })` when translation
key correctness matters.
- Use mono-lang for monolingual apps, prototypes or apps that deliberately want
- Use mono langs for monolingual apps, prototypes or apps that deliberately want
literal/fallback strings.
- Add `|fallback` text for user-facing strings so missing i18n configuration
does not paint internal paths in the UI.

@ -6,7 +6,7 @@ import type { Logger } from '$libs/logger';
export type { LangNode, LangParams, LangString, SupportedLocale, ActiveLangs };
/**
* Reactive wrapper around the pure lang engine for Svelte 5.
* Reactive wrapper around the pure langs engine for Svelte 5.
*
* The engine is stateless regarding locale — this wrapper owns the
* `_locale` `$state` and injects it into every `t()`/`ts()` call.

@ -1,4 +1,4 @@
// Public surface of the lang artifact. The pure contracts (types,
// Public surface of the langs artifact. The pure contracts (types,
// constants, helpers, plural rules, JSON serialization, diagnostics
// builders, error catalog) live in `$libs/langs`. This barrel mirrors
// them for back-compat — existing `import … from '$langs'` imports

@ -21,7 +21,7 @@ import type { Logger } from '$libs/logger';
export { LANG_MONO_LANG_CATEGORY } from '$libs/langs';
/**
* Default initial locale for `createActiveMonoLangs()`. The mono lang does not
* Default initial locale for `createActiveMonoLangs()`. The mono langs adapter does not
* translate, so the value is mostly cosmetic — it determines what
* `getLocale()` returns until `setLocale()` is called and what consumers
* see when they subscribe to `onLocaleChange`.
@ -31,9 +31,9 @@ export const DEFAULT_MONO_LOCALE: SupportedLocale = 'en';
/**
* Single-language `ActiveLangs` for monolingual apps.
*
* Used by `createActiveApp()` when the caller does not configure `lang`. Also
* Used by `createActiveApp()` when the caller does not configure `langs`. Also
* usable standalone for apps that have no i18n needs at all but still want a
* uniform `lang.t(...)` / `lang.ts(...)` call site.
* uniform `langs.t(...)` / `langs.ts(...)` call site.
*
* Behavior:
*
@ -46,9 +46,9 @@ export const DEFAULT_MONO_LOCALE: SupportedLocale = 'en';
* The locale is held as `$state` so subscribers still see updates from
* `setLocale`.
*
* In DEV the mono lang warns (deduplicated by path, once per path) when
* In DEV the mono langs adapter warns (deduplicated by path, once per path) when
* `t()` / `ts()` returns a path-shaped value verbatim — strong signal that
* the caller forgot to configure `lang` or to add `|fallback` text. Warnings
* the caller forgot to configure `langs` or to add `|fallback` text. Warnings
* flow through the supplied logger under category `lang.mono`. Calls that
* include `|fallback` are silent because the caller intentionally provided
* fallback text.

@ -43,7 +43,7 @@ export function isLangCircularReferenceError(
// ── Legacy diagnostic / log message strings ────────────────────────────
//
// These are not thrown errors — they are diagnostic/log messages built
// at the call site by the lang resolver. Kept here next to the typed
// at the call site by the langs resolver. Kept here next to the typed
// errors so the module's user-facing strings live together.
export const LANG_ERRORS = {
@ -84,5 +84,5 @@ export const LANG_ERRORS = {
`${LANG_ERRORS.KEY_NOT_FOUND(path)}. Using fallback "${fallback}".`,
MONO_PATH_RETURNED: (path: string, kind: 't' | 'ts'): string =>
`Mono langs: ${kind}("${path}") returned the path verbatim. Configure a real lang schema or use the |fallback suffix to provide literal text.`
`Mono langs: ${kind}("${path}") returned the path verbatim. Configure real translations or use the |fallback suffix to provide literal text.`
} as const;

@ -1,5 +1,5 @@
/**
* `libs/langs` — pure contracts and helpers for the lang artifact.
* `libs/langs` — pure contracts and helpers for the langs artifact.
*
* Mirrors the `libs/bus` / `libs/logger` / `libs/dom` pattern: this module
* exports interfaces, generic constants, error catalogs, pure helpers,
@ -8,7 +8,7 @@
* `arts/langs/engine-langs.ts` and the active wrappers in
* `arts/langs/active-langs.svelte.ts` / `arts/langs/mono-langs.svelte.ts`.
*
* Modules that need types or pure helpers from lang import from
* Modules that need types or pure helpers from langs import from
* `$libs/langs`. Only the composition root (`aapp`) and tests touch
* `$langs` to reach the engine factories.
*/

Loading…
Cancel
Save

Powered by TurnKey Linux.