Description

Uses the language integration configuration provided by an app to provide language services for a library. See LanguageIntegrationModule.forRoot().

Index

language/src/services/language-integration.service.ts

Properties
  • Public Readonly Optional config
Methods
Accessors

Constructor

constructor(config?: LanguageIntegrationConfig)

Creates an instance of LanguageIntegrationService.

Parameters:
Name Type Optional Description
config LanguageIntegrationConfig Yes

The language integration configuration provided using LanguageIntegrationModule.forRoot().

Methods

Public alternateLanguagesFor
alternateLanguagesFor(lang: string)

Retrieves the list of alternative languages to the specified language supported by the integrated app.

Parameters:
Name Type Optional Description
lang string No

The language for which to get the alternative languages.

Returns: string[]

An array of alternative languages supported by the integrated app.

Public ensureEnabled
ensureEnabled()

Ensures that the language integration module has been imported and a configuration object has been provided.

Returns: literal type
Public translate
translate(value: string, params?: Record)

Translates a value (typically a translation id) into the current language used by the integrated app.

Parameters:
Name Type Optional Description
value string No

The value to translate (typically a translation id).

params Record<string | > Yes

(Optional) Any params needed for translating the value.

Returns: string

The translation of the specified value and params in the current language used by the integrated app.

Public translateProperties
translateProperties(data: Record, paths: string[])

Dives deep into an object or an array and replaces the indicated properties in-place with their translation.

The paths argument is an array of paths representing deep properties which should be translated. For example:

// If we have a user object, we can translate its city and role properties
{
    id: 15,
    addresses: [
        { city: 'Tel Aviv', ... },
        { city: 'Rishon LeTzion' }
    ],
    system: {
        role: 'Admin'
    }
}

// Our paths would be:
`addresses[0].city`
`addresses[1].city`
`system.role`
Parameters:
Name Type Optional Description
data Record<string | > No

The object which holds the translatable properties. Can be a deeply nested object.

paths string[] No

The paths of the translatable properties to translate and replace.

Returns: void

Properties

Public Readonly Optional config
Type: LanguageIntegrationConfig
Decorators:
@Optional()
@Inject(LanguageIntegration)
The language integration configuration provided using `LanguageIntegrationModule.forRoot()`.

Accessors

changed
getchanged()

A subscribable event emitting every time the integrated app changes a language. The new language name is emitted with each change.

This will be undefined if the language integration module hasn't been imported by the app.

Returns: Observable | undefined
default
getdefault()

The default language used by the integrated app.

This will be undefined in the following cases:

  1. The language integration module hasn't been imported by the app.
  2. The default language hasn't resolved yet.
Returns: string | undefined
supported
getsupported()

The languages supported by the integrated app.

This will be undefined in the following cases:

  1. The language integration module hasn't been imported by the app.
  2. Supported languages haven't resolved yet.
Returns: [] | undefined
current
getcurrent()

The current language used by the integrated app.

This will be undefined in the following cases:

  1. The language integration module hasn't been imported by the app.
  2. The changed event hasn't emitted yet.
Returns: string | undefined
enabled
getenabled()

Indicated whether the language integration module has been imported into the app. If this is false, this service will serve no purpose.

ready
getready()

A resolvable async object which emits once when the intgrated language services are ready for operation.

This will complete immediately if the the language integration module hasn't been imported, or a ready observable hasn't been provided when importing the module.

Returns: Observable<void>
import { from, of, Observable         } from 'rxjs';
import { Inject, Injectable, Optional } from '@angular/core';

import { Destroyable                                    } from '@bespunky/angular-zen/core';
import { access                                         } from '@bespunky/angular-zen/utils';
import { LanguageIntegrationConfig, LanguageIntegration } from '../config/language-integration-config';

/**
 * Uses the language integration configuration provided by an app to provide language services for a library.
 * @see `LanguageIntegrationModule.forRoot()`.
 * 
 * @export
 * @class LanguageIntegrationService
 * @extends {Destroyable}
 */
@Injectable({ providedIn: 'root' })
export class LanguageIntegrationService extends Destroyable
{
    private $ready!        : Observable<void>;
    private defaultLang?   : string;
    private supportedLangs?: string[];
    private currentLang?   : string;

    /**
     * Creates an instance of LanguageIntegrationService.
     * 
     * @param {LanguageIntegrationConfig} [config] The language integration configuration provided using `LanguageIntegrationModule.forRoot()`.
     */
    constructor(@Optional() @Inject(LanguageIntegration) public readonly config?: LanguageIntegrationConfig)
    {
        super();

        this.initReadyObservable();

        if (config) this.initMultiLanguageSupport();
    }
    
    private initReadyObservable(): void
    {
        const ready = this.config?.ready;

        // Using of() instead of EMPTY as EMPTY only calls `complete` but not `next`.
        // This allows users to subscribe more intuitively.
        this.$ready = ready ? from(ready) : of();
    }

    private initMultiLanguageSupport(): void
    {
        this.subscribe(this.config!.changed, lang => this.currentLang = lang);

        // User's responsability to provide a completing observables.
        this.loadDefaultLanguage   ().subscribe(defaultLang => this.defaultLang    = defaultLang);
        this.loadSupportedLanguages().subscribe(languages   => this.supportedLangs = languages);
    }

