import { t } from '@/i18n' import type { BuiltinField } from '@/lib/contentTypes' import { formatDayValue } from '@/lib/entryValue' export type EntryRule = 'required' | 'type' | 'min' | 'max' | 'options' | 'unique' function bound(field: BuiltinField, side: 'min' | 'max') { return field[side] ?? '' } function choices(field: BuiltinField) { const options = (field.options ?? []).map(String) if (options.length < 2) { return options.join('') } return t('{0} or {1}', [options.slice(0, -1).join(', '), options[options.length - 1]!]) } function requiredMessage(field: BuiltinField) { switch (field.type) { case 'date': return t('Pick a date for {0}.', [field.label]) case 'select': return t('Choose an option for {0}.', [field.label]) case 'boolean': return t('Choose yes or no for {0}.', [field.label]) case 'media': return t('Pick an image for {0}.', [field.label]) case 'relation': return t('Choose a {0}.', [field.label]) default: return t('Fill in {0}.', [field.label]) } } function typeMessage(field: BuiltinField) { switch (field.type) { case 'number': return t('{0} must be a number.', [field.label]) case 'boolean': return t('{0} must be yes or no.', [field.label]) case 'date': return t('{0} must be a date, like 2026-11-21.', [field.label]) case 'select': return t('{0} must be one of the choices.', [field.label]) case 'media': return t('{0} must be an image from the library.', [field.label]) case 'relation': return t('{0} must point to an existing entry.', [field.label]) default: return t('{0} must be text.', [field.label]) } } function minMessage(field: BuiltinField) { const limit = bound(field, 'min') if (field.type === 'number') { return t('{0} must be at least {1}.', [field.label, String(limit)]) } if (field.type === 'date') { return t("{0} can't be before {1}.", [field.label, formatDayValue(String(limit))]) } return t('{0} needs at least {1} characters.', [field.label, String(limit)]) } function maxMessage(field: BuiltinField) { const limit = bound(field, 'max') if (field.type === 'number') { return t('{0} must be at most {1}.', [field.label, String(limit)]) } if (field.type === 'date') { return t("{0} can't be after {1}.", [field.label, formatDayValue(String(limit))]) } return t('{0} can be up to {1} characters.', [field.label, String(limit)]) } function uniqueMessage(field: BuiltinField) { switch (field.type) { case 'select': return t('Another entry already uses this choice for {0}.', [field.label]) case 'date': return t('Another entry already uses this date for {0}.', [field.label]) case 'relation': return t('That {0} is already used by another entry.', [field.label]) default: return t('Another entry already uses this {0}. Enter a different one.', [field.label]) } } export function ruleMessage(rule: EntryRule, field: BuiltinField) { switch (rule) { case 'required': return requiredMessage(field) case 'type': return typeMessage(field) case 'min': return minMessage(field) case 'max': return maxMessage(field) case 'options': return t('Choose one of: {0}.', [choices(field)]) default: return uniqueMessage(field) } } export function uniqueAlert(field: BuiltinField) { switch (field.type) { case 'select': return t('Another entry already uses this choice. Change it and save again.') case 'date': return t('Another entry already uses this date. Change it and save again.') default: return t('Another entry already uses this {0}. Change it and save again.', [ field.label.toLowerCase(), ]) } } export function uniqueHelp(field: BuiltinField) { return t('Each entry needs a different {0}.', [field.label.toLowerCase()]) } export function rangeHelp(field: BuiltinField) { const min = field.min const max = field.max if (field.type === 'date') { const from = min === null || min === undefined ? '' : formatDayValue(String(min)) const until = max === null || max === undefined ? '' : formatDayValue(String(max)) if (from && until) { return t('From {0} until {1}.', [from, until]) } if (from) { return t('From {0}.', [from]) } return until ? t('Until {0}.', [until]) : '' } if (typeof min === 'number' && typeof max === 'number') { return t('Between {0} and {1}.', [String(min), String(max)]) } if (typeof min === 'number') { return t('At least {0}.', [String(min)]) } return typeof max === 'number' ? t('At most {0}.', [String(max)]) : '' } export function mediaChangedLine(label: string) { return t( "The {0} field was changed after you opened this form. Reload the form to continue. What you typed here can't be saved against the old fields.", [label], ) } export function schemaChangedLine(key: string) { const name = key.replace(/_/g, ' ') return t( "A field was removed and a new required field, {0}, was added after you opened this form. Reload the form to continue. What you typed here can't be saved against the old fields.", [name.charAt(0).toUpperCase() + name.slice(1)], ) } export function entryLoadErrorLine(path: string) { return t('GET {0} failed. Check your connection and try again.', [path]) } export function typeLoadErrorLine(key: string) { return t('GET /v1/content-type/{0} failed. Check your connection and try again.', [key]) } export function entriesLoadErrorLine(key: string) { return t('GET /v1/content-type/{0}/entry failed. Check your connection and try again.', [key]) }