diff --git a/packages/next/src/utilities/getRequestLanguage.ts b/packages/next/src/utilities/getRequestLanguage.ts index 018f07290..7b4a45891 100644 --- a/packages/next/src/utilities/getRequestLanguage.ts +++ b/packages/next/src/utilities/getRequestLanguage.ts @@ -19,7 +19,9 @@ export const getRequestLanguage = ({ }: GetRequestLanguageArgs): AcceptedLanguages => { const langCookie = cookies.get(`${config.cookiePrefix || 'payload'}-lng`) const languageFromCookie = typeof langCookie === 'string' ? langCookie : langCookie?.value - const languageFromHeader = extractHeaderLanguage(headers.get('Accept-Language')) + const languageFromHeader = headers.get('Accept-Language') + ? extractHeaderLanguage(headers.get('Accept-Language')) + : undefined const fallbackLang = config?.i18n?.fallbackLanguage || defaultLanguage const supportedLanguageKeys = Object.keys(config?.i18n?.supportedLanguages || {}) diff --git a/packages/payload/src/exports/i18n/ar.ts b/packages/payload/src/exports/i18n/ar.ts new file mode 100644 index 000000000..897e9d2e3 --- /dev/null +++ b/packages/payload/src/exports/i18n/ar.ts @@ -0,0 +1 @@ +export { ar } from '@payloadcms/translations/languages/ar' diff --git a/packages/payload/src/exports/i18n/az.ts b/packages/payload/src/exports/i18n/az.ts new file mode 100644 index 000000000..7859c0aa5 --- /dev/null +++ b/packages/payload/src/exports/i18n/az.ts @@ -0,0 +1 @@ +export { az } from '@payloadcms/translations/languages/az' diff --git a/packages/payload/src/exports/i18n/bg.ts b/packages/payload/src/exports/i18n/bg.ts new file mode 100644 index 000000000..082a1fb52 --- /dev/null +++ b/packages/payload/src/exports/i18n/bg.ts @@ -0,0 +1 @@ +export { bg } from '@payloadcms/translations/languages/bg' diff --git a/packages/payload/src/exports/i18n/cs.ts b/packages/payload/src/exports/i18n/cs.ts new file mode 100644 index 000000000..20bbd9ecc --- /dev/null +++ b/packages/payload/src/exports/i18n/cs.ts @@ -0,0 +1 @@ +export { cs } from '@payloadcms/translations/languages/cs' diff --git a/packages/payload/src/exports/i18n/de.ts b/packages/payload/src/exports/i18n/de.ts new file mode 100644 index 000000000..c128b1877 --- /dev/null +++ b/packages/payload/src/exports/i18n/de.ts @@ -0,0 +1 @@ +export { de } from '@payloadcms/translations/languages/de' diff --git a/packages/payload/src/exports/i18n/en.ts b/packages/payload/src/exports/i18n/en.ts new file mode 100644 index 000000000..e30aafa16 --- /dev/null +++ b/packages/payload/src/exports/i18n/en.ts @@ -0,0 +1 @@ +export { en } from '@payloadcms/translations/languages/en' diff --git a/packages/payload/src/exports/i18n/es.ts b/packages/payload/src/exports/i18n/es.ts new file mode 100644 index 000000000..ac6ea9ed8 --- /dev/null +++ b/packages/payload/src/exports/i18n/es.ts @@ -0,0 +1 @@ +export { es } from '@payloadcms/translations/languages/es' diff --git a/packages/payload/src/exports/i18n/fa.ts b/packages/payload/src/exports/i18n/fa.ts new file mode 100644 index 000000000..7acff2adb --- /dev/null +++ b/packages/payload/src/exports/i18n/fa.ts @@ -0,0 +1 @@ +export { fa } from '@payloadcms/translations/languages/fa' diff --git a/packages/payload/src/exports/i18n/fr.ts b/packages/payload/src/exports/i18n/fr.ts new file mode 100644 index 000000000..65de019fd --- /dev/null +++ b/packages/payload/src/exports/i18n/fr.ts @@ -0,0 +1 @@ +export { fr } from '@payloadcms/translations/languages/fr' diff --git a/packages/payload/src/exports/i18n/hr.ts b/packages/payload/src/exports/i18n/hr.ts new file mode 100644 index 000000000..c4b712841 --- /dev/null +++ b/packages/payload/src/exports/i18n/hr.ts @@ -0,0 +1 @@ +export { hr } from '@payloadcms/translations/languages/hr' diff --git a/packages/payload/src/exports/i18n/hu.ts b/packages/payload/src/exports/i18n/hu.ts new file mode 100644 index 000000000..7fad4df04 --- /dev/null +++ b/packages/payload/src/exports/i18n/hu.ts @@ -0,0 +1 @@ +export { hu } from '@payloadcms/translations/languages/hu' diff --git a/packages/payload/src/exports/i18n/it.ts b/packages/payload/src/exports/i18n/it.ts new file mode 100644 index 000000000..36bbb80de --- /dev/null +++ b/packages/payload/src/exports/i18n/it.ts @@ -0,0 +1 @@ +export { it } from '@payloadcms/translations/languages/it' diff --git a/packages/payload/src/exports/i18n/ja.ts b/packages/payload/src/exports/i18n/ja.ts new file mode 100644 index 000000000..ef65a7567 --- /dev/null +++ b/packages/payload/src/exports/i18n/ja.ts @@ -0,0 +1 @@ +export { ja } from '@payloadcms/translations/languages/ja' diff --git a/packages/payload/src/exports/i18n/ko.ts b/packages/payload/src/exports/i18n/ko.ts new file mode 100644 index 000000000..e080561e0 --- /dev/null +++ b/packages/payload/src/exports/i18n/ko.ts @@ -0,0 +1 @@ +export { ko } from '@payloadcms/translations/languages/ko' diff --git a/packages/payload/src/exports/i18n/my.ts b/packages/payload/src/exports/i18n/my.ts new file mode 100644 index 000000000..c040b71aa --- /dev/null +++ b/packages/payload/src/exports/i18n/my.ts @@ -0,0 +1 @@ +export { my } from '@payloadcms/translations/languages/my' diff --git a/packages/payload/src/exports/i18n/nb.ts b/packages/payload/src/exports/i18n/nb.ts new file mode 100644 index 000000000..e4ea01536 --- /dev/null +++ b/packages/payload/src/exports/i18n/nb.ts @@ -0,0 +1 @@ +export { nb } from '@payloadcms/translations/languages/nb' diff --git a/packages/payload/src/exports/i18n/nl.ts b/packages/payload/src/exports/i18n/nl.ts new file mode 100644 index 000000000..432a6d74c --- /dev/null +++ b/packages/payload/src/exports/i18n/nl.ts @@ -0,0 +1 @@ +export { nl } from '@payloadcms/translations/languages/nl' diff --git a/packages/payload/src/exports/i18n/pl.ts b/packages/payload/src/exports/i18n/pl.ts new file mode 100644 index 000000000..1b722ff67 --- /dev/null +++ b/packages/payload/src/exports/i18n/pl.ts @@ -0,0 +1 @@ +export { pl } from '@payloadcms/translations/languages/pl' diff --git a/packages/payload/src/exports/i18n/pt.ts b/packages/payload/src/exports/i18n/pt.ts new file mode 100644 index 000000000..533ff4fd6 --- /dev/null +++ b/packages/payload/src/exports/i18n/pt.ts @@ -0,0 +1 @@ +export { pt } from '@payloadcms/translations/languages/pt' diff --git a/packages/payload/src/exports/i18n/ro.ts b/packages/payload/src/exports/i18n/ro.ts new file mode 100644 index 000000000..1280ecec1 --- /dev/null +++ b/packages/payload/src/exports/i18n/ro.ts @@ -0,0 +1 @@ +export { ro } from '@payloadcms/translations/languages/ro' diff --git a/packages/payload/src/exports/i18n/rs.ts b/packages/payload/src/exports/i18n/rs.ts new file mode 100644 index 000000000..193c1cfa9 --- /dev/null +++ b/packages/payload/src/exports/i18n/rs.ts @@ -0,0 +1 @@ +export { rs } from '@payloadcms/translations/languages/rs' diff --git a/packages/payload/src/exports/i18n/rsLatin.ts b/packages/payload/src/exports/i18n/rsLatin.ts new file mode 100644 index 000000000..e03c7ed9b --- /dev/null +++ b/packages/payload/src/exports/i18n/rsLatin.ts @@ -0,0 +1 @@ +export { rsLatin } from '@payloadcms/translations/languages/rsLatin' diff --git a/packages/payload/src/exports/i18n/ru.ts b/packages/payload/src/exports/i18n/ru.ts new file mode 100644 index 000000000..8795e813b --- /dev/null +++ b/packages/payload/src/exports/i18n/ru.ts @@ -0,0 +1 @@ +export { ru } from '@payloadcms/translations/languages/ru' diff --git a/packages/payload/src/exports/i18n/sv.ts b/packages/payload/src/exports/i18n/sv.ts new file mode 100644 index 000000000..4d8384459 --- /dev/null +++ b/packages/payload/src/exports/i18n/sv.ts @@ -0,0 +1 @@ +export { sv } from '@payloadcms/translations/languages/sv' diff --git a/packages/payload/src/exports/i18n/th.ts b/packages/payload/src/exports/i18n/th.ts new file mode 100644 index 000000000..1dced32a2 --- /dev/null +++ b/packages/payload/src/exports/i18n/th.ts @@ -0,0 +1 @@ +export { th } from '@payloadcms/translations/languages/th' diff --git a/packages/payload/src/exports/i18n/tr.ts b/packages/payload/src/exports/i18n/tr.ts new file mode 100644 index 000000000..f711e422b --- /dev/null +++ b/packages/payload/src/exports/i18n/tr.ts @@ -0,0 +1 @@ +export { tr } from '@payloadcms/translations/languages/tr' diff --git a/packages/payload/src/exports/i18n/uk.ts b/packages/payload/src/exports/i18n/uk.ts new file mode 100644 index 000000000..a1d6832f2 --- /dev/null +++ b/packages/payload/src/exports/i18n/uk.ts @@ -0,0 +1 @@ +export { uk } from '@payloadcms/translations/languages/uk' diff --git a/packages/payload/src/exports/i18n/vi.ts b/packages/payload/src/exports/i18n/vi.ts new file mode 100644 index 000000000..0e97a5228 --- /dev/null +++ b/packages/payload/src/exports/i18n/vi.ts @@ -0,0 +1 @@ +export { vi } from '@payloadcms/translations/languages/vi' diff --git a/packages/payload/src/exports/i18n/zh.ts b/packages/payload/src/exports/i18n/zh.ts new file mode 100644 index 000000000..248182a46 --- /dev/null +++ b/packages/payload/src/exports/i18n/zh.ts @@ -0,0 +1 @@ +export { zh } from '@payloadcms/translations/languages/zh' diff --git a/packages/payload/src/exports/i18n/zhTw.ts b/packages/payload/src/exports/i18n/zhTw.ts new file mode 100644 index 000000000..acb2ecc66 --- /dev/null +++ b/packages/payload/src/exports/i18n/zhTw.ts @@ -0,0 +1 @@ +export { zhTw } from '@payloadcms/translations/languages/zhTw' diff --git a/packages/translations/README.md b/packages/translations/README.md index 22c328f79..a6904beb5 100644 --- a/packages/translations/README.md +++ b/packages/translations/README.md @@ -1,14 +1,13 @@ # Payload Translations -These are the translations for Payload. Translations are used on both the server and the client. The admin panel uses translations to display text to the user in their selected language. The server uses translations when sending API responses. +The home of Payloads API and Admin Panel translations. ## How to contribute #### Updating a translation -1. Open the language file you wish to edit located within the `src/all` folder -2. Update the translation value -3. Run one of the following: +1. Update the translation value +2. Run one of the following: ```sh yarn build // or @@ -19,9 +18,8 @@ These are the translations for Payload. Translations are used on both the server #### Adding a new translation -1. Add the new translation key/value pair for all languages located in the `src/all` folder -2. Open the `writeTranslationFiles.ts` file and add the key to either `clientTranslationKeys` or `serverTranslationKeys` depending on where the translation will be used. -3. Run one of the following: +1. Add the new translation key/value pair for **all** languages located in the `/packages/translations/src/languages` folder +2. Run one of the following: ```sh yarn build // or @@ -32,10 +30,9 @@ These are the translations for Payload. Translations are used on both the server #### Adding a new language -1. Create a new JSON file in the `src/all` folder with the language code as the file name (e.g. `en.json` for English) -2. Translate all of the keys in the new file -3. Open the `src/index.ts` file and import your json file and then export it inside the `translations` object -4. Run one of the following: +1. Create a new TS file in the `/packages/translations/src/languages` folder, use the language code as the file name (e.g. `/packages/translations/src/languages/en.ts` for English) +2. Copy all translations from an existing language file and update all of the translations to match your new language +3. Run one of the following: ```sh yarn build // or @@ -43,6 +40,8 @@ These are the translations for Payload. Translations are used on both the server // or pnpm build ``` +4. Import and export your new language file from within `/packages/translations/src/exports/all.ts` +5. Re-export the file from within `/packages/payload/src/exports/i18n/[your-new-language].ts` Here is a full list of language keys. Note that these are not all implemented, but if you would like to contribute and add a new language, you can use this list as a reference: diff --git a/packages/translations/writeTranslationFiles.ts b/packages/translations/writeTranslationFiles.ts deleted file mode 100644 index a1d001ce7..000000000 --- a/packages/translations/writeTranslationFiles.ts +++ /dev/null @@ -1,480 +0,0 @@ -/* eslint-disable no-console */ -import { exec } from 'child_process' -import * as fs from 'fs' -import * as path from 'path' -import { fileURLToPath } from 'url' - -import { translations } from './src/all/index.js' -import { copyFile } from './src/utilities/copyFile.js' -import { ensureDirectoryExists } from './src/utilities/ensureDirExists.js' - -const filename = fileURLToPath(import.meta.url) -const dirname = path.dirname(filename) - -const serverTranslationKeys = [ - 'authentication:account', - 'authentication:api', - 'authentication:apiKey', - 'authentication:enableAPIKey', - 'authentication:newAccountCreated', - 'authentication:resetYourPassword', - 'authentication:verifyYourEmail', - 'authentication:youAreReceivingResetPassword', - 'authentication:loggedInChangePassword', - 'authentication:youDidNotRequestPassword', - 'authentication:verified', - - 'fields:textToDisplay', - 'fields:linkType', - 'fields:chooseBetweenCustomTextOrDocument', - 'fields:customURL', - 'fields:internalLink', - 'fields:enterURL', - 'fields:chooseDocumentToLink', - 'fields:openInNewTab', - - 'general:copy', - 'general:createdAt', - 'general:deletedCountSuccessfully', - 'general:deletedSuccessfully', - 'general:email', - 'general:notFound', - 'general:successfullyCreated', - 'general:successfullyDuplicated', - 'general:thisLanguage', - 'general:user', - 'general:users', - 'general:updatedAt', - 'general:updatedSuccessfully', - 'general:updatedCountSuccessfully', - 'general:value', - 'general:row', - 'general:rows', - - 'error:deletingFile', - 'error:emailOrPasswordIncorrect', - 'error:followingFieldsInvalid', - 'error:noFilesUploaded', - 'error:notAllowedToPerformAction', - 'error:problemUploadingFile', - 'error:unableToDeleteCount', - 'error:unableToUpdateCount', - 'error:unauthorized', - 'error:userLocked', - 'error:valueMustBeUnique', - - 'upload:width', - 'upload:height', - 'upload:fileSize', - 'upload:fileName', - 'upload:sizes', - - 'validation:emailAddress', - 'validation:enterNumber', - 'validation:greaterThanMax', - 'validation:invalidInput', - 'validation:invalidSelection', - 'validation:invalidSelections', - 'validation:lessThanMin', - 'validation:longerThanMin', - 'validation:notValidDate', - 'validation:required', - 'validation:requiresAtLeast', - 'validation:requiresNoMoreThan', - 'validation:requiresTwoNumbers', - 'validation:shorterThanMax', - 'validation:trueOrFalse', - 'validation:validUploadID', - - 'version:autosavedSuccessfully', - 'version:draftSavedSuccessfully', - 'version:restoredSuccessfully', - 'version:draft', - 'version:published', - 'version:status', -] - -const clientTranslationKeys = [ - 'authentication:account', - 'authentication:accountOfCurrentUser', - 'authentication:alreadyActivated', - 'authentication:alreadyLoggedIn', - 'authentication:backToLogin', - 'authentication:beginCreateFirstUser', - 'authentication:changePassword', - 'authentication:confirmGeneration', - 'authentication:confirmPassword', - 'authentication:createFirstUser', - 'authentication:emailNotValid', - 'authentication:emailSent', - 'authentication:enableAPIKey', - 'authentication:failedToUnlock', - 'authentication:forceUnlock', - 'authentication:forgotPassword', - 'authentication:forgotPasswordEmailInstructions', - 'authentication:forgotPasswordQuestion', - 'authentication:generate', - 'authentication:generateNewAPIKey', - 'authentication:logBackIn', - 'authentication:loggedOutInactivity', - 'authentication:loggedOutSuccessfully', - 'authentication:login', - 'authentication:logOut', - 'authentication:logout', - 'authentication:logoutUser', - 'authentication:newAPIKeyGenerated', - 'authentication:newPassword', - 'authentication:resetPassword', - 'authentication:stayLoggedIn', - 'authentication:successfullyUnlocked', - 'authentication:unableToVerify', - 'authentication:verified', - 'authentication:verifiedSuccessfully', - 'authentication:verify', - 'authentication:verifyUser', - 'authentication:youAreInactive', - - 'error:autosaving', - 'error:correctInvalidFields', - 'error:deletingTitle', - 'error:loadingDocument', - 'error:noMatchedField', - 'error:notAllowedToAccessPage', - 'error:previewing', - 'error:unableToDeleteCount', - 'error:unableToUpdateCount', - 'error:unauthorized', - 'error:unknown', - 'error:unspecific', - - 'fields:addLabel', - 'fields:addLink', - 'fields:addNew', - 'fields:addNewLabel', - 'fields:addRelationship', - 'fields:addUpload', - 'fields:block', - 'fields:blocks', - 'fields:blockType', - 'fields:chooseFromExisting', - 'fields:collapseAll', - 'fields:editLink', - 'fields:editRelationship', - 'fields:itemsAndMore', - 'fields:labelRelationship', - 'fields:latitude', - 'fields:linkedTo', - 'fields:longitude', - 'fields:passwordsDoNotMatch', - 'fields:removeRelationship', - 'fields:removeUpload', - 'fields:saveChanges', - 'fields:searchForBlock', - 'fields:selectFieldsToEdit', - 'fields:showAll', - 'fields:swapRelationship', - 'fields:swapUpload', - 'fields:toggleBlock', - 'fields:uploadNewLabel', - - 'general:aboutToDeleteCount', - 'general:aboutToDelete', - 'general:addBelow', - 'general:addFilter', - 'general:adminTheme', - 'general:and', - 'general:applyChanges', - 'general:ascending', - 'general:automatic', - 'general:backToDashboard', - 'general:cancel', - 'general:changesNotSaved', - 'general:close', - 'general:collapse', - 'general:collections', - 'general:columns', - 'general:columnToSort', - 'general:confirm', - 'general:confirmDeletion', - 'general:confirmDuplication', - 'general:copied', - 'general:copy', - 'general:create', - 'general:created', - 'general:createNew', - 'general:createNewLabel', - 'general:creating', - 'general:creatingNewLabel', - 'general:dark', - 'general:dashboard', - 'general:delete', - 'general:deletedCountSuccessfully', - 'general:deleting', - 'general:descending', - 'general:deselectAllRows', - 'general:duplicate', - 'general:duplicateWithoutSaving', - 'general:edit', - 'general:editing', - 'general:editingLabel', - 'general:editLabel', - 'general:email', - 'general:emailAddress', - 'general:enterAValue', - 'general:error', - 'general:errors', - 'general:fallbackToDefaultLocale', - 'general:filters', - 'general:filterWhere', - 'general:globals', - 'general:language', - 'general:lastModified', - 'general:leaveAnyway', - 'general:leaveWithoutSaving', - 'general:light', - 'general:livePreview', - 'general:loading', - 'general:locale', - 'general:menu', - 'general:moveDown', - 'general:moveUp', - 'general:noFiltersSet', - 'general:noLabel', - 'general:none', - 'general:noOptions', - 'general:noResults', - 'general:notFound', - 'general:nothingFound', - 'general:noValue', - 'general:of', - 'general:open', - 'general:or', - 'general:order', - 'general:pageNotFound', - 'general:password', - 'general:payloadSettings', - 'general:perPage', - 'general:remove', - 'general:reset', - 'general:row', - 'general:rows', - 'general:save', - 'general:saving', - 'general:searchBy', - 'general:selectAll', - 'general:selectAllRows', - 'general:selectedCount', - 'general:selectValue', - 'general:showAllLabel', - 'general:sorryNotFound', - 'general:sort', - 'general:sortByLabelDirection', - 'general:stayOnThisPage', - 'general:submissionSuccessful', - 'general:submit', - 'general:successfullyCreated', - 'general:successfullyDeleted', - 'general:thisLanguage', - 'general:titleDeleted', - 'general:unauthorized', - 'general:unsavedChangesDuplicate', - 'general:untitled', - 'general:updatedAt', - 'general:updatedCountSuccessfully', - 'general:updatedSuccessfully', - 'general:updating', - 'general:welcome', - - 'operators:equals', - 'operators:exists', - 'operators:isNotIn', - 'operators:isIn', - 'operators:contains', - 'operators:isLike', - 'operators:isNotEqualTo', - 'operators:near', - 'operators:isGreaterThan', - 'operators:isLessThan', - 'operators:isGreaterThanOrEqualTo', - 'operators:isLessThanOrEqualTo', - - 'upload:crop', - 'upload:cropToolDescription', - 'upload:dragAndDrop', - 'upload:editImage', - 'upload:focalPoint', - 'upload:focalPointDescription', - 'upload:height', - 'upload:previewSizes', - 'upload:selectCollectionToBrowse', - 'upload:selectFile', - 'upload:setCropArea', - 'upload:setFocalPoint', - 'upload:sizesFor', - 'upload:width', - - 'validation:fieldHasNo', - 'validation:limitReached', - 'validation:required', - 'validation:requiresAtLeast', - - 'version:aboutToPublishSelection', - 'version:aboutToRestore', - 'version:aboutToRestoreGlobal', - 'version:aboutToRevertToPublished', - 'version:aboutToUnpublish', - 'version:aboutToUnpublishSelection', - 'version:autosave', - 'version:autosavedSuccessfully', - 'version:changed', - 'version:confirmRevertToSaved', - 'version:compareVersion', - 'version:confirmPublish', - 'version:confirmUnpublish', - 'version:confirmVersionRestoration', - 'version:draft', - 'version:draftSavedSuccessfully', - 'version:lastSavedAgo', - 'version:noFurtherVersionsFound', - 'version:noRowsFound', - 'version:preview', - 'version:problemRestoringVersion', - 'version:publish', - 'version:publishChanges', - 'version:published', - 'version:publishing', - 'version:restoredSuccessfully', - 'version:restoreThisVersion', - 'version:restoring', - 'version:revertToPublished', - 'version:saveDraft', - 'version:selectLocales', - 'version:selectVersionToCompare', - 'version:showLocales', - 'version:status', - 'version:type', - 'version:unpublish', - 'version:unpublishing', - 'version:versionCreatedOn', - 'version:versionID', - 'version:version', - 'version:versions', - 'version:viewingVersion', - 'version:viewingVersionGlobal', - 'version:viewingVersions', - 'version:viewingVersionsGlobal', -] - -const DESTINATION_ROOT = './src/_generatedFiles_' -const SOURCE_DIR = './src/all' - -function filterKeys(obj, parentGroupKey = '', keys) { - const result = {} - - for (const [namespaceKey, value] of Object.entries(obj)) { - // Skip $schema key - if (namespaceKey === '$schema') { - result[namespaceKey] = value - continue - } - - if (typeof value === 'object') { - const filteredObject = filterKeys(value, namespaceKey, keys) - if (Object.keys(filteredObject).length > 0) { - result[namespaceKey] = filteredObject - } - } else { - for (const key of keys) { - const [groupKey, selector] = key.split(':') - - if (parentGroupKey === groupKey) { - if (namespaceKey === selector) { - result[selector] = value - } else { - const pluralKeys = ['zero', 'one', 'two', 'few', 'many', 'other'] - pluralKeys.forEach((pluralKey) => { - if (namespaceKey === `${selector}_${pluralKey}`) { - result[`${selector}_${pluralKey}`] = value - } - }) - } - } - } - } - } - - return result -} - -function sortObject(obj) { - const sortedObject = {} - Object.keys(obj) - .sort() - .forEach((key) => { - if (typeof obj[key] === 'object') { - sortedObject[key] = sortObject(obj[key]) - } else { - sortedObject[key] = obj[key] - } - }) - return sortedObject -} - -function build() { - return new Promise((resolve, reject) => { - ensureDirectoryExists(path.resolve(dirname, `${DESTINATION_ROOT}/client`)) - ensureDirectoryExists(path.resolve(dirname, `${DESTINATION_ROOT}/api`)) - - try { - // build up the client and server translation files - for (const [locale, values] of Object.entries(translations)) { - const dest1 = path.resolve(dirname, `${DESTINATION_ROOT}/client/${locale}.js`) - - const clientTranslations = sortObject(filterKeys(values, '', clientTranslationKeys)) - - fs.writeFileSync(dest1, 'export default ' + JSON.stringify(clientTranslations, null, 2), { - flag: 'w+', - }) - - const serverTranslations = sortObject(filterKeys(values, '', serverTranslationKeys)) - const dest2 = path.resolve(dirname, `${DESTINATION_ROOT}/api/${locale}.js`) - - fs.writeFileSync(dest2, 'export default ' + JSON.stringify(serverTranslations, null, 2), { - flag: 'w+', - }) - - console.info('Rebuilt:', filename) - } - - // Copy barrel file to both client and api folders - copyFile( - path.resolve(dirname, `${SOURCE_DIR}/index.ts`), - path.resolve(dirname, `${DESTINATION_ROOT}/api/index.ts`), - ) - copyFile( - path.resolve(dirname, `${SOURCE_DIR}/index.ts`), - path.resolve(dirname, `${DESTINATION_ROOT}/client/index.ts`), - ) - - // Run prettier from CLI so that files pass the pre-commit hook: - console.info('Running prettier...') - exec('prettier --write "**/*.js"', (err, stdout) => { - if (err) { - console.error(err) - } else { - console.info(stdout) - } - }) - } catch (error) { - reject(error) - } - }) -} - -build() - .then(() => { - console.log('Built client and api translation files.') - }) - .catch((error) => { - console.error('Error occurred:', error) - }) diff --git a/packages/ui/src/fields/Array/index.tsx b/packages/ui/src/fields/Array/index.tsx index bdebd7e6d..05105b86b 100644 --- a/packages/ui/src/fields/Array/index.tsx +++ b/packages/ui/src/fields/Array/index.tsx @@ -62,7 +62,7 @@ export const _ArrayField: React.FC = (props) => { labelProps, localized, maxRows, - minRows, + minRows: minRowsProp, path: pathFromProps, permissions, readOnly: readOnlyFromProps, @@ -72,6 +72,7 @@ export const _ArrayField: React.FC = (props) => { const { indexPath, readOnly: readOnlyFromContext } = useFieldProps() const readOnly = readOnlyFromProps || readOnlyFromContext + const minRows = minRowsProp ?? required ? 1 : 0 const { setDocFieldPreferences } = useDocumentInfo() const { addFieldRow, dispatchFields, setModified } = useForm() @@ -300,7 +301,7 @@ export const _ArrayField: React.FC = (props) => { {t('validation:requiresAtLeast', { count: minRows, label: - getTranslation(minRows ? labels.plural : labels.singular, i18n) || + getTranslation(minRows > 1 ? labels.plural : labels.singular, i18n) || t(minRows > 1 ? 'general:row' : 'general:rows'), })} diff --git a/packages/ui/src/fields/Blocks/index.tsx b/packages/ui/src/fields/Blocks/index.tsx index 6387d7c89..f730cbace 100644 --- a/packages/ui/src/fields/Blocks/index.tsx +++ b/packages/ui/src/fields/Blocks/index.tsx @@ -67,7 +67,7 @@ const _BlocksField: React.FC = (props) => { labels: labelsFromProps, localized, maxRows, - minRows, + minRows: minRowsProp, path: pathFromProps, readOnly: readOnlyFromProps, required, @@ -76,6 +76,7 @@ const _BlocksField: React.FC = (props) => { const { indexPath, readOnly: readOnlyFromContext } = useFieldProps() const readOnly = readOnlyFromProps || readOnlyFromContext + const minRows = minRowsProp ?? required ? 1 : 0 const { setDocFieldPreferences } = useDocumentInfo() const { addFieldRow, dispatchFields, setModified } = useForm() @@ -313,12 +314,9 @@ const _BlocksField: React.FC = (props) => { {t('validation:requiresAtLeast', { count: minRows, - label: getTranslation( - minRows === 1 || typeof minRows === 'undefined' - ? labels.singular - : labels.plural, - i18n, - ), + label: + getTranslation(minRows > 1 ? labels.plural : labels.singular, i18n) || + t(minRows > 1 ? 'general:row' : 'general:rows'), })} )} diff --git a/test/admin/e2e.spec.ts b/test/admin/e2e.spec.ts index 0e2e4d36d..bbf156c32 100644 --- a/test/admin/e2e.spec.ts +++ b/test/admin/e2e.spec.ts @@ -15,6 +15,7 @@ import { exactText, initPageConsoleErrorCatch, openDocControls, + openDocDrawer, openNav, saveDocAndAssert, saveDocHotkeyAndAssert, @@ -1052,8 +1053,8 @@ describe('admin', () => { await createPost() await page.goto(postsUrl.create) - // Open the drawer - await page.locator('.rich-text .list-drawer__toggler').click() + await openDocDrawer(page, '.rich-text .list-drawer__toggler') + const listDrawer = page.locator('[id^=list-drawer_1_]') await expect(listDrawer).toBeVisible() @@ -1092,7 +1093,7 @@ describe('admin', () => { await page.goto(postsUrl.create) // Open the drawer - await page.locator('.rich-text .list-drawer__toggler').click() + await openDocDrawer(page, '.rich-text .list-drawer__toggler') const listDrawer = page.locator('[id^=list-drawer_1_]') await expect(listDrawer).toBeVisible() diff --git a/test/buildConfigWithDefaults.ts b/test/buildConfigWithDefaults.ts index c10b1d831..dfd9a630e 100644 --- a/test/buildConfigWithDefaults.ts +++ b/test/buildConfigWithDefaults.ts @@ -25,11 +25,11 @@ import { UploadFeature, lexicalEditor, } from '@payloadcms/richtext-lexical' -import { de } from '@payloadcms/translations/languages/de' -import { en } from '@payloadcms/translations/languages/en' -import { es } from '@payloadcms/translations/languages/es' // import { slateEditor } from '@payloadcms/richtext-slate' import { type Config, buildConfig } from 'payload/config' +import { de } from 'payload/i18n/de' +import { en } from 'payload/i18n/en' +import { es } from 'payload/i18n/es' import sharp from 'sharp' import { reInitEndpoint } from './helpers/reInit.js' diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index b44855585..f12e206ae 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -12,6 +12,7 @@ import { ensureAutoLoginAndCompilationIsDone, initPageConsoleErrorCatch, navigateToListCellLink, + openDocDrawer, saveDocAndAssert, switchTab, } from '../helpers.js' @@ -767,8 +768,9 @@ describe('fields', () => { await uploadImage() await wait(500) // Open the media drawer and create a png upload - await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click() - await wait(1000) // TODO: Fix this. Need to wait a bit until the form in the drawer mounted, otherwise values sometimes disappear. This is an issue for all drawers + + await openDocDrawer(page, '.field-type.upload .upload__toggler.doc-drawer__toggler') + await page .locator('[id^=doc-drawer_uploads_1_] .file-field__upload input[type="file"]') .setInputFiles(path.resolve(dirname, './uploads/payload.png')) @@ -795,7 +797,8 @@ describe('fields', () => { test('should clear selected upload', async () => { await uploadImage() await wait(1000) // TODO: Fix this. Need to wait a bit until the form in the drawer mounted, otherwise values sometimes disappear. This is an issue for all drawers - await page.locator('.field-type.upload .upload__toggler.doc-drawer__toggler').click() + + await openDocDrawer(page, '.field-type.upload .upload__toggler.doc-drawer__toggler') await page .locator('[id^=doc-drawer_uploads_1_] .file-field__upload input[type="file"]') @@ -811,8 +814,7 @@ describe('fields', () => { test('should select using the list drawer and restrict mimetype based on filterOptions', async () => { await uploadImage() - await page.locator('.field-type.upload .upload__toggler.list-drawer__toggler').click() - await wait(500) // TODO: Fix this. Need to wait a bit until the form in the drawer mounted, otherwise values sometimes disappear. This is an issue for all drawers + await openDocDrawer(page, '.field-type.upload .upload__toggler.list-drawer__toggler') const jpgImages = page.locator('[id^=list-drawer_1_] .upload-gallery img[src$=".jpg"]') await expect @@ -834,7 +836,7 @@ describe('fields', () => { await wait(200) // open drawer - await page.locator('.field-type.upload .list-drawer__toggler').click() + await openDocDrawer(page, '.field-type.upload .list-drawer__toggler') // check title await expect(page.locator('.list-drawer__header-text')).toContainText('Uploads 3') }) diff --git a/test/helpers.ts b/test/helpers.ts index 7ae6847d0..c08273919 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -130,6 +130,11 @@ export async function openNav(page: Page): Promise { await expect(page.locator('.template-default.template-default--nav-open')).toBeVisible() } +export async function openDocDrawer(page: Page, selector: string): Promise { + await page.locator(selector).click() + await wait(300) // wait for drawer form state to initialize +} + export async function closeNav(page: Page): Promise { if (!(await page.locator('.template-default.template-default--nav-open').isVisible())) return await page.locator('.nav-toggler >> visible=true').click() diff --git a/test/uploads/e2e.spec.ts b/test/uploads/e2e.spec.ts index 0597406e7..00ffd7ba0 100644 --- a/test/uploads/e2e.spec.ts +++ b/test/uploads/e2e.spec.ts @@ -11,6 +11,7 @@ import type { Media } from './payload-types.js' import { ensureAutoLoginAndCompilationIsDone, initPageConsoleErrorCatch, + openDocDrawer, saveDocAndAssert, } from '../helpers.js' import { AdminUrlUtil } from '../helpers/adminUrlUtil.js' @@ -203,7 +204,7 @@ describe('uploads', () => { await page.locator('.field-type:nth-of-type(2) .icon--x').click() // choose from existing - await page.locator('.list-drawer__toggler').click() + await openDocDrawer(page, '.list-drawer__toggler') await expect(page.locator('.cell-title')).toContainText('draft') }) @@ -214,13 +215,16 @@ describe('uploads', () => { // remove the selection and open the list drawer await page.locator('.file-details__remove').click() - await page.locator('.upload__toggler.list-drawer__toggler').click() + + await openDocDrawer(page, '.upload__toggler.list-drawer__toggler') + const listDrawer = page.locator('[id^=list-drawer_1_]') await expect(listDrawer).toBeVisible() - // upload an image and try to select it - await listDrawer.locator('button.list-drawer__create-new-button.doc-drawer__toggler').click() + await openDocDrawer(page, 'button.list-drawer__create-new-button.doc-drawer__toggler') await expect(page.locator('[id^=doc-drawer_media_2_]')).toBeVisible() + + // upload an image and try to select it await page .locator('[id^=doc-drawer_media_2_] .file-field__upload input[type="file"]') .setInputFiles(path.resolve(dirname, './image.png'))