lang-tag
Version:
A professional solution for managing translations in modern JavaScript/TypeScript projects, especially those using component-based architectures. `lang-tag` simplifies internationalization by allowing you to define translation keys directly within the com
382 lines (341 loc) • 21.6 kB
TypeScript
/** Applies a {@link PlaceholderExtractor} to a concrete translation string. */
export declare type ApplyPlaceholderExtractor<Extractor extends PlaceholderExtractor, S extends string> = (Extractor & {
readonly template: S;
})['placeholders'];
/** Builds the placeholder-values record for the given axes. */
declare type BuildPlaceholderValues<Keys extends string, Value, Required extends boolean, Open extends boolean> = Required extends true ? Open extends true ? Record<Keys, Value> & Record<string, unknown> : Record<Keys, Value> : Open extends true ? Partial<Record<Keys, Value>> & Record<string, unknown> : Partial<Record<Keys, Value>>;
/**
* Transforms a static translation object into an object where each
* translation string or nested object is converted into a callable function
* or a nested structure of callable functions.
*
* String leaves have their parameters inferred from template placeholders
* (`{{name}}` by default), enabling autocomplete. How those parameters behave —
* strictness level, accepted value type and placeholder syntax — is configured via
* the {@link PlaceholderParamsOptions} `Params` bundle (see `./placeholder-params`),
* which defaults to the built-in behaviour and can be overridden per tag without
* patching core.
* @template T - The structure of the input translations.
* @template PPO - The {@link PlaceholderParamsOptions} bundle for inferred parameters. Defaults to all defaults.
*/
export declare type CallableTranslations<T, PPO extends PlaceholderParamsOptions = {}> = {
[P in keyof T]: NonNullable<T[P]> extends ParameterizedTranslation ? ParameterizedTranslation : NonNullable<T[P]> extends (...args: any[]) => string ? NonNullable<T[P]> : NonNullable<T[P]> extends string ? ResolvedPlaceholderTranslation<NonNullable<T[P]>, PPO> : NonNullable<T[P]> extends Record<string, any> ? CallableTranslations<NonNullable<T[P]>, PPO> : ParameterizedTranslation;
};
/**
* Creates a callable translations object from a static translations object.
* This function initializes the transformation process.
* @template T - The type of the input translations object.
* @template Config - The LangTag translations configuration type.
* @param translations - The static translations object.
* @param config - The LangTag configuration object.
* @param strategy - The translation mapping strategy.
* @returns A callable translations object.
*/
export declare function createCallableTranslations<const T extends LangTagOptionalTranslations, Config extends LangTagTranslationsConfig>(translations: T, config: Config | undefined, strategy: TranslationMappingStrategy<Config>): CallableTranslations<T>;
/** Ready-made extractor for the `${ name }` syntax. */
export declare interface DollarBraceExtractor extends PlaceholderExtractor {
readonly placeholders: ExtractDollarBracePlaceholders<this['template']>;
}
/** Default extractor: the built-in `{{ name }}` syntax. */
export declare interface DoubleBraceExtractor extends PlaceholderExtractor {
readonly placeholders: ExtractDoubleBracePlaceholders<this['template']>;
}
/**
* Extracts the union of placeholder names from a `${ name }` translation string.
* Provided as a ready-made alternative to {@link ExtractDoubleBracePlaceholders} for
* consumers who prefer the `${...}` syntax. Wire it up through {@link DollarBraceExtractor}.
* @example ExtractDollarBracePlaceholders<'Hello ${name}'> // 'name'
*/
export declare type ExtractDollarBracePlaceholders<S extends string> = S extends `${string}${'${'}${infer Placeholder}}${infer Rest}` ? Trim<Placeholder> | ExtractDollarBracePlaceholders<Rest> : never;
/**
* Extracts the union of placeholder names from a `{{ name }}` translation string.
* This is the default syntax supported by `lang-tag`.
* Resolves to `never` when the string contains no placeholders.
* @example ExtractDoubleBracePlaceholders<'Hello {{name}} from {{ sender }}'> // 'name' | 'sender'
*/
export declare type ExtractDoubleBracePlaceholders<S extends string> = S extends `${string}{{${infer Placeholder}}}${infer Rest}` ? Trim<Placeholder> | ExtractDoubleBracePlaceholders<Rest> : never;
/**
* Represents a flexible structure for translations where all properties are required, based on an original type `T`.
* Allows for strings, `ParameterizedTranslation` functions, or other compatible functions
* at any level of the translation object. This provides flexibility in how translations
* are initially defined.
* This type is an alias for `RecursiveFlexibleTranslations<T, false>`.
* @template T - The original structure of the translations.
*/
export declare type FlexibleTranslations<T> = RecursiveFlexibleTranslations<T, false>;
/**
* Helper type to determine the flexible value of a translation property.
* If `T` is a function returning a string, it can be `T` or `string`.
* If `T` is a record, it recursively applies `RecursiveFlexibleTranslations`.
* Otherwise, it can be `ParameterizedTranslation`, `T`, or `string`.
* @template T - The type of the property value.
* @template IsPartial - A boolean indicating whether properties should be optional.
*/
declare type FlexibleValue<T, IsPartial extends boolean> = T extends (...args: any[]) => string ? T | string : T extends Record<string, any> ? RecursiveFlexibleTranslations<T, IsPartial> : ParameterizedTranslation | T | string;
/**
* Defines the structure for parameters used in interpolation.
* It's a record where keys are placeholders and values are their replacements.
*/
export declare type InterpolationParams = Record<string, any>;
/** Whether a level accepts extra, non-inferred keys. */
declare type IsOpen<Level extends PlaceholderStrictness> = Level extends `${string}-open` ? true : false;
/** Whether a level requires all placeholders to be provided. */
declare type IsRequired<Level extends PlaceholderStrictness> = Level extends `required-${string}` ? true : false;
/**
* Represents a collection of optional translations.
* Keys are strings, and values can be either strings (translations)
* or nested LangTagTranslations objects for hierarchical translations.
*/
export declare type LangTagOptionalTranslations = {
[key in string]?: string | LangTagOptionalTranslations;
};
/**
* Represents a collection of translations.
* Keys are strings, and values can be either strings (translations)
* or nested LangTagTranslations objects for hierarchical translations.
*/
export declare type LangTagTranslations = {
[key: string]: string | LangTagTranslations;
};
/**
* Configuration for LangTag translations.
* @template Namespaces - The type used for namespaces, defaults to string.
*/
export declare interface LangTagTranslationsConfig<Namespaces = string> {
/** Optional base path for translation keys. */
path?: string;
/** The namespace for the translations. */
namespace?: Namespaces;
}
/**
* Retrieves a translation function from a nested translation object using a dot-separated path.
* It is recommended to use an unprefixed path (a path that does not include the base path from the configuration)
* with this function, as it operates on the structure of the callable translations object where keys are unprefixed.
* @template T - The type of the translations object.
* @param translations The object containing translation functions.
* @param dottedPath A string path using dot notation (e.g., "user.profile.greeting"). This path should generally be unprefixed.
* @returns The translation function, or null if not found or invalid.
*/
export declare function lookupTranslation<T>(translations: CallableTranslations<T>, dottedPath: string): ParameterizedTranslation | null;
/**
* Normalizes a `FlexibleTranslations` or `PartialFlexibleTranslations` object into a `CallableTranslations` object.
* Converts plain strings into `ParameterizedTranslation` functions and ensures
* that all callable elements conform to the `ParameterizedTranslation` signature.
* Only properties present in the input `translations` object will be processed and included in the result.
* @template T - The structure of the original translations.
* @param translations - The flexible or partial flexible translations object to normalize.
* @returns A `CallableTranslations` object. The returned object will only contain callable translations for properties that were present in the input `translations` object.
*/
export declare function normalizeTranslations<T>(translations: RecursiveFlexibleTranslations<T, boolean>): CallableTranslations<T>;
/**
* Represents a function that takes optional interpolation parameters
* and returns a translated string.
* @template P - The shape of the accepted interpolation parameters.
* Defaults to {@link InterpolationParams}; can be narrowed via the generic
* (typically inferred from a translation string through `PlaceholderValues`).
*/
export declare type ParameterizedTranslation<P extends Record<string, any> = InterpolationParams> = (params?: P) => string;
/**
* Represents a deeply partial version of the structure that `FlexibleTranslations<T>` would produce, based on an original type `T`.
* All properties at all levels of nesting are made optional.
* The transformation rules for property types mirror those in `FlexibleTranslations<T>`.
* This type is an alias for `RecursiveFlexibleTranslations<T, true>`.
* @template T - The original, un-transformed, structure of the translations. This is the same kind of type argument that `FlexibleTranslations<T>` expects.
*/
export declare type PartialFlexibleTranslations<T> = RecursiveFlexibleTranslations<T, true>;
/**
* Higher-kinded interface describing a placeholder extractor.
*
* An implementation extends this interface and computes {@link PlaceholderExtractor.placeholders}
* from {@link PlaceholderExtractor.template}. The template is injected by
* {@link ApplyPlaceholderExtractor}; implementations read it via `this['template']`.
*
* @example
* // Custom `${...}` extractor in your own project:
* interface MyDollarExtractor extends PlaceholderExtractor {
* placeholders: ExtractDollarBracePlaceholders<this['template']>;
* }
* // then: CallableTranslations<T, { extractor: MyDollarExtractor }>
*/
export declare interface PlaceholderExtractor {
/** The translation string to analyse (injected by {@link ApplyPlaceholderExtractor}). */
readonly template: string;
/** The resulting union of placeholder names. */
readonly placeholders: string;
}
/**
* Bundles the options that control how placeholder parameters are inferred for a
* translations tree. Grouping them keeps {@link CallableTranslations} at two type
* arguments (`<T, PPO>`) and leaves room for future options without changing arity.
* Every field is optional and falls back to the default noted below.
*/
export declare interface PlaceholderParamsOptions {
/** Strictness level. See {@link PlaceholderStrictness}. Defaults to `'optional-open'`. */
level?: PlaceholderStrictness;
/** Value type accepted for each placeholder. Defaults to `any`. */
value?: unknown;
/** Placeholder extractor. See {@link PlaceholderExtractor}. Defaults to {@link DoubleBraceExtractor} (`{{...}}`). */
extractor?: PlaceholderExtractor;
}
/**
* Controls how strictly inferred placeholder parameters are enforced. The name
* encodes two independent axes, `<presence>-<extras>`:
*
* - **presence**: `optional` — placeholders may be omitted; `required` — all must be provided.
* - **extras**: `open` — additional, non-inferred keys are accepted; `closed` — only known placeholders.
*
* | Level | Placeholders | Extra keys | Notes |
* | ------------------- | ------------ | ---------- | ------------------------------ |
* | `'optional-open'` | optional | allowed | Default. Autocomplete, permissive. |
* | `'optional-closed'` | optional | rejected | Autocomplete, only placeholders. |
* | `'required-open'` | required | allowed | Must provide placeholders; extras ok. |
* | `'required-closed'` | required | rejected | Strictest: exactly the placeholders. |
*
* Strings without placeholders always fall back to {@link InterpolationParams}
* (permissive) regardless of level, since there is nothing to infer or enforce.
*/
export declare type PlaceholderStrictness = 'optional-open' | 'optional-closed' | 'required-open' | 'required-closed';
/**
* Picks a single placeholder-strictness level in one line, with editor autocomplete.
* The argument is constrained to {@link PlaceholderStrictness} (see it for the four
* levels), so editors suggest the levels as you type, the result stays the narrow
* literal you picked, and a typo is rejected.
* @example type PlaceholderParams = { level: PlaceholderStrictnessLevel<'optional-open'> };
*/
export declare type PlaceholderStrictnessLevel<L extends PlaceholderStrictness> = L;
/**
* Builds the callable translation function type for a single translation-string leaf,
* inferring its parameters from placeholders and applying the {@link PlaceholderStrictness} level.
* At `required-*` levels the parameters argument is mandatory; otherwise it is optional.
* @template S - The literal translation string.
* @template Level - The strictness level. Defaults to `'optional-open'`.
* @template Value - The value type accepted for each placeholder. Defaults to `any`.
* @template Extractor - The placeholder extractor. Defaults to {@link DoubleBraceExtractor} (`{{...}}`).
*/
export declare type PlaceholderTranslation<S extends string, Level extends PlaceholderStrictness = 'optional-open', Value = any, Extractor extends PlaceholderExtractor = DoubleBraceExtractor> = [ApplyPlaceholderExtractor<Extractor, S>] extends [never] ? ParameterizedTranslation : IsRequired<Level> extends true ? (params: PlaceholderValues<S, Level, Value, Extractor>) => string : (params?: PlaceholderValues<S, Level, Value, Extractor>) => string;
/**
* Derives the placeholder-values object shape from a translation string, according to
* a {@link PlaceholderStrictness} level and a {@link PlaceholderExtractor}.
* When the string has no placeholders it falls back to {@link InterpolationParams}.
* @template S - The literal translation string.
* @template Level - The strictness level. Defaults to `'optional-open'`.
* @template Value - The value type accepted for each placeholder. Defaults to `any`.
* @template Extractor - The placeholder extractor. Defaults to {@link DoubleBraceExtractor} (`{{...}}`).
*/
export declare type PlaceholderValues<S extends string, Level extends PlaceholderStrictness = 'optional-open', Value = any, Extractor extends PlaceholderExtractor = DoubleBraceExtractor> = [ApplyPlaceholderExtractor<Extractor, S>] extends [never] ? InterpolationParams : BuildPlaceholderValues<ApplyPlaceholderExtractor<Extractor, S>, Value, IsRequired<Level>, IsOpen<Level>>;
/**
* Core type for flexible translations, allowing properties to be optional recursively.
* This type serves as the foundation for `FlexibleTranslations` and `PartialFlexibleTranslations`.
* It transforms a given translation structure `T` into a flexible version where each property
* can be its original type, a string, or a `ParameterizedTranslation` function.
* If `IsPartial` is true, all properties at all levels of nesting become optional.
*
* @template T The original, un-transformed, structure of the translations.
* @template IsPartial A boolean indicating whether properties should be optional.
* If true, all properties at all levels become optional (e.g., `string | undefined`).
* If false, properties are required (e.g., `string`).
*/
export declare type RecursiveFlexibleTranslations<T, IsPartial extends boolean> = IsPartial extends true ? {
[P in keyof T]?: FlexibleValue<T[P], IsPartial>;
} : {
[P in keyof T]: FlexibleValue<T[P], IsPartial>;
};
/**
* Builds the callable translation function for a single translation-string leaf from a
* {@link PlaceholderParamsOptions} bundle, resolving the level, value type and extractor
* (each with its default). This is the option-bundle counterpart of {@link PlaceholderTranslation}.
* @template S - The literal translation string.
* @template PPO - The {@link PlaceholderParamsOptions} bundle. Defaults to all defaults.
*/
export declare type ResolvedPlaceholderTranslation<S extends string, PPO extends PlaceholderParamsOptions = {}> = PlaceholderTranslation<S, ResolveLevel<PPO>, ResolveValue<PPO>, ResolveExtractor<PPO>>;
declare type ResolveExtractor<O extends PlaceholderParamsOptions> = O extends {
extractor: infer E extends PlaceholderExtractor;
} ? E : DoubleBraceExtractor;
declare type ResolveLevel<O extends PlaceholderParamsOptions> = O extends {
level: infer L extends PlaceholderStrictness;
} ? L : 'optional-open';
declare type ResolveValue<O extends PlaceholderParamsOptions> = O extends {
value: infer V;
} ? V : any;
/**
* Defines the signature for a function that processes translation keys.
* This allows for modifying or generating new keys based on the original key and value.
* @template Config - The LangTag translations configuration type.
*/
export declare type TranslationKeyProcessor<Config extends LangTagTranslationsConfig = LangTagTranslationsConfig> = (
/** Context for processing the key. */
context: TranslationKeyProcessorContext<Config>,
/**
* Callback to add a processed key.
* @param newKey - The new key to be added to the result.
* @param originalValue - The original string value associated with the key being processed.
*/
addProcessedKey: (newKey: string, originalValue: string) => void) => void;
/**
* Context provided to a translation key processor function.
* It omits the 'params' field from `TranslationTransformContext`.
* @template Config - The LangTag translations configuration type.
*/
export declare type TranslationKeyProcessorContext<Config extends LangTagTranslationsConfig> = Omit<TranslationTransformContext<Config>, 'params'>;
/**
* Defines the strategy for mapping and transforming translations.
* @template Config - The LangTag translations configuration type.
*/
export declare interface TranslationMappingStrategy<Config extends LangTagTranslationsConfig> {
/** The function used to transform raw translation strings. */
transform: TranslationTransformer<Config>;
/** Optional function to process translation keys. */
processKey?: TranslationKeyProcessor<Config>;
}
/**
* Context provided to a translation transformer function.
* @template Config - The LangTag translations configuration type.
*/
export declare interface TranslationTransformContext<Config extends LangTagTranslationsConfig> {
/** The LangTag configuration object. */
config: Config | undefined;
/** The path of the direct parent object of the current translation key, including the base path from config. */
parentPath: string;
/** The full path to the current translation key, including the base path from config. */
path: string;
/** The path to the current translation key, relative to the root of the translations object (excluding the base path from config). */
unprefixedPath: string;
/** The current translation key. */
key: string;
/** The raw string value of the translation. */
value: string;
/** Optional interpolation parameters for the translation. */
params?: InterpolationParams;
}
/**
* Defines the signature for a function that transforms a raw translation string.
* @template Config - The LangTag translations configuration type.
* @param transformContext - The context for the transformation.
* @returns The transformed translation string.
*/
export declare type TranslationTransformer<Config extends LangTagTranslationsConfig> = (transformContext: TranslationTransformContext<Config>) => string;
/** Removes leading/trailing whitespace from a string literal type. */
export declare type Trim<S extends string> = TrimLeft<TrimRight<S>>;
declare type TrimLeft<S extends string> = S extends `${Whitespace}${infer R}` ? TrimLeft<R> : S;
declare type TrimRight<S extends string> = S extends `${infer R}${Whitespace}` ? TrimRight<R> : S;
/**
* Placeholder parameter inference.
* ================================
*
* This file owns everything related to turning a translation string with `{{ name }}`
* placeholders into a typed parameters object. It is intentionally decoupled from the
* rest of the core so that:
*
* 1. `lang-tag` supports the `{{ name }}` placeholder syntax out of the box
* (see {@link DoubleBraceExtractor}), and
* 2. consumers who need a different placeholder syntax (e.g. `${name}`) can
* plug in their own extractor via the {@link PlaceholderExtractor} higher-kinded
* interface — without patching core.
*
* Nothing here is strictly enforced at runtime: placeholder replacement happens
* in the generated tag (`processPlaceholders`), which the consumer owns. These
* types only drive editor autocomplete and compile-time parameter checking.
*/
/** Whitespace characters trimmed from placeholder names. */
declare type Whitespace = ' ' | '\n' | '\t' | '\r';
export { }