Skip to main content
Version: 10

Localize custom Freedom UI component implemented using remote module

Level: intermediate

Localize custom Freedom UI components during development to save time spent on translating the ready-to-use custom Freedom UI component. Implementing localization during development ensures that all user-facing labels, captions, and messages are available in the target language from the first release. This applies to custom Freedom UI components as well as related artifacts such as validators, converters, and request handlers implemented using remote module.

Localize custom validators, converters and request handlers​

Localize custom validators, converters and request handlers using Angular DI (dependency injection). To do this:

  1. Import the SysValuesService functionality from the @creatio-devkit/common library into the component to retrieve the Creatio UI language of the current user from the userCulture system variable. We recommend using a dedicated function for this purpose. The SysValuesService service is used to localize system variables.

    "some-component.component.ts" file
    /* Import the required functionality from the libraries. */
    import { SysValuesService } from '@creatio-devkit/common';

    constructor() {}

    public async getUserCulture(): Promise<string> {
    /* Create an instance of the "SysValuesService" service from "@creatio-devkit/common." */
    const sysValuesService = new SysValuesService();
    /* Retrieve the system variable values. */
    const sysValues = await sysValuesService.loadSysValues();
    return sysValues.userCulture.displayValue;
    }
  2. Use an external library or implement the custom service to localize custom Freedom UI component. We recommend using the @ngx-translate external library for this purpose. Learn more: official vendor documentation (GitHub).

    For example, SomeTranslationService is an external library or custom service to localize custom Freedom UI components.

  3. If needed, enable support for emitting type metadata for decorators that works with the reflect-metadata module.

    1. Run the npm i reflect-metadata command in the Visual Studio Code terminal to install the reflect-metadata npm package if needed.
    2. Open the "tsconfig.json" file of the Angular project and make sure the experimentalDecorators and emitDecoratorMetadata properties are set to true.
    "tsconfig.json" file
    {
    "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    },
    }
  4. Receive the dependencies of the validator, converter, or request handler.

    1. Open the "runtime.feature-activation.ts" file.
    2. Ensure that the existing bootstrapCrtModule() call contains the resolveDependency() method. The bootstrapCrtModule() call registers the custom validator, converter, or request handler flagged using the @CrtModule decorator.
    3. Import the required functionality from the libraries into the file.
    4. Save the file.
    "runtime.feature-activation.ts" file
    /* Import the required functionality from the libraries. */
    import { ProviderToken } from '@angular/core';
    import { bootstrapCrtModule } from '@creatio-devkit/common';
    import { ensureFeatureModuleRef } from '../../remote-app-context';
    import { RuntimeFeatureModule } from './runtime-feature.module';

    export async function activateRuntimeFeature(): Promise<string> {
    const moduleRef = await ensureFeatureModuleRef(RuntimeFeatureModule);
    const injector = moduleRef.injector;

    /* Bootstrap CrtModule definitions. */
    bootstrapCrtModule('some_package', RuntimeFeatureModule, {
    /* Receive the dependencies. */
    resolveDependency: (token) =>
    injector.get(token as ProviderToken<unknown>),
    });
    }
  5. Retrieve the instance of the translation service.

    1. Open the file that implements a custom validator, converter or request handler. For example, open the "some-handler.handler.ts" file that implements a custom request handler.
    2. Receive the instant translated value using the instant() method.
    3. Import the required functionality from the libraries into the file.
    4. Save the file.
    "some-handler.handler.ts" file
    /* Import the required functionality from the libraries. */
    import {
    BaseRequestHandler,
    CrtRequestHandler
    } from "@creatio-devkit/common";
    import { SomeRequest } from "../requests/some-request.request";
    import {
    SomeTranslationService
    } from "../services/some-translation-service.service";

    /* Register the SomeHandler as a Freedom UI request handler. */
    @CrtRequestHandler({
    type: 'usr.SomeHandler',
    requestType: 'usr.SomeRequest',
    })

    export class SomeHandler extends BaseRequestHandler {
    constructor(private _someTranslationService: SomeTranslationService) {
    super();
    }
    public async handle(request: SomeRequest): Promise<string> {
    /* Receive the instant translated value. */
    const localizedMessage = this._someTranslationService.instant(
    'SomeRequest.Message'
    );
    alert(localizedMessage);
    }
    }

As a result, the custom validator, converter, or request handler will display localized values based on the current Creatio UI language after you add the corresponding artifact to the source code of the Freedom UI page schema.

Localize properties of custom Freedom UI component​

  1. Use an external library or implement the custom service to localize custom Freedom UI component. We recommend using the @ngx-translate external library for this purpose. Learn more: official vendor documentation (GitHub).

    For example, SomeTranslationService is an external library or custom service to localize custom Freedom UI components.

  2. Return translated definitions of Freedom UI Designer.

    1. Open the "runtime.designer-definitions.ts" file.
    2. Add the Freedom UI Designer properties of the custom Freedom UI component to the viewElements collection returned by the loadRuntimeDesignerDefinitions() function.
    3. Use context.cultureName to request the required culture from the shared remote app context.
    4. Return the already translated values of the designer definitions, for example, caption and defaultPropertyValues.
    5. Save the file.

    The deferred remote entry API does not localize designer definitions automatically. The remote module returns the translated interface designer definitions immediately.

    "runtime.designer-definitions.ts" file
    /* Import the required functionality from the libraries. */
    import type {
    RemoteDesignerDefinitionsLoadContext,
    RemoteFeatureDesignerDefinitions,
    } from '@creatio-devkit/common';
    import { ensureRemoteAppContext } from '../../remote-app-context';

    export async function loadRuntimeDesignerDefinitions(
    context: RemoteDesignerDefinitionsLoadContext
    ): Promise<RemoteFeatureDesignerDefinitions> {

    /* The ensureRemoteAppContext() function resolves the shared remote
    application context, including translateService, using the
    "remote-app-context.ts" file. */
    const { translateService } = await ensureRemoteAppContext(context.cultureName);

    return {
    viewElements: [
    {
    type: 'usr.SomeComponent',
    toolbarConfig: {
    /* The localizable name of the component. */
    caption: translateService.instant('SomeComponent.Caption'),

    /* The icon of the component displayed in the Freedom UI Designer
    element library. Required property. */
    icon: SOME_COMPONENT_ICON,
    },
    defaultPropertyValues: {
    label: translateService.instant('SomeComponent.DefaultLabel'),
    },
    }
    ],
    };
    }

