Skip to content

Localization

Localization or internationalization (i18n) is the process of designing and preparing your app to be usable in different locales around the world. Localization includes the support for different languages as well as different formats for entities like dates, time, decimals, currency, etc.

Angular supports localization as described in the i18n guide and is supported by pipes. Angular applies a compile time localization concept. It does not support the change of locales (language and formats) at runtime. Instead, for each locale it generates a new web application that supports exactly one locale. It replaces text in the HTML templates with the translations and sets a fixed LOCALE_ID which is used by the pipes. Changing locales is realized by changing the web application.

Element localization supports a single web application with many languages and formats by dynamically loading languages by using ngx-translate. We localize formats by following the angular pipes concept and reuse the locale and region definitions from Angular common locales. This is sufficient and standard conform for most applications. For example, if you want to display a date in a compact format use the date pipe like {{ date | date:'short' }}. You will get the format 'M/d/yy' (6/15/15) for English. If required, applications may extend and specialize the formats and pipes.

The Angular pipes that uses the locale information like the DatePipe, CurrencyPipe, DecimalPipe and PercentPipe are pure pipes. Pure pipes ony re-render when the inputs changes. This means they do not re-render when the LOCALE_ID changes. They load the LOCALE_ID only once at load time and caches the value.

We recommend to follow the same behavior as users changes to locales are seldom. As a consequence we need to reload the web application on locale changes.

Locales in Element

Element provides the service SiLocaleService to set the current locale like en, fr or a variant like fr-CA or en-GB as well as the available locales of the application. The service is configured by an in injected SiLocaleConfig object in the main module.

In addition you need to register and initialize the locale packages for Angular. The following shows a generic example that only loads the selected Angular locale into the applications.

Note, the /* webpackInclude: /(en|de|fr)\.js$/ */ statement that need to match the supported locales.

// On locale change, we dynamically reload the locale definition
// for angular. With this configuration, we only load the current
// locale into the client and not all application locales.
const genericLocaleInitializer = (localeId: string): Promise<any> => {
  console.log('Registering locale data for ' + localeId);
  return import(
    // The following trick only includes en, de and fr.
    // Applications need to set their locales in the regex.
    // note: the `/node_modules/` is due to a webpack bug: webpack/webpack#13865
    /* webpackInclude: /(en|de|fr)\.m?js$/ */
    `/node_modules/@angular/common/locales/${localeId}`
  ).then(module => {
    registerLocaleData(module.default);
  });
}

const localeConfig: SiLocaleConfig = {
  availableLocales: ['en', 'de', 'fr'],
  defaultLocale: 'en',
  localeInitializer: genericLocaleInitializer,
  dynamicLanguageChange: false,
  fallbackEnabled: false
};

:
:
providers: [
  { provide: LOCALE_ID, useClass: SiLocaleId, deps: [SiLocaleService] },
  { provide: SI_LOCALE_CONFIG, useValue: localeConfig }
  // , { provide: APP_INITIALIZER, useFactory: appLoadFactory, multi: true, deps: [DemoLocaleService] },
  // { provide: SiLocaleStore, useClass: DemoLocaleService }
],

In addition, fallbackEnabled enable ngx-translate to use the translation from the defaultLocale language when a translate value is missing.

Persisting locales using SiLocaleStore

Setting the SiLocaleService.locale = 'fr' changes the language and forces a reload of the browser window. The service uses the SiLocaleStore to persist the new locale. After reloading the application, the service uses the SiLocaleStore to load the changed locale. As default, a localStorage implementation is used. You can implement your own store to load and persis the user preferred locale from a setting backend that is shared across applications.

We implemented a demo store that loads a locale from a backend before the angular application initializes. The store is configured in the providers of the main module definition. In the following you find the key code snippets.

// Load the locale from a backend service before the app initializes
export function appLoadFactory(service: DemoLocaleService) {
  return () => service.loadConfig().toPromise();
}
// Configure the APP_INITIALIZER provider and configure to use the
// DemoLocaleService as a locale store.
providers: [
  { provide: LOCALE_ID, useClass: SiLocaleId, deps: [SiLocaleService] },
  { provide: SI_LOCALE_CONFIG, useValue: localeConfig },
  { provide: APP_INITIALIZER, useFactory: appLoadFactory, multi: true, deps: [DemoLocaleService] },
  { provide: SiLocaleStore, useClass: DemoLocaleService }
],

You can also combine a Store that caches the last value in the localStore and loads in parallel the current value from a backend.

Runtime locales changes using impure pipes

