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

@pvway/alpha-ts

v18.0.0

Published

Alpha Translation Service by p.v.Way

Downloads

7

Readme

AlphaTranslationService a.k.a. AlphaTs

This library was generated with Angular CLI version 17.2.0.

The idea behind this service is to provide translations at front end side.

Actually, the scope is a little bit bigger as it is often coupled with the ExcelTranslationProvider (dotNet) component that stores translations as (embedded) Excel sheets.

Design in a nutshell

At server side the WebApi is exposing an end point that returns translations in the form of a dictionary.

Actually, the exact structure of the data returned by the Web Api is the following:

{
  "data": {
    "isUpToDate": false,
    "translationsCache": {
      "alpha.buttons.add": {
        "en": "Add",
        "fr": "Ajouter",
        "nl": "Toevoegen"
      },
      "alpha.buttons.cancel": {
        "en": "Cancel",
        "fr": "Annuler",
        "nl": "Annuleren"
      },
      "alpha.buttons.delete": {
        "en": "Delete",
        "fr": "Supprimer",
        "nl": "Verwijderen"
      }
    }
  }
}

As you can see, this is actually a dictionary<key: string, dictionary<isoLanguageCode: string, translation: string>> wrapped into a data object.

At client side the browser stores the translations into its localStorage as a wrapper that contains

  • the date of lastUpdate
  • the dictionary of translations.

When starting the app the client calls the server passing the last update date (the one he finds in the local storage of the browser).

PS. The first time the client connects to this server end point, its localStorage is logically empty, as such the client sends a request passing the Unix epoch date.

The server compares the date provided by the client with the one at server side.

If the translations need to be refreshed (i.e. the server translation date is fresher than the client one), then the server sends back a new version of the dictionary wrapped into the data object with the isUpToDate flag set to false.

The client stores the new dictionary in its localStorage.

However, when the client translations are up-to-date, the server only returns 'isUpToDate' set to true and the translationsCache object is not present. The client does not need to refresh its localStorage as its translations are still valid.

Initialization

As usual there is a small configuration for this service to work properly.

You'll need to pass the url of your server end point that will serve the translation cache update.

The following code initialize the translation service when the app starts.

You can also pass a delegate that can post error logs. For this optional parameter you can pass your own delegate or use the alpha-ls component


...
@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  templateUrl: './app.component.html',
  styleUrl: './app.component.scss'
})
export class AppComponent implements OnInit {
  ready = false;

  constructor(
    private mLs: AlphaLsService,
    private mTs: AlphaTsService) {
  }

  ngOnInit() {
    // define the end point that alpha-ls uses for logging errors
    const postErrLogUrl = environment.apiHost + '/postErrorLog';
    this.mLs.init(postErrLogUrl);
    
    // define the end point that delivers translation cache updates
    const tcUpdateUrl = environment.apiHost + '/getTranslationCacheUpdate';
    this.mTs.init(
      tcUpdateUrl, 
      this.mLs.postErrorLog).subscribe({
        next: tsStatus => {
          console.log(tsStatus);
          this.ready = true;
        }
      });
  }

}

and the template

<div @If="ready">
  <app-header></app-header>
  <router-outlet></router-outlet>
</div>

The init call is asynchronous. In the implementation above we only set the ready flag to true when the translations are loaded...

As you can see the user will have to wait until the translations are loaded before viewing any page in the app.

Usage

With this service all translations will be managed by the code... that makes your html much clearer and easier to maintain.

@Component({
  selector: 'app-ts-demo',
  standalone: true,
  imports: [],
  templateUrl: './ts-demo.component.html',
  styleUrl: './ts-demo.component.scss'
})
export class TsDemoComponent {

  titleLit: string;
  titleInFrenchLit: string;

  constructor(ts: AlphaTsService) {
    
    // following call will get the translation
    // in the default service language 
    // (see setLanguageCode(...) method
    this.titleLit = ts.getTr('demoTs.title');
    // and this one will override the default service
    // language requesting the french translation for
    // the same key ('demoTs.title')
    this.titleInFrenchLit = ts.getTr('demoTs.title', 'fr');
  }
  

I use the suffix Lit for the literals so that they are clearly identified in the template.

and the template looks like this.

<h1>{{titleLit}}</h1>
<h2>{{titleInFrenchLit}}</h2>

With this principle in place your template should never contain any translations.

Enum translations

This components also provides a generic class named AlphaTsEnumItem that stands for translated enumeration item.

This class enables you to transport enumeration values from the server to the client by using a small wrapper that contains the code, the value and the caption of a given enumeration item.

See here after the implementation

/**
 * This is a wrapper around an enumeration item that contains a code, a value,
 * and a translated caption.
 *
 * @template T - The type of the wrapped enumeration.
 */
export interface IAlphaTsEnumItem<T> {
  code: string;
  value: T;
  caption: string;
}

/**
 * Represents a factory for creating instances of AlphaTsEnum.
 */
export class AlphaTsEnumItemFactory {
  /**
   * Creates an instance of an AlphaTsEnum by using the provided enumDso and getValue function.
   *
   * @template T - The type of the enum values.
   * @param {object} enumDso - An object containing the code and translated caption properties of the enum.
   * @param {string} enumDso.code - The code property of the enum.
   * @param {string} enumDso.caption - The translated caption property of the enum.
   * @param {function} getValue - A function that retrieves the value of the enum based on its code.
   * @returns {IAlphaTsEnumItem<T>} - A new instance of the AlphaTsEnum class.
   */
  static factor<T>(
    enumDso: { code: string, caption: string },
    getValue: (code: string) => T): IAlphaTsEnumItem<T> {
    return new GenEnum<T>(
      enumDso.code,
      getValue(enumDso.code),
      enumDso.caption);
  }
}

// CONCRETE
class GenEnum<T> implements IAlphaTsEnumItem<T> {
  code: string;
  value: T;
  caption: string;

