Development
Monorepo
The repository is an Nx monorepo with these projects:
plugin: thepayload-crowdin-syncpackage.document: thepayload-localized-fieldspackage — Payload field walks used by the plugin and usable by other import/export plugins.adapters: thepayload-localization-adapterspackage — vendor-neutralSourceWriter/TranslationReadercontracts. Phrase and Lokalise plugins in this monorepo will depend on it.
How those three packages call each other, and what a "document slice" is, is in packages.
dev: a Payload install that uses the plugin, for integration tests. Tests that need a different plugin config passpluginOptionsOverridetoinitPayloadInt.docs-site: this documentation site.
Build
In the root of the repository:
nx build pluginbuilds todist/plugin.nx build documentbuilds todist/document.nx build adaptersbuilds todist/adapters.nx build plugin --watchrebuilds when files change.nx run document:native-esm(andadapters/plugin) builds, rewrites published entrypoints, and loads the package with Node's native ESM resolver. Production relative imports use.jsspecifiers so that import works without a bundler.
Lint
nx lint pluginnx lint dev
Test
Tests use Vitest. In the root of the repository:
npm run testruns all tests.npm run test:coverageruns all tests with coverage. Reports are written tocoverage/<project>.nx test pluginruns plugin unit tests only.nx test documentrunspayload-localized-fieldsunit tests.nx test adaptersrunspayload-localization-adaptersunit tests.nx test devruns integration tests against the Payload install.
Plugin unit tests (*.spec.ts) sit next to the code they test in plugin/src. payload-localized-fields specs sit in document/src. Writer/reader specs sit in adapters/src. Payload field walks and the json/html document slice (getLocalizedFields, traverseFields, restoreOrder, buildJsonObject, buildHtmlObject, buildUpdateObject) live in the payload-localized-fields package (document/). Vendor-neutral upload/download orchestration (syncDocumentSource, applyDocumentSlice) lives in payload-localization-adapters (adapters/). Crowdin wrappers and rich-text HTML conversion stay in plugin/src/lib/utilities/. The Crowdin plugin re-exports the adapter contracts.
Integration tests (*.test.ts) are in dev/src/tests. They use Payload's Local API against a real Payload install with an in-memory MongoDB database. Calls to the Crowdin API are intercepted with nock, so tests check which requests the plugin makes without needing a Crowdin account.
To start Payload with a different plugin config, pass pluginOptionsOverride to initPayloadInt. The override is shallow-merged over the default test options, so { collections: undefined } replaces the default collections list. Pass the same override to pluginConfig when setting up Crowdin mocks.
CI runs npm run test:coverage and uploads the results to Codecov. Plugin coverage also includes document/src and adapters/src so Crowdin wrapper specs still count hits on the shared packages. This needs the CODECOV_TOKEN repository secret.
Import maps
Payload finds custom admin components through an import map. After changing admin components in dev/, regenerate it:
npm run generate:importmaps
CI fails if the committed import map is missing or out of date.
Running the integration tests can overwrite dev/src/app/(payload)/admin/importMap.js with an empty map. Restore it with git restore before committing.
Run a Payload install locally
cd dev
npm run dev
A local install is useful for trying out changes in the admin panel, and for generating fixtures for integration tests.