Skip to content

Single-language support as a first-class config shape - #5

Merged
woerndl merged 5 commits into
mainfrom
feat/single-language-support
May 31, 2026
Merged

Single-language support as a first-class config shape#5
woerndl merged 5 commits into
mainfrom
feat/single-language-support

Conversation

@woerndl

@woerndl woerndl commented May 31, 2026

Copy link
Copy Markdown
Owner

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, holding LOCALES, DEFAULT_LOCALE, LOCALE_LABELS, SITE_NAME, and the derived LOCALE_URL_PREFIX. shared.ts re-exports these so existing CMS imports keep working, and the web app reads them through web/src/cms/types.ts. Payload's localization.locales is now derived from LOCALES, which removes the boot-time check that reconciled two separate locale lists. The admin panel title reads from SITE_NAME.

When a project ships one locale, the /{lang} URL prefix is dropped: pages serve at /about rather than /de/about, and the language switcher, hreflang, and x-default tags 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_PREFIX to 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.

woerndl added 2 commits May 31, 2026 12:23
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
woerndl marked this pull request as ready for review May 31, 2026 12:57
woerndl added 3 commits May 31, 2026 16:42
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.
@woerndl
woerndl merged commit 97137c6 into main May 31, 2026
1 check passed
@woerndl
woerndl deleted the feat/single-language-support branch May 31, 2026 19:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant