@my-ul/tod-angular-client
v18.2.1
Published
- Updates the README to reflect the latest changes in the library.
Downloads
805
Readme
Changelog
18.2.1
- Updates the README to reflect the latest changes in the library.
18.2.0
- Adds initialization functions withLocaleFromMessageEvent and withLocaleFromQueryParam.
- Removes
useTranslationProxy
implementations. Signal and Observable are the recommended hooks, because they are more performant and easier to debug.
18.1.0
- Fixes some issues with the shared Observables used to propagate locale and label changes throughout the app.
- Changes TranslationComponent to InterpolateComponent.
- Adds unit tests to validate behaviors.
- Removes
@let
usage from InterpolateComponent to ensure backwards compatibility with 16 and 17.
18.0.0
- Introduces three implementations for useTranslation:
useTranslationSignal
,useTranslationObservable
, anduseTranslationProxy
. - Adds support for Angular 16+.
Quick Start
Install the @my-ul/tod-angular-client
package:
npm install @my-ul/tod-angular-client
# or yarn
yarn add @my-ul/tod-angular-client
- Add Translation on Demand providers to your app config:
// app/app.config.ts
import { provideTranslationOnDemand } from '@my-ul/tod-angular-client';
export const appConfig: AppConfig = {
providers: [
// provideRouter(routes),
provideTranslationOnDemand(
withDefaultLocale('en-US'),
withLabelEndpoint('http://localhost:3000/i18n/{0}.json'),
),
],
};
- Add a
useTranslation
hook to your component.
Provide a dictionary of labels to use in the event of any translation failure, such as network, or missing labels.
// app/components/hello.component.ts
import { Component } from '@angular/core';
import { AsyncPipe } from '@angular/common';
// pick a hook that fits your needs
// for Angular 16+, useTranslationSignal likely provides
// the best performance and developer experience
import {
useTranslationSignal,
useTranslationObservable,
} from '@my-ul/tod-angular-client';
@Component({
selector: 'app-hello',
standalone: true,
imports: [AsyncPipe]
template: `
@let labelsFromSignal = labelsSignal();
@let labelsFromObservable = labels$ | async;
<h1>{{ labelsFromSignal.HelloWorld }}</h1>
<h1>{{ labelsFromObservable.HelloWorld }}</h1>
`,
})
export class HelloComponent {
// Using signal (Angular 16+)
readonly labelsSignal = useTranslationSignal({
HelloWorld: 'Hello, World!',
});
// Using observable
readonly labels$ = useTranslationObservable({
HelloWorld: 'Hello, World!'
});
}
Configuration
Use the with*
setup functions to configure Translation on Demand:
import {
provideTranslationOnDemand,
withDefaultLocale,
withLocaleFromMessageEvent,
withLocaleFromQueryParam,
withLabelEndpoint,
withJoinChar,
withUrlLengthLimit,
withLogging
TodLogLevel
} from '@my-ul/tod-angular-client';
export const appConfig = {
providers: [
provideTranslationOnDemand(
/* Set the default locale to 'en-US'. ToD will not translate until a locale is provided. */
withDefaultLocale('en-US'),
/* Set the locale when the window receives a message */
withLocaleFromMessageEvent<T>(
/* filter to messages that are the right type */
(event: MessageEvent<unknown>): event is MessageEvent<T>
=> event.data.type === 'locale' && typeof event.data.locale === 'string',
/* extract the locale from the message */
(data: T) => data.locale,
),
/* Set the locale from the query parameter 'locale'. */
withLocaleFromQueryParam('locale'),
/**
* Set the label endpoint to 'http://localhost:3000/i18n/{0}.json'.
* The {0} will be replaced with the locale.
* Add {1} to include a cache buster in the request url.
*/
withLabelEndpoint('http://localhost:3000/i18n/{0}.json'),
/* Label keys will be joined with the provided character */
withJoinChar('_'),
/* If the URL length exceeds the limit, the label keys will be requested with a POST request. */
withUrlLengthLimit(2048),
/* Log helpful information to the console. */
withLogging(TodLogLevel.Trace),
),
],
};
Interpolation with InterpolateComponent
Use the InterpolateComponent
to interpolate values into strings.
// app/components/hello.component.ts
import { InterpolateComponent } from '@my-ul/tod-angular-client';
@Component({
selector: 'app-hello',
standalone: true,
imports: [InterpolateComponent],
template: `
@let i18n = translate();
<h1>{{ labels.HelloWorld }}</h1>
<p [interpolate]="i18n.TheNamesBond">
<ng-template>{{ firstName }}</ng-template>
<ng-template>{{ lastName }}</ng-template>
</p>
`,
})
export class HelloComponent {
firstName = 'James';
lastName = 'Bond';
translate = useTranslationSignal({
TheNamesBond: 'The name is {1}, {0} {1}.',
});
}
Custom Tokenizer
By default, the InterpolateComponent will tokenize using C# style placeholders. You can override this behavior by providing a custom tokenizer:
// app.config.ts
import {
provideTranslationOnDemand,
sprintfTokenizer,
Tokenizer,
} from '@my-ul/tod-angular-client';
const sprintf: Tokenizer = sprintfTokenizer;
// tokenize on __0__ instead of {0}
const customTokenizer: Tokenizer = createTokenizer(/__(\d+)__/g);
export const appConfig: AppConfig = {
providers: [
provideRouter(routes),
{
provide: TOD_TOKENIZER,
useValue: customTokenizer,
},
provideTranslationOnDemand(
withDefaultLocale('en-US'),
withLabelEndpoint('http://localhost:3000/i18n/{0}.json'),
),
],
};
Testing with Translation on Demand
Inject a mock translation service into your test. Provide the labels needed for the test.
// app/components/hello.component.spec.ts
describe('HelloComponent', () => {
let component: HelloComponent;
let fixture: ComponentFixture<HelloComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HelloComponent],
providers: [
provideFakeTranslationOnDemand({
HelloWorld: 'Hello, World!',
}),
],
}).compileComponents();
fixture = TestBed.createComponent(HelloComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render "Hello, World!"', () => {
expect(fixture.nativeElement.textContent).toContain('Hello, World!');
});
});
If your repository has the translation dictionaries, you can use import
to provide the
entire dictionary to the test harness.
// app/components/hello.component.spec.ts
describe('HelloComponent', () => {
let component: HelloComponent;
let fixture: ComponentFixture<HelloComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HelloComponent],
providers: [
provideFakeTranslationOnDemand(import('../../assets/i18n/en-US.json')),
],
}).compileComponents();
fixture = TestBed.createComponent(HelloComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should render "Hello, World!"', () => {
expect(fixture.nativeElement.textContent).toContain('Hello, World!');
});
});