UNPKG

carbon-components-angular

Version:
443 lines (436 loc) 15.4 kB
import * as i0 from '@angular/core'; import { Injectable, Pipe, Optional, SkipSelf, NgModule } from '@angular/core'; import { BehaviorSubject, iif, isObservable } from 'rxjs'; import { map } from 'rxjs/operators'; import { merge } from 'carbon-components-angular/utils'; var EN = { "BREADCRUMB": { "LABEL": "Breadcrumb" }, "CODE_SNIPPET": { "CODE_SNIPPET_TEXT": "Code Snippet Text", "SHOW_MORE": "Show more", "SHOW_LESS": "Show less", "SHOW_MORE_ICON": "Show more icon", "COPY_CODE": "Copy code", "COPIED": "Copied!" }, "COMBOBOX": { "PLACEHOLDER": "Filter...", "CLEAR_SELECTIONS": "Clear all selected items", "CLEAR_SELECTED": "Clear selected item", "A11Y": { "OPEN_MENU": "Open menu", "CLOSE_MENU": "Close menu", "CLEAR_SELECTIONS": "Clear all selected items", "CLEAR_SELECTED": "Clear Selection" } }, "DROPDOWN": { "OPEN": "Open menu", "SELECTED": "Selected", "CLEAR": "Clear all selected items", "FILTER": { "SELECTED_ONLY": "Show selected only", "SEARCH": "Search", "NO_RESULTS": "No search results", "RESET_SEARCH": "Reset search" } }, "DROPDOWN_LIST": { "LABEL": "Listbox" }, "FILE_UPLOADER": { "CHECKMARK": "Checkmark", "OPEN": "Add file", "REMOVE_BUTTON": "Close button" }, "LOADING": { "TITLE": "Loading" }, "MODAL": { "CLOSE": "Close modal" }, "NOTIFICATION": { "CLOSE_BUTTON": "Close alert notification" }, "NUMBER": { "INCREMENT": "Increment value", "DECREMENT": "Decrement value" }, "OVERFLOW_MENU": { "OVERFLOW": "Overflow" }, "SEARCH": { "LABEL": "Search", "PLACEHOLDER": "Search", "CLEAR_BUTTON": "Clear search input" }, "PAGINATION": { "ITEMS_PER_PAGE": "Items per page:", "OPEN_LIST_OF_OPTIONS": "Open list of options", "BACKWARD": "Backward", "FORWARD": "Forward", "TOTAL_ITEMS_UNKNOWN": "{{start}}-{{end}} items", "TOTAL_ITEMS": "{{start}}-{{end}} of {{total}} items", "TOTAL_ITEM": "{{start}}-{{end}} of {{total}} item", "PAGE": "page", "OF_LAST_PAGES": "of {{last}} pages", "OF_LAST_PAGE": "of {{last}} page", "NEXT": "Next", "PREVIOUS": "Previous", "SELECT_ARIA": "Select page number" }, "PROGRESS_INDICATOR": { "CURRENT": "Current", "INCOMPLETE": "Incomplete", "COMPLETE": "Complete", "INVALID": "Invalid" }, "TABLE": { "FILTER": "Filter", "END_OF_DATA": "You've reached the end of your content", "SCROLL_TOP": "Scroll to top", "CHECKBOX_HEADER": "Select all rows", "CHECKBOX_ROW": "Select {{value}}", "EXPAND_BUTTON": "Expand row", "SORT_DESCENDING": "Sort rows by this header in descending order", "SORT_ASCENDING": "Sort rows by this header in ascending order", "ROW": "row" }, "TABLE_TOOLBAR": { "ACTION_BAR": "Table action bar", "BATCH_TEXT": "", "BATCH_TEXT_SINGLE": "1 item selected", "BATCH_TEXT_MULTIPLE": "{{count}} items selected", "CANCEL": "Cancel" }, "TABS": { "BUTTON_ARIA_LEFT": "Go to the previous tab", "BUTTON_ARIA_RIGHT": "Go to the next tab", "HEADER_ARIA_LABEL": "List of tabs" }, "TILES": { "TILE": "tile", "EXPAND": "Expand", "COLLAPSE": "Collapse" }, "TOGGLE": { "OFF": "Off", "ON": "On" }, "UI_SHELL": { "SKIP_TO": "Skip to content", "HEADER": { "OPEN_MENU": "Open menu", "CLOSE_MENU": "Close menu" }, "SIDE_NAV": { "TOGGLE_OPEN": "Open", "TOGGLE_CLOSE": "Close" } } }; /** * Takes the `Observable` returned from `i18n.get` and an object of variables to replace. * * The keys specify the variable name in the string. * * Example: * ```typescript * service.set({ "TEST": "{{foo}} {{bar}}" }); * * service.replace(service.get("TEST"), { foo: "test", bar: "asdf" }) * ``` * * Produces: `"test asdf"` * * @param subject the translation to replace variables on * @param variables object of variables to replace */ const replace = (subject, variables) => subject.pipe(map(str => { const keys = Object.keys(variables); for (const key of keys) { const value = variables[key]; str = str.replace(new RegExp(`{{\\s*${key}\\s*}}`, "g"), value); } return str; })); /** * Represents an "overridable" translation value. * * Largely an internal usecase. There are situations where we want an `Observable` that * can emit events from a centralized source **OR** an `Observable` that will emit events * from a component local source. The key example being on/off text in a `Toggle` - In some cases * we want the `Toggle` to use `I18n`s global translations, but in others we'd prefer to use a local * override. We don't ever need to return to a non-overridden state, but we do need the ability to * switch _to_ an overridden sate. */ class Overridable { constructor(path, i18n) { this.path = path; this.i18n = i18n; /** * Our base non-overridden translation. */ this.baseTranslation = this.i18n.get(this.path); /** * A boolean to flip between overridden and non-overridden states. */ this.isOverridden = false; /** * ensure `$override` is initialized with the correct default value * in some cases `_value` can get changed for an `Observable` before `$override` is created */ const value = this.i18n.getValueFromPath(this.path); this.$override = new BehaviorSubject(value); this._value = value; } /** * The raw value of the translation. Defaults to the string value, but will return the value passed to `override` * * @readonly */ get value() { return this._value; } set value(v) { this.override(v); } /** * The translation subject. Returns either a stream of overridden values, or our base translation values. * * @readonly */ get subject() { /** * since inputs are bound on template instantiation (and thusly will always have _some_ value) * We can use a simple boolean and the `iif` function to determine which subject to return on subscription */ return iif(() => this.isOverridden, this.$override, this.baseTranslation); } /** * Takes a string or an `Observable` that emits strings. * Overrides the value provided by the `I18n` service. */ override(value) { this.isOverridden = true; // To ensure that there are not multiple subscriptions created for the same observable, we // unsubscribe if a subscription already exists for an observable before creating a new one. if (this.subscription) { this.subscription.unsubscribe(); this.subscription = null; } this._value = value; if (isObservable(value)) { this.subscription = value.subscribe(v => { this.$override.next(v); }); } else { this.$override.next(value); } } } /** * The I18n service is a minimal internal singleton service used to supply our components with translated strings. * * All the components that support I18n also support directly passed strings. * Usage of I18n is optional, and it is not recommended for application use (libraries like ngx-translate * are a better choice) * */ class I18n { constructor() { this.translationStrings = EN; this.translations = new Map(); this.locale = new BehaviorSubject("en"); } /** * Sets the locale and optionally the translation strings. Locale is used by components that * are already locale aware (datepicker for example) while the translation strings are used * for components that are not. * * Locales set here will override locales/languages set in components * @param language an ISO 639-1 language code - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes * @param strings an object of strings, optional */ setLocale(language, strings) { this.locale.next(language); if (strings) { this.set(strings); } } /** * Returns the current locale */ getLocale() { return this.locale.value; } /** * Returns an observable that resolves to the current locale, and will update when changed */ getLocaleObservable() { return this.locale.asObservable(); } /** * Set/update the translations from an object. Also notifies all participating components of the update. * * @param strings an object of strings, should follow the same format as src/i18n/en.json */ set(strings) { this.translationStrings = merge({}, EN, strings); // iterate over all our tracked translations and update each observable const translations = Array.from(this.translations); for (const [path, subject] of translations) { subject.next(this.getValueFromPath(path)); } } /** * When a path is specified returns an observable that will resolve to the translation string value. * * Returns the full translations object if path is not specified. * * @param path optional, looks like `"NOTIFICATION.CLOSE_BUTTON"` */ get(path) { if (!path) { return this.translationStrings; } return this.getSubject(path); } /** * Returns all descendents of some path fragment as an object. * * @param partialPath a path fragment, for example `"NOTIFICATION"` */ getMultiple(partialPath) { const values = this.getValueFromPath(partialPath); const subjects = {}; for (const key of Object.keys(values)) { if (values[key] === Object(values[key])) { subjects[key] = this.getMultiple(`${partialPath}.${key}`); } else { subjects[key] = this.getSubject(`${partialPath}.${key}`); } } return subjects; } /** * Returns an instance of `Overridable` that can be used to optionally override the value provided by `I18n` * @param path looks like `"NOTIFICATION.CLOSE_BUTTON"` */ getOverridable(path) { return new Overridable(path, this); } /** * Takes the `Observable` returned from `i18n.get` and an object of variables to replace. * * The keys specify the variable name in the string. * * Example: * ``` * service.set({ "TEST": "{{foo}} {{bar}}" }); * * service.replace(service.get("TEST"), { foo: "test", bar: "asdf" }) * ``` * * Produces: `"test asdf"` * * @param subject the translation to replace variables on * @param variables object of variables to replace */ replace(subject, variables) { return replace(subject, variables); } /** * Trys to resolve a value from the provided path. * * @param path looks like `"NOTIFICATION.CLOSE_BUTTON"` */ getValueFromPath(path) { let value = this.translationStrings; for (const segment of path.split(".")) { if (value[segment] !== undefined && value[segment] !== null) { value = value[segment]; } else { throw new Error(`no key ${segment} at ${path}`); } } return value; } /** * Helper method that returns an observable from the internal cache based on the path * * @param path looks like `"NOTIFICATION.CLOSE_BUTTON"` */ getSubject(path) { try { // we run this here to validate the path exists before adding it to the translation map const value = this.getValueFromPath(path); if (this.translations.has(path)) { return this.translations.get(path); } const translation = new BehaviorSubject(value); this.translations.set(path, translation); return translation; } catch (error) { console.error(error); } } } I18n.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18n, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); I18n.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18n }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18n, decorators: [{ type: Injectable }] }); class ReplacePipe { transform(value, variables) { return replace(value, variables); } } ReplacePipe.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ReplacePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); ReplacePipe.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: ReplacePipe, name: "i18nReplace" }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: ReplacePipe, decorators: [{ type: Pipe, args: [{ name: "i18nReplace" }] }] }); // either provides a new instance of I18n, or returns the parent function I18N_SERVICE_PROVIDER_FACTORY(parentService) { return parentService || new I18n(); } // I18n should provide a single instance of itself to ensure that translations are consistent through the app const I18N_SERVICE_PROVIDER = { provide: I18n, deps: [[new Optional(), new SkipSelf(), I18n]], useFactory: I18N_SERVICE_PROVIDER_FACTORY }; class I18nModule { } I18nModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18nModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); I18nModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.3.0", ngImport: i0, type: I18nModule, declarations: [ReplacePipe], exports: [ReplacePipe] }); I18nModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18nModule, providers: [ I18n, I18N_SERVICE_PROVIDER ] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.3.0", ngImport: i0, type: I18nModule, decorators: [{ type: NgModule, args: [{ declarations: [ReplacePipe], exports: [ReplacePipe], providers: [ I18n, I18N_SERVICE_PROVIDER ] }] }] }); /** * Generated bundle index. Do not edit. */ export { I18N_SERVICE_PROVIDER, I18N_SERVICE_PROVIDER_FACTORY, I18n, I18nModule, Overridable, ReplacePipe, replace }; //# sourceMappingURL=carbon-components-angular-i18n.mjs.map