@my-ul/tod-angular-client
TypeScript icon, indicating that this package has built-in type declarations

18.3.0 • Public • Published

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, and useTranslationProxy.
  • 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
  1. 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"),
    ),
  ],
};
  1. 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!",
    );
  });
});

Package Sidebar

Install

npm i @my-ul/tod-angular-client

Weekly Downloads

78

Version

18.3.0

License

none

Unpacked Size

79.8 kB

Total Files

32

Last publish

Collaborators

  • bradkovach
  • matthew.gardner