npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@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, 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: [
        // provideRouter(routes),
        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.

// 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!');
    });
});