
Security News
arXiv Is Rate Limiting Authors Following a Flood of AI Slop Submissions
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.
@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.
@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 2,254 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
arXiv now limits authors to two submissions a month as AI slop overwhelms moderators, delays good papers, and sparks debate over applying the limit to everyone.

Research
/Security News
A new GhostAction wave hits hundreds of GitHub repos, expanding CI/CD secret theft to cloud and AI credentials in source code and git history.

Research
/Security News
Tensorlake npm SDK version 0.5.144 was compromised in a ChainDrop / Shai-Hulud attack, delivering credential-stealing malware.