@my-ul/tod-angular-client
v18.3.0
Published
## 18.3.0
Downloads
872
Readme
Changelog
18.3.0
- Updates the withLocaleFromMessageEvent and withLocaleFromQueryParam
to hook window initialization using
APP_INITIALIZER
.
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: [
// other providers
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.
import { AsyncPipe } from "@angular/common";
import {
Component,
type ApplicationConfig,
} from "@angular/core";
import {
provideFakeTranslationOnDemand,
provideTranslationOnDemand,
useTranslationObservable,
useTranslationSignal,
withDefaultLocale,
withLabelEndpoint,
} from "@my-ul/tod-angular-client";
export const appConfig: ApplicationConfig = {
providers: [
provideTranslationOnDemand(
withDefaultLocale("en-US"),
withLabelEndpoint(
"http://localhost:4200/assets/i18n/{0}.json",
),
),
],
};
@Component({
selector: "app-hello",
standalone: true,
imports: [AsyncPipe],
template: `
@let labelsFromSignal = labelsSignal();
@let labelsFromObservable = labelsObservable | async;
<h1>{{ labelsFromSignal.HelloWorld }}</h1>
<h1>{{ labelsFromObservable.HelloWorld }}</h1>
`,
})
class HelloComponent {
// Using signal (Angular 16+)
readonly labelsSignal = useTranslationSignal({
HelloWorld: "Hello, World!",
});
// Using observable (Angular 15)
readonly labelsObservable = useTranslationObservable({
HelloWorld: "Hello, World!",
});
}
Configuration
Use the with*
setup functions to configure Translation on Demand:
import { provideHttpClient } from "@angular/common/http";
import {
type ApplicationConfig,
Component,
} from "@angular/core";
import { provideRouter } from "@angular/router";
import {
provideTranslationOnDemand,
TodLogLevel,
TranslationService,
withDefaultLocale,
withJoinChar,
withLabelEndpoint,
withLocaleFromMessageEvent,
withLocaleFromQueryParam,
withLogging,
withUrlLengthLimit,
} from "@my-ul/tod-angular-client";
interface NewLocaleEvent {
type: "locale";
locale: string;
}
const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(),
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<NewLocaleEvent>(
/** Filter to messages that are the right type */
(
event: MessageEvent<unknown>,
): event is MessageEvent<NewLocaleEvent> =>
!!event.data &&
(event.data as NewLocaleEvent).type === "locale",
/** Extract the locale from the message */
(data: NewLocaleEvent) => 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(256),
/** Log helpful information to the console. */
withLogging(TodLogLevel.Trace),
),
],
};
Interpolation with InterpolateComponent
Use the InterpolateComponent
to interpolate values into strings.
import { Component } from "@angular/core";
import { InterpolateComponent } from "@my-ul/tod-angular-client";
@Component({
selector: "app-hello",
standalone: true,
imports: [InterpolateComponent],
template: `
<p data-testid="result" [interpolate]="TheNamesBond">
<ng-template>{{ firstName }}</ng-template>
<ng-template>{{ lastName }}</ng-template>
</p>
`,
})
export class HelloComponent {
firstName = "James";
lastName = "Bond";
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:
import { type ApplicationConfig, inject } from "@angular/core";
import {
createTokenizer,
PlaceholderToken,
TextToken,
TOD_TOKENIZER,
} from "@my-ul/tod-angular-client";
const appConfig: ApplicationConfig = {
providers: [
{
provide: TOD_TOKENIZER,
useValue: createTokenizer(/__(\d+)__/g),
},
],
};
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!",
);
});
});