  constructor(
    code: string,
    value: T,
    caption: string) {
    this.code = code;
    this.value = value;
    this.caption = caption;
  }

}

Advanced usage


@Component({
  selector: 'app-ts-demo',
  standalone: true,
  imports: [],
  templateUrl: './ts-demo.component.html',
  styleUrl: './ts-demo.component.scss'
})
export class TsDemoComponent implements OnInit, OnDestroy {

  private sub = -1;

  titleLit: string | undefined;
  private setLiterals() {
    this.titleLit = this.mTs.getTr('demoTs.title');
  }

  constructor(
    private mTs: AlphaTsService,
    private mLbs: AlphaLbsService) {
    this.setLiterals();
  }

  ngOnInit(): void {
    this.sub = this.mLbs.subscribe(
      (lc: string) => {
        this.mTs.changeLanguageCode(lc);
        this.setLiterals();
      },
      'LANGUAGE_CODE_UPDATED');
  }

  ngOnDestroy() {
    this.mLbs.unsubscribe(this.sub);
  }

}

This implementation uses the AlphaLbsService.

A component that updates the user language could also use the AlphaLbsService for publishing the value of the selected user language with the channel 'LANGUAGE_CODE_UPDATED';

onLanguageChanged(languageCode: string): void {
    this.mLbs.publish(languageCode, 'LANGUAGE_CODE_UPDATED');
}

As a side effect of this publication your component will change the language code used by the translation service and re-populate the literals.

for more information see AlphaLbsService.

Overriding the standard API call

If needed you can also override the standard behaviour of the translation service Api (the service that gets the translations from the backend);

For this you may use the following method:

useGetTranslationCacheUpdate(getTranslationCacheUpdate: (lastUpdateDate: Date) =>
  Observable<IAlphaTranslationCache | null>): void;

And you provide your own method that returns an observable with an ITranslationCache object for a given date.

Backend side

See here after an example of implementation at back-end side... here in dotNet core 6

    [HttpGet("getTranslationCacheUpdate")]
    public async Task<ActionResult> GetTranslationCacheUpdate(DateTime clientDate)
    {
        DsoHttpResult<AlphaDsoTranslationsCacheUpdate> hRes;
        try
        {
            clientDate = clientDate.ToUniversalTime();
            var tc = (IAlphaTranslationCache?) _sp.GetService(
                typeof(IAlphaTranslationCache));
            if (tc == null) throw new Exception("tc should not be null");
            var serverDate = tc.LastUpdateDateUtc;
            var dif = serverDate - clientDate;
            var dso = dif.TotalSeconds <= 1.0
                ? new AlphaDsoTranslationsCacheUpdate(null, null)
                : new AlphaDsoTranslationsCacheUpdate(serverDate, tc.Translations);
            hRes = new DsoHttpResult<AlphaDsoTranslationsCacheUpdate>(dso);
        }
        catch (Exception e)
        {
            await Ls.LogAsync(e);
            hRes = new DsoHttpResult<AlphaDsoTranslationsCacheUpdate>(e);
        }

        return hRes.GetActionResult(this);
    }

here after the DsoHttpResult object that wraps the response data

// Decompiled with JetBrains decompiler
// Type: pvWay.MethodResultWrapper.nc6.DsoHttpResult`1
// Assembly: pvWay.MethodResultWrapper.nc6, Version=1.0.2.0, Culture=neutral, PublicKeyToken=null
// MVID: 64113074-5D4E-48F5-9DEB-A342FE05D3E6
// Assembly location: C:\Users\pierr\.nuget\packages\pvway.methodresultwrapper.nc6\1.0.2\lib\net6.0\pvWay.MethodResultWrapper.nc6.dll

using System;

#nullable enable
namespace pvWay.MethodResultWrapper.nc6
{
  public class DsoHttpResult<T> : DsoHttpResult
  {
    public T Data { get; }

    public DsoHttpResult(T data) => this.Data = data;

    public DsoHttpResult(T data, bool hasMoreResults)
      : base(SeverityEnum.Ok, hasMoreResults)
    {
      this.Data = data;
    }

    public DsoHttpResult(T data, DsoHttpResultMutationEnum mutation)
      : base(SeverityEnum.Ok, false, mutation)
    {
      this.Data = data;
    }

    public DsoHttpResult(IMethodResult res)
      : base(res)
    {
    }

    public DsoHttpResult(Exception e)
      : base(e)
    {
    }
  }
}

now the embedded data object

public class AlphaDsoTranslationsCacheUpdate
{
    public bool IsUpToDate { get; }

    public AlphaDsoTranslationsCache? TranslationsCache { get; }

    public AlphaDsoTranslationsCacheUpdate(
        DateTime? lastUpdateDate,
        IDictionary<string, IDictionary<string, string>>? translations)
    {
        if (lastUpdateDate.HasValue)
        {
            IsUpToDate = false;
            TranslationsCache = new AlphaDsoTranslationsCache(lastUpdateDate.Value, translations);
        }
        else
        {
            IsUpToDate = true;
            TranslationsCache = null;
        }
    }

}

and finally the TranslationCache

public class AlphaDsoTranslationsCache
{
    public DateTime LastUpdateDate { get; }
    public IDictionary<string, IDictionary<string, string>>? Translations { get; }

    public AlphaDsoTranslationsCache(
        DateTime lastUpdateDate,
        IDictionary<string, IDictionary<string, string>>? translations)
    {
        LastUpdateDate = lastUpdateDate;
        Translations = translations;
    }
}