173 lines
5.4 KiB
TypeScript
173 lines
5.4 KiB
TypeScript
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])
|
|
}
|