Single-language support as a first-class config shape - #5
Merged
Conversation
cms/src/site-config.ts is now the single source of truth for the shipped locale set (LOCALES, DEFAULT_LOCALE), the per-locale admin labels, the build-time SITE_NAME, and the derived LOCALE_URL_PREFIX flag. shared.ts re-exports the four names so existing importers are unchanged, and the web app reads them through web/src/cms/types.ts. payload.config.ts derives localization.locales from LOCALES instead of a hardcoded array, which lets the locale set change in one place and makes the old boot-time drift guard structurally impossible, so it is removed. The admin panel title suffix now reads from SITE_NAME. For the shipped ['de','en'] config this is additive: generated types and public output are unchanged.
A project that ships one locale now serves public URLs without the
/{locale} prefix (/about instead of /de/about). The pages-plugin always
stores paths as /{locale}/..., so the prefix is normalized away at the
web ingress. stripLocalePath rewrites a single stored path and
stripLocalePathsDeep walks a whole populated document (nav links,
breadcrumbs, list blocks, and rich-text internal links). Both no-op in
multi-locale mode and on preview requests, so multi-locale output is
byte-for-byte unchanged and preview stays prefixed end to end.
The CMS keeps storing prefixed paths, so switching a project between one
and several locales is a config-only change with no content migration.
The admin's links to a published page drop the prefix to match the
served URL. The live-preview link keeps it.
woerndl
marked this pull request as ready for review
May 31, 2026 12:57
A single-locale project may want to ship /en/about today so its URLs
survive adding a second locale later with no redirects. FORCE_URL_PREFIX
in site-config.ts forces the prefix on. Left null, prefixing follows the
locale count as before. Forcing it off while several locales ship is
rejected at import, since their un-prefixed URLs would collide.
Splits the gating so URL shape and multilingual UI no longer move
together: LOCALE_URL_PREFIX drives the /{locale} segment, while
MULTIPLE_LOCALES (more than one locale ships) drives the language switcher
and the hreflang and x-default tags. A forced single-locale prefix
therefore keeps clean markup, with no switcher and no alternates.
Updates the README, architecture, and maintenance docs.
The committed map no longer matched payload generate:importmap output, so the CMS dev server rewrote it on every boot. Regenerated to match. Only the LinkFeatureClient import position changed, no functional difference.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes single-language a first-class config shape, so a project that ships one locale doesn't have to delete locales or add a
/de/prefix workaround.Locale and site config move into one module,
cms/src/site-config.ts, holdingLOCALES,DEFAULT_LOCALE,LOCALE_LABELS,SITE_NAME, and the derivedLOCALE_URL_PREFIX.shared.tsre-exports these so existing CMS imports keep working, and the web app reads them throughweb/src/cms/types.ts. Payload'slocalization.localesis now derived fromLOCALES, which removes the boot-time check that reconciled two separate locale lists. The admin panel title reads fromSITE_NAME.When a project ships one locale, the
/{lang}URL prefix is dropped: pages serve at/aboutrather than/de/about, and the language switcher,hreflang, andx-defaulttags fall away. The CMS still stores the prefixed path and normalizes it away at the web boundary, so preview URLs stay prefixed, multi-locale projects are byte-for-byte unaffected, and switching a project between one and several locales stays config-only with no content migration.A project that ships one locale today but expects to add more later can set
FORCE_URL_PREFIXto keep the/{lang}prefix, so its URLs survive that change with no redirects. Setting it to drop the prefix while several locales ship is rejected at startup, since the un-prefixed URLs would collide.For the default
['de','en']config the change is additive: generated types and public output are unchanged. A single-locale (['de']) config compiles, boots, and regenerates types to one locale.Ships as 0.3.0.