fix(cms): nl/pt/ru i18n + gate external_url in public response

Two follow-ups to PR #372 (external-URL toggle for imprint /
privacy CMS pages):

1. **i18n.** PR #372 added 6 new `cms.*` keys to the en + de
   locales but the project ships 5 locales total. Adds the missing
   nl / pt / ru translations so the admin CMS page renders in the
   active language for those users instead of falling back to
   English literals next to the German/Dutch/Portuguese/Russian
   surrounding strings.

2. **API shape.** `publicCMS.js` returned `external_url`
   unconditionally — even when `use_external_url` is false the URL
   value was still emitted in the public response. The frontend
   correctly gated on both flags so it worked, but the API surface
   was leaking a value the admin had explicitly disabled. The
   value still lives in the DB (so the toggle can be flipped back
   on without losing it), but the public endpoint now returns
   `null` whenever the toggle is off.

   Note: kept the existing `logo_url` shape unchanged. Its semantics
   are different — null means "fall back to global branding" and
   consumers rely on always having the field, so emitting it
   unconditionally is intentional there.

No frontend change needed: both `GalleryLayout` and `LegalPage`
already gate on `use_external_url && external_url`, so the
short-circuit handles `external_url: null` correctly.
This commit is contained in:
Paul Nothaft
2026-05-04 00:14:07 +02:00
parent ccd3fd9349
commit bce5c1f725
4 changed files with 26 additions and 5 deletions
+5 -2
View File
@@ -27,9 +27,12 @@ router.get('/pages/:slug', async (req, res) => {
logo_url: page.logo_url || null, logo_url: page.logo_url || null,
// Per-page external-URL override. When use_external_url is true and // Per-page external-URL override. When use_external_url is true and
// external_url is set, consumers should redirect / link out instead // external_url is set, consumers should redirect / link out instead
// of rendering the internal title/content. // of rendering the internal title/content. external_url is gated by
// use_external_url so the public response never exposes a URL the
// admin has saved-but-disabled (the value stays in the DB so the
// toggle can be flipped back on, but it shouldn't leak via the API).
use_external_url: !!page.use_external_url, use_external_url: !!page.use_external_url,
external_url: page.external_url || null, external_url: page.use_external_url && page.external_url ? page.external_url : null,
updated_at: page.updated_at updated_at: page.updated_at
}); });
} catch (error) { } catch (error) {
+7 -1
View File
@@ -1753,7 +1753,13 @@
"lastUpdated": "Laatst bijgewerkt:", "lastUpdated": "Laatst bijgewerkt:",
"impressum": "Colofon", "impressum": "Colofon",
"datenschutz": "Privacybeleid", "datenschutz": "Privacybeleid",
"pageUpdated": "Pagina succesvol bijgewerkt" "pageUpdated": "Pagina succesvol bijgewerkt",
"useExternalUrl": "Externe URL gebruiken",
"useExternalUrlHelp": "Bezoekers worden doorgestuurd naar een externe pagina in plaats van de interne inhoud te zien. De interne titel en inhoud blijven als fallback opgeslagen.",
"externalUrl": "Externe URL",
"externalUrlPlaceholder": "https://example.com/impressum",
"externalUrlInvalid": "Moet een geldige https://-URL zijn",
"externalUrlActive": "Externe URL is actief — interne inhoud blijft bewaard maar wordt niet aan bezoekers getoond."
}, },
"eventTypes": { "eventTypes": {
"title": "Evenementtypes", "title": "Evenementtypes",
+7 -1
View File
@@ -1753,7 +1753,13 @@
"lastUpdated": "Última atualização:", "lastUpdated": "Última atualização:",
"impressum": "Aviso Legal", "impressum": "Aviso Legal",
"datenschutz": "Política de Privacidade", "datenschutz": "Política de Privacidade",
"pageUpdated": "Página atualizada com sucesso" "pageUpdated": "Página atualizada com sucesso",
"useExternalUrl": "Usar URL externa",
"useExternalUrlHelp": "Redirecione os visitantes para uma página externa em vez de mostrar o conteúdo interno. O título e o conteúdo internos permanecem salvos como fallback.",
"externalUrl": "URL externa",
"externalUrlPlaceholder": "https://example.com/impressum",
"externalUrlInvalid": "Deve ser uma URL https:// válida",
"externalUrlActive": "URL externa está ativa — o conteúdo interno é preservado, mas não é exibido aos visitantes."
}, },
"eventTypes": { "eventTypes": {
"title": "Tipos de Evento", "title": "Tipos de Evento",
+7 -1
View File
@@ -1753,7 +1753,13 @@
"lastUpdated": "Последнее обновление:", "lastUpdated": "Последнее обновление:",
"impressum": "Правовая информация", "impressum": "Правовая информация",
"datenschutz": "Политика конфиденциальности", "datenschutz": "Политика конфиденциальности",
"pageUpdated": "Страница успешно обновлена" "pageUpdated": "Страница успешно обновлена",
"useExternalUrl": "Использовать внешний URL",
"useExternalUrlHelp": "Перенаправлять посетителей на внешнюю страницу вместо показа внутреннего содержимого. Внутренний заголовок и содержимое остаются сохранёнными как резервная копия.",
"externalUrl": "Внешний URL",
"externalUrlPlaceholder": "https://example.com/impressum",
"externalUrlInvalid": "Должен быть действительный URL вида https://",
"externalUrlActive": "Внешний URL активен — внутреннее содержимое сохранено, но не отображается посетителям."
}, },
"eventTypes": { "eventTypes": {
"title": "Типы событий", "title": "Типы событий",