As a result, the Freedom UI Designer will display the localized properties of the custom Freedom UI component, such as captions and default values.

Upload the translations of custom Freedom UI component from static content​

  1. Find the URL to upload the translations. To do this, use the __webpack_public_path__ global variable. Learn more: Public Path (official vendor documentation).

  2. Create a dedicated localization module. For example, create the "remote-localization.module.ts" file.

  3. Add an app initializer that retrieves the Creatio UI language of the current user from the userCulture system variable and passes the value to the translation service.

    "remote-localization.module.ts" file
    /* Import the required functionality from the libraries. */
    import { HttpClient, HttpClientModule } from '@angular/common/http';
    import { APP_INITIALIZER, NgModule } from '@angular/core';
    import { SysValuesService } from '@creatio-devkit/common';
    import {
    TranslateLoader,
    TranslateModule,
    TranslateService
    } from '@ngx-translate/core';
    import { TranslateHttpLoader } from '@ngx-translate/http-loader';
    import { firstValueFrom } from 'rxjs';

    declare const __webpack_public_path__: string;

    const TRANSLATIONS_PATH = 'assets/i18n/';
    const DEFAULT_CULTURE_NAME = 'en-US';

    async function getUserCultureNameFromSysValues(): Promise<string> {
    try {
    const sysValues = await new SysValuesService().loadSysValues();
    return sysValues?.userCulture?.displayValue || DEFAULT_CULTURE_NAME;
    } catch {
    return DEFAULT_CULTURE_NAME;
    }
    }

    @NgModule({
    imports: [
    HttpClientModule,
    TranslateModule.forRoot({
    defaultLanguage: DEFAULT_CULTURE_NAME,
    loader: {
    provide: TranslateLoader,
    useFactory: (httpClient: HttpClient) => new TranslateHttpLoader(
    httpClient,
    `${__webpack_public_path__}${TRANSLATIONS_PATH}`,
    '.json'
    ),
    deps: [HttpClient],
    },
    }),
    ],
    exports: [TranslateModule],
    providers: [{
    provide: APP_INITIALIZER,
    useFactory: (translateService: TranslateService) => async () => {
    const cultureName = await getUserCultureNameFromSysValues();
    await firstValueFrom(translateService.use(cultureName));
    },
    multi: true,
    deps: [TranslateService],
    }],
    })
    export class RemoteLocalizationModule {}
  4. Import the localization module into the "remote-app.module.ts" file.

  5. If needed, import the translation module into the runtime and design feature modules.

    "remote-app.module.ts" file
    /* Import the required functionality from the libraries. */
    import { BrowserModule } from '@angular/platform-browser';
    import { DoBootstrap, NgModule } from '@angular/core';
    import {
    RemoteLocalizationModule
    } from './localization/remote-localization.module';

    @NgModule({
    imports: [BrowserModule, RemoteLocalizationModule],
    })
    export class RemoteAppModule implements DoBootstrap {
    public ngDoBootstrap(): void {
    }
    }
  6. Extend the shared remote app context. Update remote-app-context.ts to expose the TranslateService and switch the active culture when cultureName is provided.

    "remote-app-context.ts" file
    /* Import the required functionality from the libraries. */
    import { Injector, NgModuleRef, Type, createNgModule } from '@angular/core';
    import { platformBrowser } from '@angular/platform-browser';
    import { TranslateService } from '@ngx-translate/core';
    import { firstValueFrom } from 'rxjs';

    import { RemoteAppModule } from './remote-app.module';

    export interface RemoteAppContext {
    injector: Injector;
    moduleRef: NgModuleRef<RemoteAppModule>;
    translateService: TranslateService;
    }

    let remoteAppContextPromise: Promise<RemoteAppContext> | undefined;
    const featureModuleRefs = new Map<Type<unknown>, NgModuleRef<unknown>>();

    async function bootstrapRemoteApp(): Promise<RemoteAppContext> {
    const moduleRef = await platformBrowser().bootstrapModule(RemoteAppModule);
    const injector = moduleRef.injector;

    return {
    injector,
    moduleRef,
    translateService: injector.get(TranslateService),
    };
    }

    export async function ensureRemoteAppContext(
    cultureName?: string
    ): Promise<RemoteAppContext> {
    if (!remoteAppContextPromise) {
    remoteAppContextPromise = bootstrapRemoteApp().catch((error: unknown) => {
    remoteAppContextPromise = undefined;
    throw error;
    });
    }

    const context = await remoteAppContextPromise;
    if (cultureName && context.translateService.currentLang !== cultureName) {
    await firstValueFrom(context.translateService.use(cultureName));
    }

    return context;
    }
  7. Save the files.

As a result, the custom Freedom UI component will load translations from static content and display localized values based on the current Creatio UI language.


See also​

Custom Freedom UI component implemented using remote module

Custom validator implemented using remote module

Custom converter implemented using remote module

Custom request handler implemented using remote module


Resources​

Public Path (official vendor documentation)