    private loadDefaultLanguage(): Observable<string>
    {
        const defaultLang = this.config!.default;
    
        return typeof defaultLang === 'string' ? of(defaultLang) : from(defaultLang());
    }

    private loadSupportedLanguages(): Observable<string[]>
    {
        const supported = this.config!.supported;
        
        return Array.isArray(supported) ? of(supported) : from(supported());
    }

    /**
     * A subscribable event emitting every time the integrated app changes a language.
     * The new language name is emitted with each change.
     *
     * This will be `undefined` if the language integration module hasn't been imported by the app.
     * 
     * @readonly
     * @type {Observable<string> | undefined}
     */
    public get changed(): Observable<string> | undefined
    {
        return this.config?.changed;
    }

    /**
     * The default language used by the integrated app.
     *
     * This will be `undefined` in the following cases:
     * 1. The language integration module hasn't been imported by the app.
     * 2. The default language hasn't resolved yet.
     * 
     * @readonly
     * @type {string | undefined}
     */
    public get default(): string | undefined
    {
        return this.defaultLang;
    }

    /**
     * The languages supported by the integrated app.
     *
     * This will be `undefined` in the following cases:
     * 1. The language integration module hasn't been imported by the app.
     * 2. Supported languages haven't resolved yet.
     *
     * @readonly
     * @type {string[] | undefined}
     */
    public get supported(): string[] | undefined
    {
        return this.supportedLangs;
    }

    /**
     * The current language used by the integrated app.
     *
     * This will be `undefined` in the following cases:
     * 1. The language integration module hasn't been imported by the app.
     * 2. The `changed` event hasn't emitted yet.
     *
     * @readonly
     * @type {string | undefined}
     */
    public get current(): string | undefined
    {
        return this.currentLang;
    }
    
    /**
     * Indicated whether the language integration module has been imported into the app.
     * If this is `false`, this service will serve no purpose.
     *
     * @readonly
     * @type {boolean}
     */
    public get enabled()
    {
        return !!(this.config);
    }

    /**
     * A resolvable async object which emits once when the intgrated language services are ready for operation.
     * 
     * This will complete immediately if the the language integration module hasn't been imported, or a `ready` observable hasn't
     * been provided when importing the module.
     *
     * @readonly
     * @type {Observable<void>}
     */
    public get ready(): Observable<void>
    {
        return this.$ready;
    }

    /**
     * Retrieves the list of alternative languages to the specified language supported by the integrated app.
     *
     * @param {string} lang The language for which to get the alternative languages.
     * @returns {string[]} An array of alternative languages supported by the integrated app.
     * @throws If the language integration module hasn't been imported into the app.
     */
    public alternateLanguagesFor(lang: string): string[]
    {
        this.ensureEnabled();
        
        return this.supported!.filter(supportedLocale => supportedLocale !== lang);
    }
    
    /**
     * Translates a value (typically a translation id) into the current language used by the integrated app.
     *
     * @param {string} value The value to translate (typically a translation id).
     * @param {Record<string, unknown>} [params] (Optional) Any params needed for translating the value.
     * @returns {string} The translation of the specified value and params in the current language used by the integrated app.
     * @throws If the language integration module hasn't been imported into the app.
     */
    public translate(value: string, params?: Record<string, unknown>): string
    {
        this.ensureEnabled();

        return this.config!.translate(value, params);
    }

    /**
     * Dives deep into an object or an array and replaces the indicated properties in-place with their translation. 
     *
     * The `paths` argument is an array of paths representing deep properties which should be translated.
     * For example:
     * 
     * ```typescript
     * // If we have a user object, we can translate its city and role properties
     * {
     *     id: 15,
     *     addresses: [
     *         { city: 'Tel Aviv', ... },
     *         { city: 'Rishon LeTzion' }
     *     ],
     *     system: {
     *         role: 'Admin'
     *     }
     * }
     * 
     * // Our paths would be:
     * `addresses[0].city`
     * `addresses[1].city`
     * `system.role`
     * ```
     * 
     * @param {Record<string, unknown>} data The object which holds the translatable properties. Can be a deeply nested object.
     * @param {string[]} paths The paths of the translatable properties to translate and replace.
     * @throws If the language integration module hasn't been imported into the app.
     */
    public translateProperties(data: Record<string, unknown>, paths: string[]): void
    {
        this.ensureEnabled();
        
        paths.forEach(path =>
        {
            const valueAccessor = access<string>(data, path);
            const value = valueAccessor.get();

            if (typeof value !== 'string') return;

            valueAccessor.set(this.translate(value));
        });
    }
    
    /**
     * Ensures that the language integration module has been imported and a configuration object has been provided.
     * 
     * @throws {Error} The language integration module hasn't been imported by the app.
     */
    public ensureEnabled(): this is { config: LanguageIntegrationConfig }
    {
        if (this.enabled) return true;

        throw new Error(`
            Language integration hasn't been enabled.
            Did you import the language integration module in your app module using 'LanguageIntegrationModule.forRoot()'?
        `);
    }
}

results matching ""

    No results matching ""