If you need to support locale changes without reloading, we recommend to extend the Angular pipes and set the pure property to false.

@Pipe({
  name: 'dateImpure',
  pure: false // eslint-disable-line @angular-eslint/no-pipe-impure
})
export class DateImpurePipe extends DatePipe implements PipeTransform {}

Translation in Element

Element >= v43 includes a translation abstraction layer which allows us to support multiple translation frameworks. There is no hard dependency to a specific translation library anymore. Therefore, by default, translation keys (TranslatableString) will no longer be translated.

Supported Frameworks

If a translation framework is used, Element must be configured to use this framework as well. For module-based applications, the respective module must be imported in the root module. For standalone applications, the respective provider factory must be imported in the application configuration.

Supported frameworks:

FrameworkPathModuleProvider factoryRemarks
ngx-translate@siemens/element-translate-ng/ngx-translateSiTranslateNgxTModuleprovideNgxTranslateForElement
@angular/localize@siemens/element-translate-ng/angular-localizeSiTranslateNgLocalizeModuleprovideNgLocalizeForElementThe support is experimental. Please reach out to us via an issue, if you plan to use this in a productive app.

Remember, this is only the activation of the respective layer for Element, you still need to import and configure the framework in your application as you would normally do.

If no framework is configured, Element will fall back to English.

Support for other translation frameworks

Support for @ngneat/transloco and other frameworks might be added in the future on request.

Overriding default text keys globally

Element provides the possibility to override text keys on a global level. This can be used to change the default value of text keys that are used multiple times within an application but have most likely the same value. This is usually the case for static labels like Close, Ok, ...

All keys that can be overridden can be found here.

The overriding of text keys is available for every framework except @angular/localize due to technical limitations.

Overrides are declared like this:

import { provideSiTranslatableOverrides } from '@siemens/element-ng/translate';

@NgModule({
  providers: [
    provideSiTranslatableOverrides({
      'SI-TOAST.CLOSE': 'MY-CUSTOM-CLOSE'
    })
  ]
})
export class AppModule {}

How it works

Within Element, a TranslatableString is declared using a syntax based on @angular/localize:

const value = $localize`:description@@id:default-value`;
  • description: A description for a translator. Can be omitted.
  • id: An id or key that will be passed to the translation framework.
  • default-value: The default value will be used when no translation framework is used.

Unless @angular/localize is used for translation, Element provides its own implementation of $localize. In addition, Element has its own translate pipe. Both, $localize and the translate pipe are needed for translation.

$localize resolves its input either to the id, if a translation framework is used, or to the default value, if no translation framework is used. In addition, a key can be overridden by a global provider.

The translate pipe is needed for frameworks like ngx-translate where translation happens at runtime. It resolves a TranslatableString generated by $localize using an actual translation framework.

Adding Cache busting feature to the translation *.json files

By default, the *.json files used for translation, are not hashed by Webpack during the build and may cause caching issues when newer versions of the applications are deployed. To counter this, we can either use the bundler to load translations OR we could simply provide a randomly generated string as a query parameter to the GET HTTP call, which fetches the *.json from the server.

  • Define a random hash key in environment.ts file for each environment
export const environment = {
  production: false,
  hash:`${new Date().valueOf()}`
};
  • When you initialize TranslateHttpLoader in your application, just append the below query parameter at the end:
export const createTranslateLoader = (http: HttpClient) => new TranslateHttpLoader(http, './assets/i18n/', '.json?hash=' + environment.hash)

Note that this hash key will only be appended to the translation based JSON files which will be loaded by the TranslateHttpLoader and rest of the API calls will be working as usual.

SiLocaleService API Documentation

provided in root

Attributes and Methods

NameTypeDefaultDescription
config
SiLocaleConfig...The config for the local service.
hasLocale(...)
(locale: string) => booleanTest if the given locale is part of the available locales.

Parameters
  • locale: string  The locale to be tested.
locale
stringSets a new locale to the locale service and also to the translate service.
Throws An error if the new value is not configured in the available locales or if the new locale cannot be saved, an error is thrown.
(readonly) locale$
BehaviorSubject<string>Holds the used locale definition like en, de, or en-US.
(readonly) localePackageLoaded$
ReplaySubject<void>...Emits to indicate that the localization package (e.g. @angular/common/locales/${localeId}) is loaded and registered. Emits after calling localeInitializer from the config object.

Types Documentation

