@malva-ui/i18n
v0.2.0
Published
Localisation for Malva UI — signal-based per-component i18n with ICU MessageFormat and 14 lazy-loaded language packs.
Maintainers
Readme
@malva-ui/i18n
Localisation for Malva UI — signal-based, per-component, with ICU MessageFormat and lazy-loaded language packs.
Every string a Malva UI component renders (pagination labels, dialog close buttons, empty-state copy, date-picker month names) comes from here. Nothing is hardcoded in the components.
Install
npm install @malva-ui/i18nInstalled automatically by ng add @malva-ui/core.
Quick start
Register one lazy-loaded language pack in your root providers:
import { provideMlvI18n } from '@malva-ui/i18n';
export const appConfig: ApplicationConfig = {
providers: [provideMlvI18n(() => import('@malva-ui/i18n/en'))],
};The pack is fetched during app initialisation, so it is a separate chunk — you ship one locale, not fourteen.
The loader may resolve to either shape of MlvLanguageModule: a module whose
default export is the pack, or one that exposes it under a <locale>Language
name — which is what the published locale entry points carry, since the package
build flattens each one to a single named export. Importing a locale entry point
directly, as above, therefore works unchanged; a pack you write yourself uses
export default.
import { provideMlvI18n, type MlvLanguageModule } from '@malva-ui/i18n';
// Both are accepted.
provideMlvI18n(() => import('@malva-ui/i18n/en')); // export const enLanguage
provideMlvI18n(() => import('./my-own-pack')); // export default myPack
// `MlvLanguageModule` types a loader you hold yourself.
const loader: () => Promise<MlvLanguageModule> = () => import('@malva-ui/i18n/uk');MlvI18nService.switchLanguage() takes the same two shapes, so runtime locale
switching needs no wrapper either.
One module, one pack. default wins whenever it is there, and is used
as-is. Without one, the module must expose exactly one <locale>Language — the
loader throws rather than guessing:
// Fine — `default` is returned as-is, whatever your pack's keys are called.
provideMlvI18n(async () => ({ default: myPack }));
// Throws: two `<locale>Language` exports, and which one wins would be the order
// your barrel happens to declare them in.
provideMlvI18n(() => import('./locales')); // export * from '…/en'; export * from '…/de';Re-export a single locale, or pick one explicitly —
import('@malva-ui/i18n/en').then((pack) => ({ default: pack.enLanguage })).
Available locales
de · en · es · fr · id · it · ja · nl · pl · pt · ro · tr · uk · zh-Hans
provideMlvI18n(() => import('@malva-ui/i18n/uk'));Translating your own strings
MlvTranslatePipe and MlvI18nService read the same message store, so application copy can share the component locale:
import { MlvTranslatePipe } from '@malva-ui/i18n';
@Component({ imports: [MlvTranslatePipe] })
export class MyComponent {}Messages are ICU MessageFormat, so plurals and selects work as expected:
{count, plural, =0 {No results} one {# result} other {# results}}Formatting locale
Plural categories, # number formatting and Malva UI's dates format in one
locale, MLV_LOCALE — a Signal<string> holding the active pack's locale,
falling back to Angular's LOCALE_ID while no pack is loaded or when the pack
declares none. It moves with MlvI18nService.switchLanguage(), and it never
reads the browser language, so a server render and its hydration agree.
Every shipped pack declares its locale (pt declares pt-PT). A pack you write
yourself declares it in the optional locale field — a BCP 47 tag; without one,
the pack formats in LOCALE_ID:
import type { MlvLanguage } from '@malva-ui/i18n';
export default {
locale: 'de-CH',
// …every component slice
} satisfies MlvLanguage;MLV_LOCALE always reports a canonical tag: a malformed pack locale falls back
to LOCALE_ID instead of making Intl throw. Read it to format your own values
in the same locale:
import { MLV_LOCALE } from '@malva-ui/i18n';
private readonly _locale = inject(MLV_LOCALE);
protected readonly _price = computed(() =>
new Intl.NumberFormat(this._locale(), { style: 'currency', currency: 'EUR' })
.format(this.amount()),
);Provide it yourself only to decouple formatting from the pack, and do it in the
application providers — { provide: MLV_LOCALE, useValue: signal('de-CH').asReadonly() }.
Testing
@malva-ui/i18n/testing provides helpers for unit tests that render Malva UI components without booting a real language pack.
Peer dependencies
@angular/core, intl-messageformat (>=10).
Related packages
@malva-ui/core— the component library@malva-ui/cdk— headless primitives@malva-ui/editor— rich-text editor
Versioning and support
Semver contract, what counts as public API, the deprecation window and the support window per major: VERSIONING.md (the repository is private while the library is pre-1.0, so the link needs repository access — ask us for the policy if it 404s for you).
License
MIT
