Skip to content

Voice Resolver Hook

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.

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');
type ResolveVoiceHook = (
selector: VoiceSelector | undefined,
) => VoiceSelector | undefined;
ParameterTypeDescription
selectorVoiceSelector | undefinedThe incoming voice selector, or undefined when no voice was specified
ReturnsVoiceSelector | undefinedA transformed selector, or undefined to use defaultVoice

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

TypeJS formWire formMeaning
string'UK English Female'sameResolve by exact voice name
RegexSelector/Portuguese/{ regex: 'Portuguese', flags: '' }First voice matching the pattern
VoiceQuery{ lang: 'pt' }sameStructured 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.

Hook returnsWhat happens
A stringResolves by name (exact match, then fallback chain)
A RegExp or RegexSelectorResolves by pattern (first match)
A VoiceQueryResolves by structured query (lang, gender, provider)
undefinedFalls through to the configured defaultVoice

Map legacy or external voice names to ResponsiveVoice names:

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;
},

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

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

Observe what selectors are being passed without changing behavior:

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

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

import type { ResolveVoiceHook, VoiceSelector } from '@responsivevoice/core';
const myHook: ResolveVoiceHook = (selector) => {
// your logic
return selector;
};