From c5c1a3bcaeed1235e1181b71f9f65a8bfbd982b6 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Tue, 16 Jun 2026 17:44:04 -0400 Subject: [PATCH 1/2] feat(submissions): let submitters choose the content language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Public submission forms (Submit Project, Refer Community Project, Refer Local Group) get a "Content language" dropdown at the top that defaults to the page's current locale via useLocale(). The selected language flows through the Next.js API route and lands on the WP REST handler, which validates against CDCF_LOCALE_NAMES and calls pll_set_post_language($post_id, $language) instead of the old hardcoded 'en'. Result: a Spanish-speaking submitter on /es/proyectos who clicks "Referir un proyecto comunitario" gets a form preselected to Spanish, submits Spanish content that lands as a Spanish post in WordPress, and the auto-translation worker (combined with the now-language- agnostic submission-publish pipeline shipped in PR #227/#228/#230) fans out to the OTHER 5 languages with the Polylang group linked end-to-end. No more manual "change to Spanish" step in wp-admin. Server-side validation rejects any language not in CDCF_LOCALE_NAMES with a 400, so a tampered request can't land posts in an unconfigured Polylang language. Defaults to 'en' when the field is absent so legacy callers keep working unchanged. Strings live under common.submissionLanguage / common.submissionLanguageHint in all 6 message files. The LanguageSwitcher's local localeLabels map moves to a new shared src/i18n/locale-labels.ts module so the modals can reuse it. Tests: 3 shared scenarios added to SubmissionHandlerTestBase (provided-honored, default-to-en, reject-unsupported) — 9 RED first across the 3 handlers, 9 GREEN after wiring cdcf_validate_submission_language. Full theme suite 588/588. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/api/refer-community-project/route.ts | 1 + app/api/refer-local-group/route.ts | 1 + app/api/submit-project/route.ts | 1 + components/LanguageSwitcher.tsx | 10 +-- .../sections/ReferCommunityProjectModal.tsx | 26 ++++++- components/sections/ReferLocalGroupModal.tsx | 27 +++++++- components/sections/SubmitProjectModal.tsx | 26 ++++++- messages/de.json | 4 +- messages/en.json | 4 +- messages/es.json | 4 +- messages/fr.json | 4 +- messages/it.json | 4 +- messages/pt.json | 4 +- src/i18n/locale-labels.ts | 10 +++ wordpress/themes/cdcf-headless/functions.php | 3 + .../handlers/refer-community-project.php | 13 +++- .../includes/handlers/refer-local-group.php | 13 +++- .../includes/handlers/submit-project.php | 13 +++- .../cdcf-headless/includes/security.php | 32 +++++++++ .../tests/SubmissionHandlerTestBase.php | 68 +++++++++++++++++++ 20 files changed, 244 insertions(+), 24 deletions(-) create mode 100644 src/i18n/locale-labels.ts diff --git a/app/api/refer-community-project/route.ts b/app/api/refer-community-project/route.ts index 0f42ef4..9d0cd41 100644 --- a/app/api/refer-community-project/route.ts +++ b/app/api/refer-community-project/route.ts @@ -73,6 +73,7 @@ export async function POST(request: NextRequest) { project_url: body.project_url || '', github_url: body.github_url || '', tags: Array.isArray(body.tags) ? body.tags : [], + language: typeof body.language === 'string' ? body.language : '', submitter_name: body.submitter_name, submitter_email: body.submitter_email, verification_code: body.verification_code || '', diff --git a/app/api/refer-local-group/route.ts b/app/api/refer-local-group/route.ts index ff7910e..bb04239 100644 --- a/app/api/refer-local-group/route.ts +++ b/app/api/refer-local-group/route.ts @@ -71,6 +71,7 @@ export async function POST(request: NextRequest) { location: body.location || '', url: body.url, description: body.description, + language: typeof body.language === 'string' ? body.language : '', submitter_name: body.submitter_name, submitter_email: body.submitter_email, verification_code: body.verification_code || '', diff --git a/app/api/submit-project/route.ts b/app/api/submit-project/route.ts index 6df34f8..7b05c72 100644 --- a/app/api/submit-project/route.ts +++ b/app/api/submit-project/route.ts @@ -73,6 +73,7 @@ export async function POST(request: NextRequest) { url: (body.url as string) || '', repo_urls: Array.isArray(body.repo_urls) ? body.repo_urls : [], tags: Array.isArray(body.tags) ? body.tags : [], + language: typeof body.language === 'string' ? body.language : '', submitter_name: body.submitter_name, submitter_email: body.submitter_email, verification_code: body.verification_code || '', diff --git a/components/LanguageSwitcher.tsx b/components/LanguageSwitcher.tsx index 36e47c0..5426a50 100644 --- a/components/LanguageSwitcher.tsx +++ b/components/LanguageSwitcher.tsx @@ -3,19 +3,11 @@ import { useLocale } from 'next-intl' import { useRouter, usePathname } from '@/src/i18n/navigation' import { locales, type Locale } from '@/src/i18n/routing' +import { localeLabels } from '@/src/i18n/locale-labels' import { GlobeAltIcon } from '@heroicons/react/24/outline' import { useState, useRef, useEffect, useTransition } from 'react' import clsx from 'clsx' -const localeLabels: Record = { - en: 'English', - it: 'Italiano', - es: 'Espanol', - fr: 'Francais', - pt: 'Portugues', - de: 'Deutsch', -} - export default function LanguageSwitcher() { const locale = useLocale() const router = useRouter() diff --git a/components/sections/ReferCommunityProjectModal.tsx b/components/sections/ReferCommunityProjectModal.tsx index 30492bf..5a174e7 100644 --- a/components/sections/ReferCommunityProjectModal.tsx +++ b/components/sections/ReferCommunityProjectModal.tsx @@ -1,7 +1,9 @@ 'use client' import { useState, useRef, useCallback } from 'react' -import { useTranslations } from 'next-intl' +import { useTranslations, useLocale } from 'next-intl' +import { locales, type Locale } from '@/src/i18n/routing' +import { localeLabels } from '@/src/i18n/locale-labels' interface ReferCommunityProjectModalProps { buttonLabel: string @@ -11,6 +13,8 @@ type Status = 'idle' | 'sending_code' | 'awaiting_code' | 'submitting' | 'succes export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommunityProjectModalProps) { const t = useTranslations('communityProjects') + const tc = useTranslations('common') + const currentLocale = useLocale() as Locale const dialogRef = useRef(null) const openedAtRef = useRef(0) const [formData, setFormData] = useState<{ fields: Record; tags: string[] }>({ fields: {}, tags: [] }) @@ -50,6 +54,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni description: data.get('description') as string, project_url: data.get('project_url') as string, github_url: data.get('github_url') as string, + language: data.get('language') as string, submitter_name: data.get('submitter_name') as string, submitter_email: data.get('submitter_email') as string, website: data.get('website') as string, @@ -290,6 +295,25 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni /> +
+ + +

{tc('submissionLanguageHint')}

+
+
+
+ + +

{tc('submissionLanguageHint')}

+
+
+
+ + +

{tc('submissionLanguageHint')}

+
+