
Security News
GitHub to Block pull_request_target Workflows by Default Starting December 2
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.
@ultimat3/i18n
Advanced tools
Dependency-free translator, catalog flattening, locale negotiation and loud missing-key rendering
Golden rule: no user-facing string is written twice, and a missing one is loud.
Every string goes through t(). A key with no translation renders ⟦key⟧ — never blank,
never the English fallback, never silence. Numbers, dates and money are not i18n
strings: format those with Intl via @ultimat3/money and @ultimat3/time.
| Concern | Owner | Rule |
|---|---|---|
| Registration | define-catalogs.ts | defineCatalogs() — one call, at boot, per app |
| Lookup + interpolation | translator.ts | t(key, vars?), miss → ⟦key⟧ |
| Catalog shape | catalog.ts | nested authoring → flat dot-keys |
| Placeholders + plurals | interpolate.ts | {var}, CLDR categories via Intl.PluralRules |
| Locale resolution | locales.ts | supported set, normalizeLocale, negotiateLocale |
| Request locale | context.ts | resolved once, read from ALS — never passed by hand |
| Enforcement | extract.ts | x verify fails on a missing key in a shipped locale |
defineCatalogs is the only way an app registers strings. Never call registerCatalog by hand.
// the app's own packages/i18n/src/index.ts — its whole i18n module
import { defineCatalogs, type TranslationKey, type Translator, useI18n } from '@ultimat3/i18n';
import en from '../catalogs/en.json';
import es from '../catalogs/es.json';
export const catalogs = defineCatalogs({ default: 'en', locales: { en, es } });
export type AppCatalog = typeof en;
export type AppKey = TranslationKey<AppCatalog>; // 'nav.home' | 'posts.likes' | …
export const useT = (): Translator<AppCatalog> => useI18n<AppCatalog>();
| One call | Because |
|---|---|
| validates + flattens every locale before registering any | a malformed catalog fails the boot whole, never half |
registers the framework catalog under en first, the app's second | later wins, so app strings override framework strings |
configureLocales({ supported, fallback }) | the locale set is declared once, not twice |
returns { default, locales, catalogs, keys() } | the app's key space, for tests and tooling |
A default outside locales is X_LOCALE_UNSUPPORTED — and a compile error before that.
import { negotiateLocale, t } from '@ultimat3/i18n';
// per-request: the HTTP layer resolves the locale once
const locale = negotiateLocale(request.headers.get('accept-language')); // 'de'
// anywhere downstream — no locale argument, ever
t('pagination.showing', { from: 1, to: 20, total: 137 });
t('pagination.result', { count: 1 }); // "1 result"
t('pagination.result', { count: 9 }); // "9 results"
t('nav.settings'); // "⟦nav.settings⟧" — fix it or ship it broken, visibly
t('pagination.result'); // "⟦count⟧ result" — vars or none, the template is always
// interpolated; t.raw(key) is the template itself
| Call | Returns | Use for |
|---|---|---|
t(key, vars?) | string | one string, framework or app, untyped keys |
useI18n<AppCatalog>() | Translator<AppCatalog> | a component rendering several — unknown key is a build error |
translatorFor(locale) | Translator | an explicit locale: mail, a worker tick, a preview |
Translator with no type argument keeps taking any string, which is what @ultimat3/ui and
@ultimat3/mail are built against. has() and raw() stay permissive on purpose — a probe for
a key that may not exist is what they are for.
n === 1Author two forms with key / key_plural, or all forms a locale needs with
key_<category> where category is zero | one | two | few | many | other.
{ "files": { "n_one": "{count} plik", "n_few": "{count} pliki", "n_many": "{count} plików" } }
t('files.n', { count: 3 }) → 3 pliki, { count: 5 } → 5 plików. Selection runs
through Intl.PluralRules, so Polish, Russian and Arabic work without a special case.
Suffixes are underscores on the leaf, never a nested { one, other } branch: the runtime probes
files.n_few, and TranslationKey admits the stem files.n for exactly the same reason.
Author nested and feature-namespaced; defineCatalogs flattens to dot-keys and rejects a
non-string leaf with X_CATALOG_INVALID. Registration order is framework then app — later
wins, which is how an app overrides errors.notFound.title without forking the framework.
src/catalogs/en.json ships the framework's own strings: errors.*, auth.*,
pagination.*, admin.*, validation.*, common.*, time.cron.*.
It is registered under en and no other locale — the golden rule applies to framework strings
too. An app shipping es that has not translated errors.notFound.title renders
⟦errors.notFound.title⟧ there, not Page not found: filling every locale with the English
catalog is a fallback chain, it reads as isMiss === false, and assertCatalogsComplete cannot
see it because CatalogSet.catalogs carries app strings only. Translate the framework keys your
app renders into your own catalog — that is the one path, and it is the same merge an override is.
A translator is a function and cannot be an island's prop. catalogSubset(t, keys) resolves the
templates keys name — every plural variant included — into { locale, catalog }, plain JSON;
the island builds subsetTranslator(subset) over it: the same lookup, interpolation, CLDR plural
pick and loud ⟦key⟧ miss as the server's. Both are on @ultimat3/i18n/subset, an entry that
installs no framework catalog (the barrel does), so an island chunk can import it.
@ultimat3/ui's uiCatalog(t) is the design system's own slice.
@ultimat3/i18n/app-catalogs — server-only, its own entry so no island bundle carries it:
| Export | What |
|---|---|
APP_CATALOGS_PATH | 'packages/i18n/src/index.ts', where x new writes the app's defineCatalogs() |
loadAppCatalogs(root) | imports that module and answers { locales, defaultLocale } (default first), or undefined when no defineCatalogs() was declared — never the ambient localeConfig() read blind. An exported set answers for itself, whatever the locale config says by then. A module that throws rejects with its own error |
appLocaleSet(root) | the same answer, with UNDECLARED_LOCALES for an app that declared nothing — what every consumer that needs a locale asks |
UNDECLARED_LOCALES | { locales: ['en'], defaultLocale: 'en' }, frozen: the one answer for an app with no catalogs, so the PWA manifest, the service worker, the prerender and every x command agree |
AppLocaleSet | the type of both answers |
The one reader the CLI (loadApp's locales, the pwa manifest and service worker, the static export, x shot, x g, x i18n) and @ultimat3/testing's e2e preload share. @ultimat3/testing asks loadAppCatalogs deliberately: an app that declared nothing pins no browser language rather than a guessed one.
DEFAULT_LOCALE, directionOf and isRtl are @ultimat3/core's since 25.0.0 — import them from there. The barrel still re-exports the three until @ultimat3/mail and @ultimat3/ui move (marked PENDING in src/index.ts); Direction stays a type re-export, because LocaleResolution.direction names it.
extractKeys() scans source for t('...') calls; auditCatalogs() reports keys used,
keys missing per locale, and keys defined but never used; assertCatalogsComplete() is
the x verify gate.
X_CATALOG_MISSING_KEYS: catalog is incomplete
cause: packages/i18n/catalogs/es.json is missing 2 key(s) used in source: admin.nav.jobs, common.save
fix: x i18n sync es
| Code | When |
|---|---|
X_LOCALE_UNSUPPORTED | a tag outside the supported set was asserted, or a defineCatalogs default that is not one of its locales |
X_LOCALE_INVALID | defineCatalogs or configureLocales was given a tag that is not well-formed BCP 47 (en_US). @ultimat3/core's code, raised by its assertLocale — not an I18nError and not in I18N_ERROR_CODES |
X_CATALOG_MISSING_KEYS | a shipped locale lacks a key the source uses |
X_CATALOG_INVALID | non-string leaf, bad key segment, or a dotted/nested collision |
Every error class src/index.ts exports, for instanceof inside one process. Across a wire or
a job boundary the class is gone and the code is what survives — match on that.
| Class | Code | Declared in |
|---|---|---|
I18nError | X_LOCALE_UNSUPPORTED, X_CATALOG_MISSING_KEYS, X_CATALOG_INVALID, X_CATALOG_UNREGISTERED | src/errors.ts |
Retrofitting i18n means touching every string in the app. Structuring for many locales on day one costs nothing; the loud-miss rule is what keeps it honest once there are two.
FAQs
Dependency-free translator, catalog flattening, locale negotiation and loud missing-key rendering
The npm package @ultimat3/i18n receives a total of 3,592 weekly downloads. As such, @ultimat3/i18n popularity was classified as popular.
We found that @ultimat3/i18n demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GitHub is tightening GitHub Actions security with new restrictions on privileged pull request workflows. Here's what changes and how maintainers are affected.

Security News
/Application Security
A practical guide to securing GitHub Actions against supply chain attacks, covering malicious pull requests, credential theft, dependency risks, and release pipelines.

Security News
Composer 2.11 adds dependency cooldowns, joining package managers that delay new releases to reduce supply chain risks while allowing urgent security updates.