ember-intl
Version:
A internationalization toolbox for ambitious applications.
121 lines (97 loc) • 3.74 kB
text/typescript
/**
* Copyright 2015, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/
import { warn } from '@ember/debug';
import type { SafeString } from '@ember/template/-private/handlebars';
import { Formats } from '../../types';
import type IntlMessageFormat from 'intl-messageformat';
export type ValueOf<ObjectType, ValueType extends keyof ObjectType = keyof ObjectType> = ObjectType[ValueType];
// eslint-disable-next-line @typescript-eslint/ban-types
const EMPTY_OBJECT: {} = Object.create(null);
export interface FormatterConfig {
onError: (info: { kind: unknown; error: unknown }) => void;
readFormatConfig: () => Formats;
}
export interface BaseOptions {
format?: string;
}
/**
* @private
* @hide
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export default abstract class FormatterBase<KnownOptions extends {}> {
protected readonly config: FormatterConfig;
protected readonly readFormatConfig: () => Formats;
static type: keyof Formats;
protected abstract readonly createNativeFormatter: (
locales: string | string[],
options: KnownOptions
) => Intl.DateTimeFormat | Intl.RelativeTimeFormat | Intl.NumberFormat | IntlMessageFormat;
constructor(config: FormatterConfig) {
this.config = config;
// NOTE: a fn since we lazily grab the formatter from the config
// as it can change at runtime by calling intl.set('formats', {...});
this.readFormatConfig = config.readFormatConfig;
}
get options(): readonly (keyof KnownOptions)[] {
return ([] as unknown[]) as readonly (keyof KnownOptions)[];
}
/**
* Filters out all of the whitelisted formatter options
*
* @method filterKnownOptions
* @param {Object} Options object
* @return {Object} Options object containing just whitelisted options
* @private
*/
filterKnownOptions<O extends BaseOptions>(options?: O): { [K in keyof O & keyof KnownOptions]: O[K] } {
if (!options) {
return EMPTY_OBJECT as { [K in keyof O & keyof KnownOptions]: O[K] };
}
const found = {} as { [K in keyof O & keyof KnownOptions]: O[K] };
for (const key in options) {
if (this.options.includes((key as unknown) as keyof O & keyof KnownOptions)) {
found[(key as unknown) as keyof O & keyof KnownOptions] =
options[(key as unknown) as keyof O & keyof KnownOptions];
}
}
return found;
}
readOptions<O extends BaseOptions & KnownOptions>(formatOptions?: O) {
let formatterOptions = this.filterKnownOptions(formatOptions);
if (formatOptions && 'format' in formatOptions) {
const namedFormatsOptions = this.getNamedFormat((formatOptions as BaseOptions).format!);
formatterOptions = {
...namedFormatsOptions,
...formatterOptions,
};
}
return formatterOptions;
}
validateFormatterOptions(locale: string | string[], _formatterOptions: BaseOptions & KnownOptions): void {
if (!locale) {
// TODO: config.onError instead?
warn(
`[ember-intl] no locale has been set! See: https://ember-intl.github.io/ember-intl/docs/quickstart#4-configure-ember-intl`,
false,
{
id: 'ember-intl-no-locale-set',
}
);
}
}
getNamedFormat(key: string): void | ValueOf<ValueOf<Required<Formats>>> {
const formats = this.readFormatConfig();
const namedFormatsForType = formats[(this.constructor as typeof FormatterBase).type];
if (namedFormatsForType && namedFormatsForType[key]) {
return namedFormatsForType[key];
}
}
abstract format(
locale: string | string[],
value: unknown,
formatOptions?: KnownOptions & BaseOptions
): string | SafeString;
}