> ## Documentation Index
> Fetch the complete documentation index at: https://docs.responsivevoice.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice Resolver Hook

## Overview

The `resolveVoice` hook lets integrating applications intercept the voice selector passed to `speak()` and transform it before the voice matching chain runs. This is useful when an external system passes voice names that don't match ResponsiveVoice's voice catalogue — instead of modifying every call site, a single hook can remap, normalize, or redirect selectors.

## Usage

```typescript
import { getResponsiveVoice } from '@responsivevoice/core';

const rv = await getResponsiveVoice({
  apiKey: 'YOUR_KEY',
  resolveVoice: (selector) => {
    if (typeof selector === 'string') {
      const aliases: Record<string, string> = {
        'Google UK English Female': 'UK English Female',
        'Microsoft Zira': 'US English Female',
      };
      return aliases[selector] ?? selector;
    }
    return selector;
  },
});

// "Google UK English Female" is silently remapped to "UK English Female"
rv.speak('Hello', 'Google UK English Female');
```

## Hook Signature

```typescript
type ResolveVoiceHook = (
  selector: VoiceSelector | undefined,
) => VoiceSelector | undefined;
```

| Parameter   | Type                         | Description                                                             |
| ----------- | ---------------------------- | ----------------------------------------------------------------------- |
| `selector`  | `VoiceSelector \| undefined` | The incoming voice selector, or `undefined` when no voice was specified |
| **Returns** | `VoiceSelector \| undefined` | A transformed selector, or `undefined` to use `defaultVoice`            |

`VoiceSelector` is a union of three forms (the post-parse output the hook receives):

| Type            | JS form               | Wire form                            | Meaning                          |
| --------------- | --------------------- | ------------------------------------ | -------------------------------- |
| `string`        | `'UK English Female'` | same                                 | Resolve by exact voice name      |
| `RegexSelector` | `/Portuguese/`        | `{ regex: 'Portuguese', flags: '' }` | First voice matching the pattern |
| `VoiceQuery`    | `{ lang: 'pt' }`      | same                                 | Structured filter (AND logic)    |

The hook receives the post-parse `VoiceSelector`, where any incoming JS `RegExp` has already been normalized to the `{ regex, flags }` literal form. The hook's return value can be either form — a returned `RegExp` is normalized the same way before reaching the resolver.

## Return Value Semantics

| Hook returns                  | What happens                                          |
| ----------------------------- | ----------------------------------------------------- |
| A `string`                    | Resolves by name (exact match, then fallback chain)   |
| A `RegExp` or `RegexSelector` | Resolves by pattern (first match)                     |
| A `VoiceQuery`                | Resolves by structured query (lang, gender, provider) |
| `undefined`                   | Falls through to the configured `defaultVoice`        |

> [!WARNING]
> The hook does **not** fire when `params.voice` is set. That escape hatch passes a raw `SpeechSynthesisVoice` object and bypasses the entire resolution chain, including the hook.

## Patterns

### Static aliasing

Map legacy or external voice names to ResponsiveVoice names:

```typescript
resolveVoice: (selector) => {
  if (typeof selector === 'string') {
    const map: Record<string, string> = {
      'old-voice-name': 'UK English Female',
      'legacy-male': 'US English Male',
    };
    return map[selector] ?? selector;
  }
  return selector;
},
```

### Locale-based routing

Redirect to a locale-appropriate voice when the exact name doesn't exist:

```typescript
resolveVoice: (selector) => {
  if (typeof selector === 'string' && !knownVoices.has(selector)) {
    const locale = extractLocale(selector);
    const gender = extractGender(selector);
    return { lang: locale, gender };
  }
  return selector;
},
```

### Debug logging

Observe what selectors are being passed without changing behavior:

```typescript
resolveVoice: (selector) => {
  console.log('[RV] resolving voice:', selector);
  return selector;
},
```

## TypeScript

Both `ResolveVoiceHook` and `VoiceSelector` are exported from `@responsivevoice/core`:

```typescript
import type { ResolveVoiceHook, VoiceSelector } from '@responsivevoice/core';

const myHook: ResolveVoiceHook = (selector) => {
  // your logic
  return selector;
};
```