Properties
The list of available locales (e.g. en, fr, de, en-GB, de-AT)
availableLocales?: string[]
The default locale to be used, when no user preference available and the browser language is not part of the available languages.
defaultLocale?: string
Default is false and defines that on setting a new locale, the locale is stored and the browser is reloaded. When changing to true, window reload is not invoked, but angular pure pipes like DatePipe will not work.
dynamicLanguageChange?: boolean
Set to true to also enable the default language on ngx-translate. When true, ngx-translate will use a translate value from the default language when a required value is not available in the current language. But note, this will also enforce to load the default language translation file into the application, even if a different locale is active. In other words, the application start time increases.
fallbackEnabled?: boolean
The localeInitializer function is invoked on every locale change. Make sure to invoke registerLocaleData with the locale to enable the Angular localization.
localeInitializer?: (localeId: string) => Promise<any>

Translatable keys in Element

Properties
SI_ALERT_DIALOG.OK?: string
SI_APPLICATION_HEADER.LAUNCHPAD?: string
SI_APPLICATION_HEADER.TOGGLE_ACTIONS?: string
SI_APPLICATION_HEADER.TOGGLE_NAVIGATION?: string
SI_BREADCRUMB?: string
SI_COLUMN_SELECTION_DIALOG.CANCEL?: string
SI_COLUMN_SELECTION_DIALOG.HIDDEN?: string
SI_COLUMN_SELECTION_DIALOG.ITEM_MOVED?: string
SI_COLUMN_SELECTION_DIALOG.ITEM_NOT_MOVED?: string
SI_COLUMN_SELECTION_DIALOG.LIST_ARIA_LABEL?: string
SI_COLUMN_SELECTION_DIALOG.RENAME_INPUT_ARIA_LABEL?: string
SI_COLUMN_SELECTION_DIALOG.RESTORE_TO_DEFAULT?: string
SI_COLUMN_SELECTION_DIALOG.SUBMIT?: string
SI_COLUMN_SELECTION_DIALOG.VISIBLE?: string
SI_CONFIRMATION_DIALOG.NO?: string
SI_CONFIRMATION_DIALOG.YES?: string
SI_CONTENT_ACTION_BAR.TOGGLE?: string
SI_DASHBOARD.EXPAND?: string
SI_DASHBOARD.RESTORE?: string
SI_DATE_RANGE_FILTER.ADVANCED?: string
SI_DATE_RANGE_FILTER.AFTER?: string
SI_DATE_RANGE_FILTER.APPLY?: string
SI_DATE_RANGE_FILTER.BEFORE?: string
SI_DATE_RANGE_FILTER.DATE?: string
SI_DATE_RANGE_FILTER.DATE_PLACEHOLDER?: string
SI_DATE_RANGE_FILTER.DAYS?: string
SI_DATE_RANGE_FILTER.FROM?: string
SI_DATE_RANGE_FILTER.HOURS?: string
SI_DATE_RANGE_FILTER.MINUTES?: string
SI_DATE_RANGE_FILTER.MONTHS?: string
SI_DATE_RANGE_FILTER.NOW?: string
SI_DATE_RANGE_FILTER.PRESETS?: string
SI_DATE_RANGE_FILTER.PREVIEW?: string
SI_DATE_RANGE_FILTER.RANGE?: string
SI_DATE_RANGE_FILTER.REF_POINT?: string
SI_DATE_RANGE_FILTER.SEARCH?: string
SI_DATE_RANGE_FILTER.TO?: string
SI_DATE_RANGE_FILTER.TODAY?: string
SI_DATE_RANGE_FILTER.UNIT?: string
SI_DATE_RANGE_FILTER.VALUE?: string
SI_DATE_RANGE_FILTER.WEEKS?: string
SI_DATE_RANGE_FILTER.WITHIN?: string
SI_DATE_RANGE_FILTER.YEARS?: string
SI_DATEPICKER.CALENDAR_TOGGLE_BUTTON?: string
SI_DATEPICKER.CALENDAR_WEEK_LABEL?: string
SI_DATEPICKER.DISABLED_TIME_TEXT?: string
SI_DATEPICKER.ENABLED_TIME_TEXT?: string
SI_DATEPICKER.END_DATE_PLACEHOLDER?: string
SI_DATEPICKER.END_TIME_LABEL?: string
SI_DATEPICKER.HOURS?: string
SI_DATEPICKER.MILLISECONDS?: string
SI_DATEPICKER.MINUTES?: string
SI_DATEPICKER.NEXT?: string
SI_DATEPICKER.PERIOD?: string
SI_DATEPICKER.PREVIOUS?: string
SI_DATEPICKER.SECONDS?: string
SI_DATEPICKER.START_DATE_PLACEHOLDER?: string
SI_DATEPICKER.START_TIME_LABEL?: string
SI_DELETE_CONFIRMATION_DIALOG.CANCEL_BTN?: string
SI_DELETE_CONFIRMATION_DIALOG.DELETE_BTN?: string
SI_DELETE_CONFIRMATION_DIALOG.MESSAGE?: string
SI_EDIT_DISCARD_DIALOG.CANCEL_BTN?: string
SI_EDIT_DISCARD_DIALOG.DISABLE_SAVE_DISCARD_BTN?: string
SI_EDIT_DISCARD_DIALOG.DISABLE_SAVE_MESSAGE?: string
SI_EDIT_DISCARD_DIALOG.DISCARD_BTN?: string
SI_EDIT_DISCARD_DIALOG.MESSAGE?: string
SI_EDIT_DISCARD_DIALOG.SAVE_BTN?: string
SI_ELECTRON_TITLEBAR.BACK?: string
SI_ELECTRON_TITLEBAR.FORWARD?: string
SI_ELECTRON_TITLEBAR.MENU?: string
SI_FILE_UPLOADER.ACCEPTED_FILE_TYPES?: string
SI_FILE_UPLOADER.CANCEL?: string
SI_FILE_UPLOADER.CLEAR?: string
SI_FILE_UPLOADER.DROP?: string
SI_FILE_UPLOADER.ERROR_FILE_SIZE_EXCEEDED?: string
SI_FILE_UPLOADER.ERROR_FILE_TYPE?: string
SI_FILE_UPLOADER.FILE_SELECT?: string
SI_FILE_UPLOADER.MAX_FILE_REACHED?: string
SI_FILE_UPLOADER.MAX_SIZE?: string
SI_FILE_UPLOADER.REMOVE?: string
SI_FILE_UPLOADER.UPLOAD?: string
SI_FILE_UPLOADER.UPLOAD_COMPLETED?: string
SI_FILE_UPLOADER.UPLOAD_FAILED?: string
SI_FILE_UPLOADER.UPLOADING?: string
SI_FILTER_BAR.COLLAPSED_FILTERS_DESCRIPTION?: string
SI_FILTER_BAR.NO_FILTERS?: string
SI_FILTER_BAR.RESET_FILTERS?: string
SI_FILTERED_SEARCH.CLEAR?: string
SI_FILTERED_SEARCH.ITEMS?: string
SI_FILTERED_SEARCH.NO_MATCHING_CRITERIA?: string
SI_FILTERED_SEARCH.SEARCH?: string
SI_FILTERED_SEARCH.SUBMIT?: string
SI_FILTERED_SEARCH.SUBMIT_BUTTON?: string
SI_FORM_CONTAINER.ERROR.DATE_FORMAT?: string
SI_FORM_CONTAINER.ERROR.EMAIL?: string
SI_FORM_CONTAINER.ERROR.IPV4?: string
SI_FORM_CONTAINER.ERROR.IPV6?: string
SI_FORM_CONTAINER.ERROR.MAX?: string
SI_FORM_CONTAINER.ERROR.MAX_DATE?: string
SI_FORM_CONTAINER.ERROR.MAX_LENGTH?: string
SI_FORM_CONTAINER.ERROR.MIN?: string
SI_FORM_CONTAINER.ERROR.MIN_DATE?: string
SI_FORM_CONTAINER.ERROR.MIN_LENGTH?: string
SI_FORM_CONTAINER.ERROR.NUMBER_FORMAT?: string
SI_FORM_CONTAINER.ERROR.PATTERN?: string
SI_FORM_CONTAINER.ERROR.REQUIRED?: string
SI_FORM_CONTAINER.ERROR.REQUIRED_TRUE?: string
SI_ICON_STATUS.CAUTION?: string
SI_ICON_STATUS.CRITICAL?: string
SI_ICON_STATUS.DANGER?: string
SI_ICON_STATUS.INFO?: string
SI_ICON_STATUS.PENDING?: string
SI_ICON_STATUS.PROGRESS?: string
SI_ICON_STATUS.SUCCESS?: string
SI_ICON_STATUS.UNKNOWN?: string
SI_ICON_STATUS.WARNING?: string
SI_LANGUAGE_SWITCHER.LABEL?: string
SI_LAUNCHPAD.CLOSE?: string
SI_LAUNCHPAD.DEFAULT_CATEGORY_TITLE?: string
SI_LAUNCHPAD.FAVORITE_APPS?: string
SI_LAUNCHPAD.SHOW_LESS?: string
SI_LAUNCHPAD.SHOW_MORE?: string
SI_LAUNCHPAD.SUB_TITLE?: string
SI_LAUNCHPAD.SUBTITLE?: string
SI_LAUNCHPAD.TITLE?: string
SI_LIST_DETAILS.BACK?: string
SI_LIST_WIDGET.SEARCH_PLACEHOLDER?: string
SI_LIST_WIDGET.SORT_ASCENDING?: string
SI_LIST_WIDGET.SORT_DESCENDING?: string
SI_LOADING_SPINNER.LABEL?: string
SI_MAIN_DETAIL_CONTAINER.BACK?: string
SI_NAVBAR_VERTICAL.COLLAPSE?: string
SI_NAVBAR_VERTICAL.EXPAND?: string
SI_NAVBAR_VERTICAL.SEARCH_PLACEHOLDER?: string
SI_NAVBAR_VERTICAL.SKIP_LINK.MAIN_LABEL?: string
SI_NAVBAR_VERTICAL.SKIP_LINK.NAVIGATION_LABEL?: string
SI_NAVBAR.OPEN_LAUNCHPAD?: string
SI_NAVBAR.TOGGLE_NAVIGATION?: string
SI_PAGINATION.BACK?: string
SI_PAGINATION.FORWARD?: string
SI_PAGINATION.NAV_LABEL?: string
SI_PASSWORD_TOGGLE.HIDE?: string
SI_PASSWORD_TOGGLE.SHOW?: string
SI_PHONE_NUMBER_INPUT.PHONE_NUMBER_INPUT_LABEL?: string
SI_PHONE_NUMBER_INPUT.SEARCH_NO-RESULTS_FOUND?: string
SI_PHONE_NUMBER_INPUT.SEARCH_PLACEHOLDER?: string
SI_PHONE_NUMBER_INPUT.SELECT_COUNTRY?: string
SI_PHOTO_UPLOAD.APPLY_PHOTO?: string
SI_PHOTO_UPLOAD.CANCEL?: string
SI_PHOTO_UPLOAD.CHANGE_PHOTO?: string
SI_PHOTO_UPLOAD.CROPPER_FRAME_LABEL?: string
SI_PHOTO_UPLOAD.ERROR_FILE_SIZE_EXCEEDED?: string
SI_PHOTO_UPLOAD.ERROR_FILE_TYPE?: string
SI_PHOTO_UPLOAD.MODAL_TITLE?: string
SI_PHOTO_UPLOAD.REMOVE?: string
SI_PHOTO_UPLOAD.UPLOAD_PHOTO?: string
SI_PILLS_INPUT.INPUT_ELEMENT_ARIA_LABEL?: string
SI_PROGRESSBAR.LABEL?: string
SI_SELECT.NO-RESULTS-FOUND?: string
SI_SELECT.SEARCH-PLACEHOLDER?: string
SI_SIDE_PANEL.CLOSE?: string
SI_SIDE_PANEL.SEARCH_PLACEHOLDER?: string
SI_SIDE_PANEL.TOGGLE?: string
SI_SKIP_LINKS.JUMP_TO?: string
SI_SLIDER.DECREMENT?: string
SI_SLIDER.INCREMENT?: string
SI_SLIDER.LABEL?: string
SI_SORT_BAR.TITLE?: string
SI_STATUS_BAR.ALL_OK?: string
SI_STATUS_BAR.COLLAPSE?: string
SI_STATUS_BAR.EXPAND?: string
SI_STATUS_BAR.MUTE?: string
SI_THRESHOLD.ADD?: string
SI_THRESHOLD.DELETE?: string
SI_THRESHOLD.INPUT_LABEL?: string
SI_THRESHOLD.STATUS?: string
SI_TOAST.CLOSE?: string
SI_TOUR.BACK?: string
SI_TOUR.CLOSE?: string
SI_TOUR.DONE?: string
SI_TOUR.NEXT?: string
SI_TOUR.PROGRESS?: string
SI_TOUR.SKIP?: string
SI_TREE_VIEW.COLLAPSE_ALL?: string
SI_TREE_VIEW.EXPAND_ALL?: string
SI_TYPEAHEAD.AUTOCOMPLETE_LIST_LABEL?: string
SI_WIZARD.BACK?: string
SI_WIZARD.CANCEL?: string
SI_WIZARD.COMPLETED?: string
SI_WIZARD.NEXT?: string
SI_WIZARD.SAVE?: string

Except where otherwise noted, content on this site is licensed under MIT License.