Internationalization (i18n)
@sentinel-password/core has pluggable message templates. Validators emit a stable MessageCode (e.g. 'length.tooShort') plus a params object of interpolation values, then render the user-facing string through a fallback chain you control:
options.formatMessage(code, params, defaultMessage)if provided (highest precedence — integrates withreact-intl,i18next, FormatJS/ICU, etc.)options.messages?.[code]template if provided ({placeholder}tokens are substituted)- Built-in English default
If neither option is set, you get the legacy English strings — backwards-compatible with existing apps using the lookup-table workaround below.
The codes
MessageCode | Params | Default English template |
|---|---|---|
length.tooShort | { minLength } | Password must be at least {minLength} characters |
length.tooLong | { maxLength } | Password must be at most {maxLength} characters |
characterTypes.missing | { missing, missingTypes } | Password must contain at least one {missing} |
repetition.tooMany | { maxRepeatedChars } | Password contains too many repeated characters (max {maxRepeatedChars}) |
sequential.found | {} | Password contains sequential characters (e.g., abc, 123) |
keyboardPattern.found | {} | Password contains common keyboard patterns |
commonPassword.found | {} | Password is too common. Please choose a more unique password. |
personalInfo.found | {} | Password contains personal information |
Codes are part of the public API and stable across patch and minor releases. The default English strings remain stable too, so the lookup-table pattern documented below keeps working.
For characterTypes.missing, two params are provided:
params.missing— joined English ('uppercase letter, digit') used by the default template.params.missingTypes— raw comma-separated tokens ('uppercase,digit') — split it to translate type names individually.
Pattern 1 — messages: template map
Best for static locales with a small set of fixed messages. Pass partial overrides keyed by MessageCode; missing codes fall back to English.
import { validatePassword, type MessageCode } from '@sentinel-password/core'
const es: Partial<Record<MessageCode, string>> = {
'length.tooShort': 'La contraseña debe tener al menos {minLength} caracteres',
'length.tooLong': 'La contraseña no debe superar los {maxLength} caracteres',
'characterTypes.missing': 'La contraseña debe contener al menos un {missing}',
'repetition.tooMany': 'Demasiados caracteres repetidos (máx. {maxRepeatedChars})',
'sequential.found': 'La contraseña contiene caracteres secuenciales (ej. abc, 123)',
'keyboardPattern.found': 'La contraseña contiene patrones de teclado',
'commonPassword.found': 'La contraseña es demasiado común',
'personalInfo.found': 'La contraseña contiene información personal',
}
const result = validatePassword(password, {
minLength: 12,
messages: es,
})
// result.feedback.warning → "La contraseña debe tener al menos 12 caracteres"
// result.feedback.suggestions → fully localized stringsTemplate strings may include {placeholder} tokens — they're substituted with the matching params value. Unknown placeholders are left intact so missing data surfaces clearly during development.
Pattern 2 — formatMessage: callback
Best for integrating with an i18n library you already use (react-intl, i18next, lingui, FormatJS), or when you need pluralization, gender, or ICU-style formatting.
import { useIntl } from 'react-intl'
import { validatePassword } from '@sentinel-password/core'
function useValidate() {
const intl = useIntl()
return (password: string) =>
validatePassword(password, {
minLength: 12,
requireUppercase: true,
formatMessage: (code, params, defaultMessage) => {
// Reuse react-intl's message catalog. Falls back to English if a
// translation is missing for this code in the active locale.
return intl.formatMessage(
{ id: `sentinelPassword.${code}`, defaultMessage },
params
)
},
})
}Re-localizing missing character types
For characterTypes.missing, use params.missingTypes to translate each type name individually instead of working with the joined English string:
const typeNames: Record<string, Record<string, string>> = {
es: { uppercase: 'mayúscula', lowercase: 'minúscula', digit: 'dígito', symbol: 'símbolo' },
}
validatePassword(password, {
formatMessage: (code, params, defaultMessage) => {
if (code === 'characterTypes.missing') {
const tokens = (params.missingTypes as string).split(',')
const localized = tokens.map((t) => typeNames.es[t] ?? t).join(', ')
return `La contraseña debe contener al menos un ${localized}`
}
return defaultMessage
},
})Pluralization
The built-in template engine is a literal {placeholder} substitution — it does not handle plurals or gender. For locales that need them (Russian, Polish, Arabic, etc.), use formatMessage with an ICU-capable library:
import { IntlMessageFormat } from 'intl-messageformat'
validatePassword(password, {
formatMessage: (code, params, defaultMessage) => {
const messageById: Record<string, string> = {
'length.tooShort':
'{minLength, plural, one {Минимум # символ} few {Минимум # символа} other {Минимум # символов}}',
}
const tmpl = messageById[code]
if (!tmpl) return defaultMessage
return new IntlMessageFormat(tmpl, 'ru').format(params) as string
},
})Strength labels
result.strength is a typed enum ('very-weak' | 'weak' | 'medium' | 'strong' | 'very-strong'), not a user-facing message. Translate it in your UI layer — the library doesn't and shouldn't:
const strengthLabels = {
'very-weak': 'Muy débil',
weak: 'Débil',
medium: 'Media',
strong: 'Fuerte',
'very-strong': 'Muy fuerte',
} as const
<span>{strengthLabels[result.strength]}</span>React layer
@sentinel-password/react's usePasswordValidator hook accepts the same options (UsePasswordValidatorOptions extends ValidatorOptions) — messages and formatMessage thread through to core unchanged:
const { result } = usePasswordValidator({
minLength: 12,
messages: es,
// ...or formatMessage
})PasswordInput from @sentinel-password/react-components accepts a nested validatorOptions prop (so it doesn't collide with the HTML input's minLength / maxLength attributes) plus four optional props for the visibility toggle's English text:
import { PasswordInput } from '@sentinel-password/react-components'
<PasswordInput
label="Contraseña"
validatorOptions={{
minLength: 12,
messages: { 'length.tooShort': 'Mínimo {minLength} caracteres' },
// or formatMessage: (code, params, defaultMessage) => intl.formatMessage(...)
}}
toggleShowText="Mostrar"
toggleHideText="Ocultar"
toggleShowLabel="Mostrar contraseña"
toggleHideLabel="Ocultar contraseña"
/>The aria-live region renders the resolved string, so screen readers announce the localized message on each validation pass.
Legacy: lookup-table workaround
The previous workaround — mapping English message strings to translations — still works because default English templates are stable across patch and minor releases. Prefer messages / formatMessage for new code, but you don't need to migrate existing apps:
const lookup: Record<string, string> = {
'Password must be at least 8 characters': 'La contraseña debe tener al menos 8 caracteres',
// ...
}
const result = validatePassword(password)
const localized = result.feedback.suggestions.map((m) => lookup[m] ?? m)See Also
- Configuration — full options reference
- Validators — the canonical list of validators
- Core API —
ValidatorOptions,MessageCode,MessageFormatter,DEFAULT_TEMPLATES