UNPKG

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
/** 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 { }