import type { CollectionConfig, SelectFieldManyValidation, TextFieldSingleValidation, } from 'payload' import { select } from 'payload/shared' import { DEFAULT_LOCALE, LOCALES } from '@/i18n/locales' import { anyRole, managers, owners } from '../access/roles' import { localeOptions } from '../fields/locales' import { groups, label, message } from '../i18n' /** * Языки сайта — языки его контента. Домен показывает любую их часть. * Английский обязателен: на него откатывается непереведённый контент. */ const validateLocales: SelectFieldManyValidation = async (value, args) => { const base = select(value, args) if (base !== true) return base const locales = value ?? [] if (!locales.includes(DEFAULT_LOCALE)) { return message(args.req, { en: 'English is required: untranslated content falls back to it', ru: 'Английский обязателен: на него откатывается непереведённый контент', }) } if (args.id) { const { docs } = await args.req.payload.find({ collection: 'domains', where: { site: { equals: args.id } }, depth: 0, pagination: false, overrideAccess: true, req: args.req, }) const used = new Set(docs.flatMap((domain) => domain.locales ?? [])) const missing = [...used].filter((locale) => !locales.includes(locale)) if (missing.length > 0) { return message(args.req, { en: `These languages are enabled on the site's domains, remove them there first: ${missing.join(', ')}`, ru: `Эти языки включены на доменах сайта, сначала уберите их там: ${missing.join(', ')}`, }) } } return true } const validateOrderPrefix: TextFieldSingleValidation = (value, { req }) => typeof value === 'string' && /^[A-Z]{2,4}$/.test(value) ? true : message(req, { en: '2–4 capital Latin letters, e.g. VX', ru: '2–4 заглавные латинские буквы, например VX', }) export const Sites: CollectionConfig = { slug: 'sites', labels: { singular: label('Site', 'Сайт'), plural: label('Sites', 'Сайты'), }, admin: { group: groups.settings, useAsTitle: 'name', defaultColumns: ['name', 'code', 'orderPrefix', 'locales'], }, access: { read: anyRole, create: managers, update: managers, // вместе с сайтом плагин удаляет все его документы delete: owners, }, fields: [ { name: 'code', type: 'select', required: true, unique: true, options: [ { value: 'dedicated', label: label('Veilix (dedicated)', 'Вейликс (dedicated)') }, { value: 'mass', label: label('VeilixVPN (mass)', 'ВейликсВПН (mass)') }, ], label: label('Brand', 'Бренд'), // по коду выбираются тема и страницы бренда, после создания не меняется access: { update: () => false }, }, { name: 'name', type: 'text', required: true, localized: true, label: label('Name', 'Название'), }, { name: 'orderPrefix', type: 'text', required: true, unique: true, label: label('Order number prefix', 'Префикс номеров'), validate: validateOrderPrefix, }, { name: 'locales', type: 'select', hasMany: true, required: true, options: localeOptions, defaultValue: [...LOCALES], label: label('Content languages', 'Языки контента'), validate: validateLocales, admin: { description: label( 'Each domain of the site shows some of these languages.', 'Каждый домен сайта показывает часть этих языков.', ), }, }, ], }